Compare commits
2 Commits
voice/stt-
...
d175d5ba5a
| Author | SHA1 | Date | |
|---|---|---|---|
| d175d5ba5a | |||
| ce273d14db |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,3 +29,4 @@ memory.bak/
|
|||||||
approved-tasks.json
|
approved-tasks.json
|
||||||
dashboard/status.json
|
dashboard/status.json
|
||||||
tools/anaf-monitor/monitor.log
|
tools/anaf-monitor/monitor.log
|
||||||
|
models/
|
||||||
|
|||||||
@@ -110,7 +110,8 @@
|
|||||||
],
|
],
|
||||||
"user_name": "Marius",
|
"user_name": "Marius",
|
||||||
"default_voice": "F1",
|
"default_voice": "F1",
|
||||||
"auto_leave_minutes": 5
|
"auto_leave_minutes": 5,
|
||||||
|
"stt_model": "/home/moltbot/echo-core/models/whisper-small-ro-cv11-int8"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"personality": "personality/",
|
"personality": "personality/",
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"name": "discord-test",
|
|
||||||
"cron": "0 18 2 4 *",
|
|
||||||
"channel": "echo-core",
|
|
||||||
"model": "haiku",
|
|
||||||
"prompt": "Răspunde doar cu textul: Test Discord cron job — funcționează!",
|
|
||||||
"allowed_tools": [],
|
|
||||||
"enabled": false,
|
|
||||||
"last_run": "2026-04-02T18:09:42.851876+00:00",
|
|
||||||
"last_status": "ok",
|
|
||||||
"next_run": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "anaf-monitor",
|
"name": "anaf-monitor",
|
||||||
"kind": "shell",
|
"kind": "shell",
|
||||||
@@ -23,9 +11,9 @@
|
|||||||
"report_on": "changes",
|
"report_on": "changes",
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"last_run": "2026-06-06T16:00:00.002312+00:00",
|
"last_run": "2026-06-27T16:00:00.001505+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "ok",
|
||||||
"next_run": "2026-06-09T10:00:00+00:00"
|
"next_run": "2026-06-30T10:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "security-audit-daily",
|
"name": "security-audit-daily",
|
||||||
@@ -38,10 +26,10 @@
|
|||||||
],
|
],
|
||||||
"report_on": "changes",
|
"report_on": "changes",
|
||||||
"timeout": 180,
|
"timeout": 180,
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"last_run": "2026-06-09T03:00:00.002688+00:00",
|
"last_run": "2026-06-21T03:00:00.004155+00:00",
|
||||||
"last_status": "error",
|
"last_status": "error",
|
||||||
"next_run": "2026-06-10T03:00:00+00:00"
|
"next_run": "2026-06-22T03:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "kb-index-refresh",
|
"name": "kb-index-refresh",
|
||||||
@@ -55,9 +43,9 @@
|
|||||||
"report_on": "never",
|
"report_on": "never",
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"last_run": "2026-06-09T03:30:00.002397+00:00",
|
"last_run": "2026-06-27T03:30:00.002414+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "ok",
|
||||||
"next_run": "2026-06-10T03:30:00+00:00"
|
"next_run": "2026-06-28T03:30:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "archive-tasks-daily",
|
"name": "archive-tasks-daily",
|
||||||
@@ -71,9 +59,9 @@
|
|||||||
"report_on": "changes",
|
"report_on": "changes",
|
||||||
"timeout": 60,
|
"timeout": 60,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"last_run": "2026-06-09T03:00:00.002281+00:00",
|
"last_run": "2026-06-27T03:00:00.001794+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "ok",
|
||||||
"next_run": "2026-06-10T03:00:00+00:00"
|
"next_run": "2026-06-28T03:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "backup-config",
|
"name": "backup-config",
|
||||||
@@ -87,9 +75,9 @@
|
|||||||
"report_on": "never",
|
"report_on": "never",
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"last_run": "2026-06-09T02:00:00.002899+00:00",
|
"last_run": "2026-06-27T02:00:00.001781+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "ok",
|
||||||
"next_run": "2026-06-10T02:00:00+00:00"
|
"next_run": "2026-06-28T02:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "insights-extract",
|
"name": "insights-extract",
|
||||||
@@ -248,31 +236,31 @@
|
|||||||
"next_run": null
|
"next_run": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "heartbeat-2h",
|
"name": "heartbeat-4h",
|
||||||
"cron": "0 6-18/2 * * *",
|
"cron": "0 6-18/4 * * *",
|
||||||
"channel": "echo-work",
|
"channel": "echo-work",
|
||||||
"model": "sonnet",
|
"model": "sonnet",
|
||||||
"prompt": "Heartbeat check. Rulează src/heartbeat.py printr-un scurt raport de status.\nDacă nu e nimic de raportat (email=0, calendar nu are evenimente <2h, kb ok), răspunde doar cu HEARTBEAT_OK și oprește-te — nu trimite mesaj.\nDacă e ceva: raport scurt pe Discord #echo-work.",
|
"prompt": "Heartbeat check. Rulează src/heartbeat.py printr-un scurt raport de status.\nDacă nu e nimic de raportat (email=0, calendar nu are evenimente <2h, kb ok), răspunde doar cu HEARTBEAT_OK și oprește-te — nu trimite mesaj.\nDacă e ceva: raport scurt pe Discord #echo-work.",
|
||||||
"allowed_tools": [],
|
"allowed_tools": [],
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"last_run": "2026-06-09T08:00:00.001362+00:00",
|
"last_run": "2026-06-27T18:00:00.001107+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "ok",
|
||||||
"next_run": "2026-06-09T10:00:00+00:00"
|
"next_run": "2026-06-28T06:00:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "night-execute",
|
"name": "night-execute",
|
||||||
"cron": "0 23 * * *",
|
"cron": "0 23 * * *",
|
||||||
"channel": "echo-work",
|
"channel": "echo-work",
|
||||||
"model": "opus",
|
"model": "opus",
|
||||||
"enabled": true,
|
"enabled": false,
|
||||||
"prompt": "NIGHT-EXECUTE - Implementare autonoma proiecte aprobate\n\n## PASUL 1: Citeste proiectele aprobate\n\nCiteste /home/moltbot/echo-core/approved-tasks.json\nSelecteaza proiectele cu status='approved'\nDaca nu sunt proiecte aprobate: raporteaza pe Discord si opreste-te.\n\n## PASUL 2: Pentru fiecare proiect aprobat\n\nPentru un proiect cu schema extinsa (campuri optionale {repo, branch, base_branch}):\n - {name} = slug-ul proiectului (cheia 'name' din JSON)\n - {repo} = numele repo-ului Gitea (default = {name} daca nu e setat)\n - {branch} = feature branch nou (None inseamna 'lucreaza pe HEAD-ul default al repo-ului')\n - {base_branch} = branch-ul de la care porneste {branch} (default 'main')\n\n1. Verifica daca workspace-ul exista: /home/moltbot/workspace/{name}\n - Daca NU exista:\n TOKEN=$(grep GITEA_TOKEN /home/moltbot/echo-core/dashboard/.env | cut -d= -f2)\n git clone https://moltbot:${TOKEN}@gitea.romfast.ro/romfast/{repo}.git /home/moltbot/workspace/{name}\n # NOTA: cloneaza {repo}, nu {name}, ca sa suporte features pe repo-uri existente\n # (ex: slug='roa2web-bonuri', repo='roa2web')\n cd /home/moltbot/workspace/{name}\n # Daca {branch} e setat: creeaza branch nou de la {base_branch}\n if [ -n \"{branch}\" ]; then\n git fetch origin {base_branch:-main}\n git checkout {base_branch:-main}\n git checkout -b {branch} 2>/dev/null || git checkout {branch}\n fi\n - Daca EXISTA workspace-ul si {branch} e setat: asigura-te ca esti pe {branch}:\n cd /home/moltbot/workspace/{name}\n git checkout {branch} 2>/dev/null || git checkout -b {branch} {base_branch:-main}\n\n2. Verifica daca prd.json exista: /home/moltbot/workspace/{name}/scripts/ralph/prd.json\n - Daca nu: ruleaza generatorul PRD:\n source .venv/bin/activate\n python3 tools/ralph_prd_generator.py \"{name}\" \"{description}\" /home/moltbot/workspace\n\n3. Lanseaza Ralph loop:\n cd /home/moltbot/workspace/{name}\n chmod +x scripts/ralph/ralph.sh\n mkdir -p scripts/ralph/logs\n nohup ./scripts/ralph/ralph.sh 15 > scripts/ralph/logs/ralph-$(date +%Y%m%d).log 2>&1 &\n echo $! > scripts/ralph/.ralph.pid\n\n4. Actualizeaza approved-tasks.json:\n - status: 'running'\n - started_at: timestamp curent\n - pid: PID din .ralph.pid\n\n## PASUL 3: Raport Discord\n\nTrimite pe echo-work:\n- Cate proiecte au pornit\n- PID-urile lor\n- Pentru cele cu {branch} setat, mentioneaza branch-ul activ\n- 'morning-report va raporta progresul la 08:30'\n\n## REGULI IMPORTANTE\n\n- Nu modifica niciodata src/router.py, src/claude_session.py sau alte fisiere core echo-core prin Ralph\n- echo-core self-improvement NUMAI pe branch ralph/echo-improve, nu pe master\n- Daca ralph.sh esueaza: log in approved-tasks.json (status: failed, error: mesaj)\n- Daca git clone esueaza (repo inexistent): log status='failed' cu mesajul, NU continua cu PRD/ralph\n- Delay 5 secunde intre proiecte pentru a evita rate limiting\n",
|
"prompt": "NIGHT-EXECUTE - Implementare autonoma proiecte aprobate\n\n## PASUL 1: Citeste proiectele aprobate\n\nCiteste /home/moltbot/echo-core/approved-tasks.json\nSelecteaza proiectele cu status='approved'\nDaca nu sunt proiecte aprobate: raporteaza pe Discord si opreste-te.\n\n## PASUL 2: Pentru fiecare proiect aprobat\n\nPentru un proiect cu schema extinsa (campuri optionale {repo, branch, base_branch}):\n - {name} = slug-ul proiectului (cheia 'name' din JSON)\n - {repo} = numele repo-ului Gitea (default = {name} daca nu e setat)\n - {branch} = feature branch nou (None inseamna 'lucreaza pe HEAD-ul default al repo-ului')\n - {base_branch} = branch-ul de la care porneste {branch} (default 'main')\n\n1. Verifica daca workspace-ul exista: /home/moltbot/workspace/{name}\n - Daca NU exista:\n TOKEN=$(grep GITEA_TOKEN /home/moltbot/echo-core/dashboard/.env | cut -d= -f2)\n git clone https://moltbot:${TOKEN}@gitea.romfast.ro/romfast/{repo}.git /home/moltbot/workspace/{name}\n # NOTA: cloneaza {repo}, nu {name}, ca sa suporte features pe repo-uri existente\n # (ex: slug='roa2web-bonuri', repo='roa2web')\n cd /home/moltbot/workspace/{name}\n # Daca {branch} e setat: creeaza branch nou de la {base_branch}\n if [ -n \"{branch}\" ]; then\n git fetch origin {base_branch:-main}\n git checkout {base_branch:-main}\n git checkout -b {branch} 2>/dev/null || git checkout {branch}\n fi\n - Daca EXISTA workspace-ul si {branch} e setat: asigura-te ca esti pe {branch}:\n cd /home/moltbot/workspace/{name}\n git checkout {branch} 2>/dev/null || git checkout -b {branch} {base_branch:-main}\n\n2. Verifica daca prd.json exista: /home/moltbot/workspace/{name}/scripts/ralph/prd.json\n - Daca nu: ruleaza generatorul PRD:\n source .venv/bin/activate\n python3 tools/ralph_prd_generator.py \"{name}\" \"{description}\" /home/moltbot/workspace\n\n3. Lanseaza Ralph loop:\n cd /home/moltbot/workspace/{name}\n chmod +x scripts/ralph/ralph.sh\n mkdir -p scripts/ralph/logs\n nohup ./scripts/ralph/ralph.sh 15 > scripts/ralph/logs/ralph-$(date +%Y%m%d).log 2>&1 &\n echo $! > scripts/ralph/.ralph.pid\n\n4. Actualizeaza approved-tasks.json:\n - status: 'running'\n - started_at: timestamp curent\n - pid: PID din .ralph.pid\n\n## PASUL 3: Raport Discord\n\nTrimite pe echo-work:\n- Cate proiecte au pornit\n- PID-urile lor\n- Pentru cele cu {branch} setat, mentioneaza branch-ul activ\n- 'morning-report va raporta progresul la 08:30'\n\n## REGULI IMPORTANTE\n\n- Nu modifica niciodata src/router.py, src/claude_session.py sau alte fisiere core echo-core prin Ralph\n- echo-core self-improvement NUMAI pe branch ralph/echo-improve, nu pe master\n- Daca ralph.sh esueaza: log in approved-tasks.json (status: failed, error: mesaj)\n- Daca git clone esueaza (repo inexistent): log status='failed' cu mesajul, NU continua cu PRD/ralph\n- Delay 5 secunde intre proiecte pentru a evita rate limiting\n",
|
||||||
"allowed_tools": [
|
"allowed_tools": [
|
||||||
"Bash",
|
"Bash",
|
||||||
"Read",
|
"Read",
|
||||||
"Write"
|
"Write"
|
||||||
],
|
],
|
||||||
"last_run": "2026-06-08T23:00:00.001531+00:00",
|
"last_run": "2026-06-20T23:00:00.001763+00:00",
|
||||||
"last_status": "ok",
|
"last_status": "error",
|
||||||
"next_run": "2026-06-09T23:00:00+00:00"
|
"next_run": "2026-06-21T23:00:00+00:00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"last_sent": 21,
|
"last_sent": 24,
|
||||||
"year": 2026,
|
"year": 2026,
|
||||||
"last_sent_at": "2026-06-04T19:53:04.648928+00:00"
|
"last_sent_at": "2026-06-25T17:01:48.783259+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# How the Top 1% Actually Run Claude Code Now
|
||||||
|
|
||||||
|
**URL:** https://youtu.be/2-0lxK2wgJ8
|
||||||
|
**Data:** 2026-06-09
|
||||||
|
**Tags:** @work @growth #loops #agents #automation #claude-code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Videoul descrie tranziția de la Stage 2 (juglezi manual mai mulți agenți) la Stage 3 (proiectezi loop-uri autonome care promtează agenții în locul tău). Unitatea de muncă nu mai e prompt-ul individual — e loop-ul întreg. Boris Czerny (Anthropic) și creatorul OpenAI confirmă ambii că acesta e viitorul. Se discută inner loops, outer loops, memory pentru loops și problema entropy/slop acumulat.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
1. **Paradigm shift central:** Nu mai promtezi agenți direct — proiectezi loop-uri care promtează agenții autonom. "I don't prompt Claude anymore. I have loops that are running."
|
||||||
|
|
||||||
|
2. **Cele 3 etape:**
|
||||||
|
- Stage 1: AI autocomplete (scrii cod, AI completează)
|
||||||
|
- Stage 2: Juglezi manual mai mulți agenți (unde sunt cei mai mulți acum)
|
||||||
|
- Stage 3: Proiectezi loop-uri care rulează agenții — leverage urcă un nivel
|
||||||
|
|
||||||
|
3. **Inner loop concret** (spec → implementare → code review → fix → verificare → merge PR → monitorizare erori post-merge). Loops mai mici se imbricau deja în workflow-ul manual — acum devin un singur loop automat.
|
||||||
|
|
||||||
|
4. **Outer loop** monitorizează surse externe (competitori, arxiv, Sentry) și alimentează inner loop-ul cu noi specs. Human-in-the-loop la aprobare rămâne important.
|
||||||
|
|
||||||
|
5. **Memory pentru loops:** Git/commits ca memorie naturală (ce a fost deja fix-uit ieri). Slack sau Airtable ca decision surface — botul postează recomandări, reacția ta cu emoji = instrucțiune pentru următoarea rulare.
|
||||||
|
|
||||||
|
6. **Problema entropy/slop:** Loop-urile pot acumula erori rapid ("collapse entropy into a much shorter time"). Soluția: un "oracle extern" obiectiv — teste care trec, erori reale din producție, Stripe revenue, reply rates — care ține loop-ul în check. Adversarial code review ca mecanism anti-entropy.
|
||||||
|
|
||||||
|
7. **Meta-loop:** Un loop care monitorizează toate celelalte loop-uri, identifică ce funcționează, ce nu, și propune noi loop-uri (cu aprobare umană).
|
||||||
|
|
||||||
|
8. **Relevanță directă pentru Echo/Ralph:** Sistemul Ralph din echo-core este exact Stage 3. Night-execute + ralph.sh + morning-report = un loop complet. Outer loop = evening-report care propune, Marius aprobă, inner loop = ralph.sh per story.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "I don't prompt Claude anymore. I have loops that are running. They're the ones that are prompting Claude and kind of figuring out what to do. My job is to write loops."
|
||||||
|
— Boris Czerny (Anthropic)
|
||||||
|
|
||||||
|
> "A unit of work being the loop itself rather than just a prompt."
|
||||||
|
|
||||||
|
> "Eventually you stop being the thing that runs. You design the thing that runs and the leverage moves up one more layer."
|
||||||
|
|
||||||
|
> "Entropy is slowly increasing over time anyway. And agents, because they can do a lot of work, basically collapse that into a much shorter time."
|
||||||
|
|
||||||
|
> "Are these tokens actually economically valuable? Are they making a difference to my bottom line?"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Idei acționabile
|
||||||
|
|
||||||
|
- [ ] Revizuiește ralph.sh ca inner loop complet: spec (final-plan.md) → implementare → /review → /qa → merge PR → /loop monitorizare erori
|
||||||
|
- [ ] Outer loop pentru Echo: un job săptămânal care analizează ce loop-uri rulează, care au impact, și propune loop-uri noi
|
||||||
|
- [ ] Memory pattern Slack-like: evening-report ca decision surface cu reacții/comenzi simple (/a, /k) — deja implementat, validat de video
|
||||||
|
- [ ] Oracle anti-entropy: adaugă metrică obiectivă per proiect Ralph (teste passing %, build status) ca exit condition pentru loop
|
||||||
110
memory/kb/youtube/2026-06-12_iulia-borcsa-suplimente.md
Normal file
110
memory/kb/youtube/2026-06-12_iulia-borcsa-suplimente.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Dezvoltator Suplimente: "Producătorii De Vitamine Au Un Truc Ascuns" | Iulia Borcsa | Gândește Diferit
|
||||||
|
|
||||||
|
**Sursă:** https://www.youtube.com/watch?v=GYiHEMUCvGI
|
||||||
|
**Data:** 2026-06-12
|
||||||
|
**Durată:** ~117 minute
|
||||||
|
**Tags:** @health @growth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Iulia Borcsa, cercetător și dezvoltator de suplimente în Germania (~10 ani), explică ce nu știe consumatorul mediu despre industria suplimentelor: aditivi ascunși, forme cu biodisponibilitate scăzută, trucuri de marketing pe etichetă și cum să alegi corect fiecare supliment. Concluzia: 80-90% din suplimentele de pe piață conțin aditivi problematici, materia primă vine majoritar din China, brandurile diferite pot fi identice în interior, iar forma contează mai mult decât brandul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
### Aditivii — trucul ascuns din industrie
|
||||||
|
- **80-90% din suplimente** conțin aditivi și excipienți pentru producție, profit și termen de valabilitate
|
||||||
|
- **Stearat de magneziu (E470B)** — cel mai folosit aditiv, interferează cu absorbția substanței active, slăbește sistemul imunitar pe termen lung; la 10 capsule/zi ajungi la >1g, periculos
|
||||||
|
- **Celuloză microcristalină** — umplutură de capsulă, poate conține nanoparticule care nu se elimină din corp
|
||||||
|
- **Dioxid de siliciu** — agent de stabilizare în tablete
|
||||||
|
- Tabletele și comprimatele au CEI MAI MULȚI aditivi; formele lichide/uleioase au cei mai puțini
|
||||||
|
- Tabletele efervescente sunt problematice: arome sintetice, coloranți artificiali, corectori de aciditate
|
||||||
|
|
||||||
|
### Cum să citești eticheta corect
|
||||||
|
- **Magneziu bisglicinat 1000mg** pe față ≠ 1000mg magneziu elementar — sunt ~100-110mg magneziu + ~890mg glicină
|
||||||
|
- Caută mereu **mg substanță activă elementară** și **% din doza zilnică recomandată**
|
||||||
|
- Verifică termenul de valabilitate — unele produse nu îl au printat
|
||||||
|
- Suplimentele lichide cu vitamina C și B degradează după deschidere; după 6-12 luni poți primi 30-50% din cantitatea declarată
|
||||||
|
- Cumpără cât mai aproape de data fabricării
|
||||||
|
|
||||||
|
### Formele suplimentelor — de la bună la proastă
|
||||||
|
1. **Picături** — cea mai bună biodisponibilitate (absorbție începe în cavitatea bucală)
|
||||||
|
2. **Capsule uleioase** — vitamina D3 dizolvată deja în ulei, absorbție excelentă
|
||||||
|
3. **Capsule gelatinoase moi** — bune pentru liposolubile
|
||||||
|
4. **Capsule clasice (pulbere)** — medii, mai puțini aditivi decât tabletele
|
||||||
|
5. **Tablete comprimate** — cei mai mulți aditivi, evitați când posibil
|
||||||
|
6. **Efervescente** — colorate, aromate artificial, evitate
|
||||||
|
|
||||||
|
### Industria — cum funcționează de fapt
|
||||||
|
- Oricine poate lansa un supliment fără cunoștințe; producătorul oferă formula cea mai ieftină
|
||||||
|
- Private label: branduri diferite = aceeași pilulă din aceeași fabrică, prețuri diferite din marketing
|
||||||
|
- Materia primă vine majoritar din China (nu neapărat rău, dar variabil calitativ)
|
||||||
|
- România: zero controale aleatorii pe rafturi; alte țări au demonstrat degradarea conținutului
|
||||||
|
|
||||||
|
### Ghid per supliment
|
||||||
|
|
||||||
|
**Vitamina D3**
|
||||||
|
- Formă ideală: picături în ulei sau capsule uleioase cu D3+K2
|
||||||
|
- Nu tablete/capsule uscate — biodisponibilitate mult mai slabă
|
||||||
|
- K2 obligatoriu alături de D3 (direcționează calciul spre oase, nu artere)
|
||||||
|
|
||||||
|
**Magneziu**
|
||||||
|
- Bisglicinat = formă cu absorbție bună și toleranță digestivă
|
||||||
|
- Citrat = mai ieftin, ușor laxativ în doze mari
|
||||||
|
- Oxidul de magneziu — evitat, biodisponibilitate minimă (~4%)
|
||||||
|
- Verifică mg elementar pe etichetă, nu mg compus total
|
||||||
|
|
||||||
|
**Omega 3**
|
||||||
|
- Forma **trigliceride** (naturală) > forma ester etilici (prelucrată, biodisponibilitate scăzută)
|
||||||
|
- Catre oxidare rapidă — verifică să conțină antioxidanți (extract de rozmarin)
|
||||||
|
- Omega 3 oxidat pe termen lung face mai mult rău decât bine
|
||||||
|
- Preferă ulei proaspăt, nu concentrate ultraprelucrate
|
||||||
|
|
||||||
|
**Vitamina B12**
|
||||||
|
- **Methylcobalamin** sau Adenosylcobalamin > Cyanocobalamin
|
||||||
|
- Cyanocobalamin = ieftin, sintetizat, necesită conversie în organism (mulți nu o fac eficient)
|
||||||
|
- Methylcobalamin = forma activă, direct utilizabilă
|
||||||
|
|
||||||
|
**Fier**
|
||||||
|
- Se ia SINGUR, la 2 ore distanță de orice alt supliment și de cafea
|
||||||
|
- Interferează cu: magneziu, zinc, calciu — se inhibă reciproc
|
||||||
|
- Vitamina C crește absorbția fierului (se poate combina)
|
||||||
|
|
||||||
|
**Zinc**
|
||||||
|
- Nu se combină cu cupru (se inhibă reciproc la absorbție)
|
||||||
|
- Bisglicinat sau citrat > oxid de zinc
|
||||||
|
|
||||||
|
**Calciu**
|
||||||
|
- Nu combina cu fier sau zinc simultan
|
||||||
|
- Calciul poate inhiba absorbția mai multor minerale — luat separat
|
||||||
|
|
||||||
|
**Vitamina C**
|
||||||
|
- Forma liposomală = absorbție superioară față de tablete standard
|
||||||
|
- Mega-doze în tablete — cea mai mare parte eliminată prin urină
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri memorabile
|
||||||
|
|
||||||
|
> "Dacă suplimentezi cu un omega 3 oxidat, mai ales pe termen lung, îți pot face mai mult rău decât bine."
|
||||||
|
|
||||||
|
> "Tu când deschizi flaconul, iei o capsulă, îl închizi, deschizi iar — posibil să mai iei din produs în jur de 50% din cantitatea declarată de producător."
|
||||||
|
|
||||||
|
> "Dai un search pe internet și cauți magneziu, găsești 100 de produse. Tu n-o să ieși din carență de vitamina D cu un supliment prost formulat, pentru că vitaminele și mineralele concurează la absorbție. Se pot anula între ele."
|
||||||
|
|
||||||
|
> "90% dintre suplimentele de pe piață conțin [aditivi problematici]."
|
||||||
|
|
||||||
|
> "Oricine poate lansa un supliment pe piață. Neavând cunoștințele necesare, tu vii la mine, eu sunt producătorul, și îți dau o rețetă care mie în producție îmi este mult mai ieftină."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Idei acționabile
|
||||||
|
|
||||||
|
- [ ] La next cumpărătură suplimente: verifică forma (nu oxid, nu ester etilici), mg elementar, aditivi pe etichetă @health
|
||||||
|
- [ ] Omega 3 actual: verifică dacă e trigliceride sau ester etilici și dacă are antioxidanți (rozmarin) @health
|
||||||
|
- [ ] Vitamina D3: treci pe picături în ulei dacă ești pe capsule uscate @health
|
||||||
|
- [ ] B12: verifică dacă e methylcobalamin, nu cyanocobalamin @health
|
||||||
|
- [ ] Fier (dacă iei): izolat, nu combinat cu altele @health
|
||||||
49
memory/kb/youtube/2026-06-14_claude-trading-102k.md
Normal file
49
memory/kb/youtube/2026-06-14_claude-trading-102k.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# I Tested Letting Claude Trade For A Month and Made $102k
|
||||||
|
|
||||||
|
**URL:** https://youtu.be/RetsRS5u-8Q
|
||||||
|
**Data:** 2026-06-14
|
||||||
|
**Durată:** 19:06
|
||||||
|
**Creator:** Brendan (AI trading, background UCLA math/econ + Raymond James investment banking)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Un trader cu background în matematică și finanțe a folosit Claude ca analist și portfolio manager timp de o lună (mai 2026), începând cu $66k și terminând cu ~$169k (+155%). Claude a propus strategia, a găsit ticker-ele, a ales contractele de opțiuni și a monitorizat zilnic portofoliul printr-un sistem deterministic construit tot cu Claude.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
- **Strategia de bază:** LEAPS (opțiuni call cu expirare >1 an) pe companii mari + shares pe small-cap (opțiunile erau prea scumpe sau iliquide)
|
||||||
|
- **Două moduri Claude:** non-deterministic (analyst mode — cercetare, recomandări, analiză) + deterministic (cod Python care rulează zilnic automat)
|
||||||
|
- **Ticker-e alese de Claude:** OCR, NBIS, Service Now, HIMS, MP, Nokia — scorificate pe: catalyst timing, IV environment, corelație cu piața, teză individuală
|
||||||
|
- **Cost lunar:** ~$1/zi pentru API (rulat o dată pe zi pentru news + analiză) — inițial folosit Claude web (subscription) pentru research, API doar pentru monitorizare
|
||||||
|
- **Dashboard construit cu Claude:** valorizare live Yahoo Finance, alerts pe ținte/stop-uri, theta decay, macro gate (VIX, market breadth, credit spreads), news zilnic per poziție
|
||||||
|
- **Sistem în 4 layere:** (1) date + valorizare, (2) analytics portofoliu, (3) context macro + news, (4) alerts + dashboard
|
||||||
|
- **Cheie succesului:** context detaliat dat lui Claude (account size, risk tolerance, holding period) → output specific, nu generic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "The more specific your input, the better your output is. This way, you won't get pretty generic garbage that you would typically see if you ask open-ended questions to Claude."
|
||||||
|
|
||||||
|
> "Claude here didn't treat these all equally. It scored them across different dimensions."
|
||||||
|
|
||||||
|
> "Every single trade plus adjustment in the portfolio was all made by Claude."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Idei acționabile
|
||||||
|
|
||||||
|
- Sistem de monitorizare portofoliu cu 4 layere poate fi adaptat pentru **orice asset**, nu doar opțiuni
|
||||||
|
- Pattern replicabil: Claude web pentru research inițial (subscription, fără cost API) → cod Python automat pentru monitorizare zilnică
|
||||||
|
- LEAPS = risc calculat: expirare >45 zile elimină time decay agresiv, upside mare, downside capped față de short-dated options
|
||||||
|
- Diversificare pe catalyst windows (short/medium/long) și sectoare (healthcare, AI infra, enterprise software, European AI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
@work @growth #trading #claude #automatizare #options #investing
|
||||||
66
memory/kb/youtube/2026-06-19_business-gurus-5m-review.md
Normal file
66
memory/kb/youtube/2026-06-19_business-gurus-5m-review.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# We Spent $5M on Business Gurus, So You Don't Have To
|
||||||
|
|
||||||
|
**Sursă:** https://youtu.be/14fe4x5e4bk
|
||||||
|
**Data:** 2026-06-19
|
||||||
|
**Durată:** 65 minute
|
||||||
|
**Tip:** Podcast | @growth @work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Doi antreprenori cu afaceri de 8-9 cifre (Nick Fischer - New Reach, $150M+/an) analizează cele mai valoroase cursuri și guru-uri în care au investit colectiv $5M+. Concluzia principală: primele câteva investiții în fundamentele marketingului și copywriting-ului au generat cel mai mare ROI — nu cursurile scumpe de strategie avansată.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
### Honorable mentions — copywriting
|
||||||
|
- **Frank Kern** — Framework simplu și puternic: *"Here's what I got, here's what it'll do for you, here's what to do next."* Un singur post Facebook cu acest format → $171k revenue
|
||||||
|
- **Jason Fladlien** — Cel mai bun la vânzări one-to-many (webinare), master al obiecțiilor
|
||||||
|
- **John Benson** — Proces mecanic pentru generarea de "big ideas" de marketing (big idea map)
|
||||||
|
- **Todd Brown (E5)** — Cel mai bun teacher de direct response marketing: thesis → central marketing argument → subbeliefs → claims → proof → assets de marketing/vânzări
|
||||||
|
|
||||||
|
### #5 — Alex Hormozi (gratis)
|
||||||
|
- Cursul de lead gen pe acquisition.com — framework "more, better, new"
|
||||||
|
- Ordinea: **Mai mult** (volum) → **Mai bine** (optimizare) → **Nou** (idei noi)
|
||||||
|
- Greșeala comună: oamenii sar direct la "nou" când nu au testat nici "mai mult"
|
||||||
|
- Recomandat ca prim pas pentru orice om nou din echipă
|
||||||
|
|
||||||
|
### Concept esențial: Dimensionalized Pain Points
|
||||||
|
Când descrii durerea clientului, nu rămâne la abstract — **ancorează-o senzorial**:
|
||||||
|
- Ce **vede**? (ex: calendarul gol)
|
||||||
|
- Ce **aude**? (ex: "nu am buget")
|
||||||
|
- Ce **simte**? (ex: instabilitate financiară)
|
||||||
|
|
||||||
|
Diferența: "Nu ai leads?" (abstract) vs "Arată calendarul tău așa?" (concret, vizual) → al doilea convertește mult mai bine.
|
||||||
|
|
||||||
|
### Copy Platform — instrument practic
|
||||||
|
Un document cu: thesis → subbeliefs → belief ladder → empathy → common enemy → pain points dimensionalizate.
|
||||||
|
- Produce automat 100+ idei de ads
|
||||||
|
- Nu mai pornești niciodată de la pagină albă
|
||||||
|
- AI poate ajuta să completezi golurile
|
||||||
|
|
||||||
|
### Grit ca fundament
|
||||||
|
Nick — 50 de email-uri reci/zi, manual, timp de 1 an, fără răspuns (domeniu blocat din prima zi cu 400 email-uri). Lecția: disciplina procesului e critică, dar trebuie să validezi premisele tehnice de bază.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "Here's what I got, here's what it'll do for you, here's what to do next. It's just the most simple direct offer copy of all time." — despre Frank Kern
|
||||||
|
|
||||||
|
> "Specific and tangible and concrete language is what converts. 'Does your calendar look like this?' — see how there's a difference there?" — despre dimensionalized pain points
|
||||||
|
|
||||||
|
> "You got to do more before you can refine or do better ads, and you got to do better ads before you start thinking of new concepts altogether." — framework Hormozi
|
||||||
|
|
||||||
|
> "You will come out of that copy platform process with a hundred ad ideas. It'll just flow." — despre procesul de ideație
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Aplicabil pentru Marius
|
||||||
|
|
||||||
|
- **Todd Brown E5** — relevant dacă vrei să îmbunătățești comunicarea ROA (thesis clar, argumente, dovezi)
|
||||||
|
- **"More, better, new"** — înainte să cauți clienți noi, maximizează ce ai deja (clienți existenți, mai multă muncă la preț bun)
|
||||||
|
- **Dimensionalized pain points** — util pentru orice prezentare sau propunere comercială ROA
|
||||||
|
- **Copy platform** — chiar și pentru un ERP: ce vede clientul când are probleme? Calendar de raportări ratat, erori Oracle, angajat care nu știe să folosească modulul...
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Matt Pocock's Agentic Engineering Workflow (just copy him)
|
||||||
|
|
||||||
|
**Sursa:** https://youtu.be/nQwJVHCtDDY
|
||||||
|
**Data:** 2026-06-19
|
||||||
|
**Durata:** 62:24
|
||||||
|
**Tags:** @work @growth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Matt Pocock (educator TypeScript, autor skills pentru Claude Code) explica filosofia sa de lucru cu AI: nu modelul conteaza cel mai mult, ci harness-ul (setup-ul, skill-urile, codebase-ul). AI a "mancat" programarea tactica — acum conteaza sa fii bun la programarea strategica. Foloseste AFK agents (Away From Keyboard) via Sand Castle + GitHub Actions, si o abordare de tip coada (queue), nu loop infinit.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
- **Tactical vs Strategic programming** — AI a preluat complet programarea tactica (scris cod, debug sintaxa). Ceea ce conteaza acum e strategic: arhitectura, interfetele, documentatia, task-urile bine scopate. Nu poti delega strategia catre AI.
|
||||||
|
|
||||||
|
- **Harness > Model** — Toata lumea e obsedta de model. Matt argumenteaza 50/50: modelul si harness-ul conteaza la fel. Un codebase bun permite un model mai ieftin sa dea acelasi rezultat. Optimizeaza harness-ul, nu astepta modelul nou.
|
||||||
|
|
||||||
|
- **Skill-urile ca procedures, nu abilities** — Preferi proceduri pe care TU le invoci, nu abilitati pe care modelul le alege. Ai control. Nu lasa modelul sa-ti faca gandirea strategica.
|
||||||
|
|
||||||
|
- **AFK agents + Sand Castle** — Ruleaza agenti in sandbox-uri (Docker/Podman) via Sand Castle, in GitHub Actions. Paralelizezi fara sa blochezi masina locala. Momentul in care a descoperit AFK = momentul in care outputul sau a explodat.
|
||||||
|
|
||||||
|
- **Queue, nu Loop** — Nu ai nevoie de un loop infinit. Ai nevoie de o coada de task-uri. Agent-ul preia un task, il rezolva, gata. Cum functioneaza orice echipa de developeri.
|
||||||
|
|
||||||
|
- **Skill /teach** — Skill stateful care creeaza cursuri personalizate pe baza misiunii tale. Foloseste principii pedagogice: zone of proximala development, quizuri, learning record. Matt a invatat Rubik's cube cu el.
|
||||||
|
|
||||||
|
- **Skills sunt abilitati ale tau, nu ale AI** — Cunostintele, skill-urile si intelepciunea tale sunt multiplicatorul. AI nu poate trece de nivelul tau de intelegere. Seniori iau boost 10x tocmai de aceea.
|
||||||
|
|
||||||
|
- **Codebase bun = model mai ieftin** — Daca arhitectura e clara, un model mai slab face aceeasi treaba. AI nu trebuie sa "bata capul de perete" pentru a naviga un codebase haotic.
|
||||||
|
|
||||||
|
- **Self-improving systems** — Daca AI a gasit un bug de securitate, intreaba-te DE CE a aparut. Construieste sisteme (cron jobs, review loops) care previn recurenta. Nu multumi AI-ului — repara procesul.
|
||||||
|
|
||||||
|
- **Human-in-the-loop checkpoints** — Impinge checkpointurile cat mai spre dreapta (spre final), dar nu elimina observabilitatea. Vrei sa imbunatatesti si harness-ul, nu doar codul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "AI has basically eaten tactical programming. It's gone. You need to be great at strategic programming in order to get the most out of this infinite fleet of tactical programmers."
|
||||||
|
|
||||||
|
> "Your skills are the ceiling on what AI can do."
|
||||||
|
|
||||||
|
> "Have a codebase that's easier to make changes in — that's how you optimize for token spend."
|
||||||
|
|
||||||
|
> "People are focused on the wrong thing. They're looking at the big shiny new thing when in fact just focus on the stuff that's been working for 30-40 years."
|
||||||
|
|
||||||
|
> "If someone keeps stealing your bike, maybe buy a lock."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Idei acționabile
|
||||||
|
|
||||||
|
- [ ] Sterge toate skill-urile/MCPuri, observa agentul "gol", apoi adauga inapoi doar ce-ti lipseste cu adevarat @work
|
||||||
|
- [ ] Gandeste in cozi de task-uri (queue), nu in loop-uri infinite @work
|
||||||
|
- [ ] La fiecare bug descoperit de AI, intreaba "de ce a aparut?" si construieste un sistem care previne recurenta @work
|
||||||
|
- [ ] Skill-urile utile: `grill me` (interviuri adversariale inainte de implementare), `teach` (invatare personalizata) @growth
|
||||||
|
- [ ] AFK agents pentru task-uri bine scopate = paralelizare fara effort @work
|
||||||
56
memory/kb/youtube/2026-06-21_claude-code-anki-setup.md
Normal file
56
memory/kb/youtube/2026-06-21_claude-code-anki-setup.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# This Claude Code Setup Changed My Life (Seriously…)
|
||||||
|
|
||||||
|
**URL:** https://youtu.be/1sMHcJMxYqo
|
||||||
|
**Durata:** 13:47
|
||||||
|
**Data:** 2026-06-21
|
||||||
|
**Tags:** @growth @work @productivity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Combini Claude Code cu Anki (prin Anki Connect add-on) pentru a automatiza crearea și optimizarea flashcard-urilor. Claude Code citește videoclipuri, lecturi, transcrieri și generează automat carduri Anki — inclusiv diagrame, timestamps și note-type-uri custom. Dincolo de creare, Claude Code poate analiza progresul, re-prioritiza carduri, detecta leeches și închide bucla de învățare.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
1. **Anki Connect** — add-on open-source care permite altor programe (inclusiv Claude Code) să comunice cu Anki. Instalare: Tools → Add-ons → Get Add-ons → cod Anki Connect.
|
||||||
|
|
||||||
|
2. **Creare automată flashcards** din orice sursă:
|
||||||
|
- URL YouTube (+ transcrieri automate)
|
||||||
|
- Videoclipuri descărcate local
|
||||||
|
- Lecturi/cursuri
|
||||||
|
- Instrucțiunea include: note type custom, diagrame relevante, timestamps, ordonare beginner→advanced
|
||||||
|
|
||||||
|
3. **Principiu de bază:** Concentrează-te 100% pe înțelegerea materialului. Lasă Claude Code să facă flashcard-urile. Lasă Anki să facă repetarea.
|
||||||
|
|
||||||
|
4. **Optimizare avansată a deck-urilor:**
|
||||||
|
- **Prioritizare** — re-ordonează carduri pentru exam sau topic urgent (ex: vocabular AI pentru videoclipuri japoneze)
|
||||||
|
- **Leech surgery** — identifică pattern-uri la carduri eșuate des, rescrie sau simplifică
|
||||||
|
- **Laddering** — când un card e prea greu, generează carduri intermediare pentru conceptele lipsă
|
||||||
|
- **Confusable pairs** — detectează carduri mixate între ele, creează card explicit de diferențiere
|
||||||
|
- **Example diversification** — adaugă exemple variate ca un fapt să se generalizeze, nu să fie context-bound
|
||||||
|
- **Test loop** — hrănește rezultate de la examen/practice papers înapoi în Claude Code → re-prioritizare și rescriere în stilul examenului
|
||||||
|
- **Apply check** — conectează Claude Code la tool de recording (ex: Granola MCP) și verifică dacă aplici în viața reală ce înveți din carduri
|
||||||
|
- **Refactoring** — sparge carduri mari în carduri atomice (minimum information principle)
|
||||||
|
|
||||||
|
5. **Workflow zilnic:** Claude Code inspectează Anki zilnic sau la 2 zile → analizează ce carduri eșuezi → propune acțiuni concrete (surgery, laddering, prioritizare)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "Your imagination is basically the limit when it comes to using this kind of stuff."
|
||||||
|
|
||||||
|
> "Most of your time when consuming content like lectures or videos should be focused on actually understanding the material — leave the recall part to Anki."
|
||||||
|
|
||||||
|
> "A fact memorized from one example is context-bound. It may not trigger in other contexts."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Idei acționabile
|
||||||
|
|
||||||
|
- [ ] Instalez Anki Connect și testez dacă Claude Code vede Anki-ul local
|
||||||
|
- [ ] Testez cu un URL YouTube → generare flashcards automat
|
||||||
|
- [ ] Minimum information principle: sparg carduri mari în carduri atomice
|
||||||
69
memory/kb/youtube/2026-06-23_remote-boring-businesses.md
Normal file
69
memory/kb/youtube/2026-06-23_remote-boring-businesses.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# 100% REMOTE Boring Businesses (That Almost Never Fail)
|
||||||
|
|
||||||
|
**Sursa:** https://youtu.be/EnSJN9zl-yA?si=sX8Cmtt4mEZQafKa
|
||||||
|
**Data:** 2026-06-23
|
||||||
|
**Durata:** 15:37
|
||||||
|
**Tags:** @work @growth @antreprenoriat
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Fondatorul unui business de $23M/lună face un ranking al afacerilor remote. Concluzia: cele mai bune nu sunt cele "sexy" (dropshipping, SEO, FBA) ci **expertiza + proces + autoritate** — adică afaceri de compliance/consultanță specializată unde clientul plătește ca să elimine un risc, nu ca să cumpere ore. AI amplifică aceste afaceri în loc să le distrugă.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ranking (F → S tier)
|
||||||
|
|
||||||
|
| Tier | Afacere | De ce |
|
||||||
|
|------|---------|-------|
|
||||||
|
| **F** | Dropshipping | Fără moat, fără ownership, mori când cresc costurile de ads |
|
||||||
|
| **F** | Affiliate SEO | AI a inundat internetul, Google a tăiat traficul small sites |
|
||||||
|
| **C** | Amazon FBA | Ești chiriaș pe terenul Amazon, risc platformă enorm |
|
||||||
|
| **C** | VA Agency | People business stresant + AI va distruge marginile în 3 ani |
|
||||||
|
| **B** | Bookkeeping / Billing / Payroll | Recurent, dureros, document-heavy — AI îți crește leverage |
|
||||||
|
| **B-A** | Software Implementation (HubSpot, NetSuite, Salesforce) | Ești expert pe un platform deja vândut — "restaurant in stadion sold out" |
|
||||||
|
| **A** | Fractional Executive (CFO, COO, CMO) | $5-15k/lună × 4-6 clienți, AI multiplică capacitatea |
|
||||||
|
| **S** | Compliance / Expert Stamp business | Plătești să elimini risc, nu ore — modelul Intertek |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
- **Modelul S tier = expertiza + proces + autoritate.** Clientul nu plătește ore, plătește eliminarea unui risc. Un quote de $33,600 plătit fără negociere pentru compliance mini-split.
|
||||||
|
- **Formula B tier:** Recurring + Painful + Document-heavy. Dacă adaugi "clientul răspunde cu numele lui dacă greșești" → S tier.
|
||||||
|
- **AI nu distruge expertul, îl amplifică.** Un expert care semna pe 3 clienți poate semna pe 5-6 cu AI. Stampila rămâne a omului.
|
||||||
|
- **Fractional executive** e ideal pentru 10+ ani experiență în finance/ops/marketing — nu mai aplica la full-time, trimite 50 mesaje pe LinkedIn la fondatori cu $2-20M revenue.
|
||||||
|
- **Bookkeeping:** nu ai nevoie de CPA. Înveți QuickBooks/Xero, alegi o nișă (contractors, dentists, real estate), 25 cold emails azi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "You're a tenant on Amazon's land, and the rent goes up every year."
|
||||||
|
|
||||||
|
> "The customer isn't paying for hours — they're paying to eliminate risk. That is not a labor bill, it was an insurance policy."
|
||||||
|
|
||||||
|
> "If you can kill the risk, you can make a killing."
|
||||||
|
|
||||||
|
> "The real AI opportunity isn't in building AI companies, it's rebuilding boring expert businesses with AI."
|
||||||
|
|
||||||
|
> "The best remote businesses in 2026 are completely different than you'd think."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7-Day Playbook (S tier start)
|
||||||
|
|
||||||
|
1. **Zi 1** — Alege un workflow: product compliance, R&D tax credits, HIPAA, OSHA (unul singur)
|
||||||
|
2. **Zi 2** — Citește ghidurile oficiale, nu guru-uri — practicieni
|
||||||
|
3. **Zi 3** — Alege nișa (ex: OSHA pentru producție, R&D credits pentru software)
|
||||||
|
4. **Zi 4** — One-page offer: ce faci, pentru cine, cât, cum. Headline = riscul eliminat, nu orele tale
|
||||||
|
5. **Zi 5** — Lista 50 de proprietari de business din nișă (LinkedIn, Apollo, asociații de industrie)
|
||||||
|
6. **Zi 6** — Trimite 50 mesaje: scurt, specific, lead cu suma pe care o pierd dacă greșesc
|
||||||
|
7. **Zi 7** — 3 apeluri. Nu trebuie să închizi nimic — înveți mai mult decât 3 luni de YouTube
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relevanță pentru Marius
|
||||||
|
|
||||||
|
**ERP ROA = software implementation model.** Marius este deja în B-A tier: implementează un ERP complicat pe care clienții nu îl înțeleg singuri. Exact modelul "restaurant in stadion sold out." Potențial de fractional consulting pe module specifice (D406, e-Factura, migrare Oracle).
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# #1 Biggest Mistake Blocking Your Breakthrough (Codie Sanchez)
|
||||||
|
|
||||||
|
**Sursa:** https://youtu.be/ack8NtknHMw?si=3O2zDbmlXtpgsUyK
|
||||||
|
**Data:** 2026-06-24
|
||||||
|
**Durata:** 10:03
|
||||||
|
**Tags:** @mindset @coaching @performance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Tony Robbins (neidentificat explicit, dar stilul și conținutul sunt clare) explică de ce oamenii eșuează să aibă un breakthrough: atacă problemele în ordinea greșită. Cei 3 S ai unui breakthrough trebuie aplicați în ordinea **State → Story → Strategy**, nu invers cum fac aproape toți.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cei 3 S ai unui Breakthrough (în ordinea corectă)
|
||||||
|
|
||||||
|
### 1. STATE (primul, cel mai important)
|
||||||
|
- Starea ta mentală/emoțională filtrează tot — percepția, memoria, motivația
|
||||||
|
- Când ești furios, îți amintești orice greșeală a celeilalte persoane. Când ești îndrăgostit, nimic nu e greșit.
|
||||||
|
- **State → Story → Behavior.** Dacă state-ul e greșit, niciun tool/strategie nu funcționează
|
||||||
|
- "Total resolve" vs "dieting mindset" — fasting e mai ușor decât dieta pentru că e absolut. Negocierea cu tine însuți te slăbește.
|
||||||
|
- **Greșeala:** "Mă voi pune pe dietă, chiar o voi face" spus fără resolve = nu se va întâmpla niciodată
|
||||||
|
|
||||||
|
### 2. STORY (al doilea)
|
||||||
|
- Narativa despre tine îți controlează viața mai mult decât orice altceva
|
||||||
|
- O credință = ceva ce ți-ai spus ție atât de des încât l-ai acceptat ca adevăr
|
||||||
|
- Frica de bază a tuturor (regi, câștigători de Grammy, jucători NFL): *"Nu sunt suficient. Nu voi fi iubit."*
|
||||||
|
- "Am încercat totul" = de fapt ai repetat același 2 lucruri care nu funcționează
|
||||||
|
- Credința nu = adevărul. Certitudinea despre ce nu va funcționa te oprește
|
||||||
|
|
||||||
|
### 3. STRATEGY (al treilea, cel mai puțin important)
|
||||||
|
- Strategia poate economisi 10 ani. Dar dacă story-ul nu o susține, nu o vei folosi
|
||||||
|
- Problema fitnesului nu e lipsa strategiei — există milioane de strategii, toate funcționează
|
||||||
|
- "Tyranny of how": fără bază de referință → nesiguranță → nu urmezi
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Insight cheie: Fasting vs Dieting
|
||||||
|
|
||||||
|
> "Fasting is absolute. You just don't do it. Dieting is a rule by attrition — you negotiate with yourself. Negotiation with yourself makes you weak."
|
||||||
|
|
||||||
|
Modelul aplicabil la orice schimbare: **absolutismul e mai sustenabil decât moderația** pentru că elimină decizia.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cele 6 Nevoi Umane
|
||||||
|
|
||||||
|
1. **Certitudine** — confort de bază (survival need)
|
||||||
|
2. **Incertitudine/Varietate** — fără ea, plictiseală
|
||||||
|
3. **Semnificație** — supravalorizată azi prin social media
|
||||||
|
4. **Conexiune și iubire** — nevoia cea mai profundă, dar oamenii o controlează și o distrug
|
||||||
|
5. **Creștere** — spiritual need. "Everything grows or dies."
|
||||||
|
6. **Contribuție** — ce face viața să aibă sens. Ceva dincolo de tine însuți
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "The right numbers to a vault in the wrong order — you don't get the treasure."
|
||||||
|
|
||||||
|
> "Your state controls your story. Your story and your state control whether you do anything."
|
||||||
|
|
||||||
|
> "You have to divorce your limitations and marry the truth of your unlimited ability."
|
||||||
|
|
||||||
|
> "The deepest fear everybody has — kings, Grammy winners, NFL champions — is that they're not enough."
|
||||||
|
|
||||||
|
> "If you get to that place of total resolve, fasting is easy. When you're dieting, you're negotiating. Negotiation with yourself makes you weak."
|
||||||
|
|
||||||
|
> "We need jackpots in our life to make us feel alive."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relevanță pentru Marius
|
||||||
|
|
||||||
|
Conceptul **State → Story → Strategy** e util când un proiect stagnează sau motivația cade. Înainte de "ce fac" (strategie), verifică: în ce stare mentală ești? Ce story îți spui despre ce e posibil? Absolultismul (decide complet, fără negociere) funcționează mai bine decât moderația pentru schimbări de comportament.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Google Just Dropped a Masterclass on Agentic Engineering
|
||||||
|
|
||||||
|
**URL:** https://youtu.be/zbmuiaPuiNM
|
||||||
|
**Data:** 2026-06-25
|
||||||
|
**Durata:** 21:55
|
||||||
|
**Tags:** @work @growth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Google a publicat un ghid de 51 de pagini despre AI-driven SDLC (Software Development Life Cycle). Concluzia centrală: **harness-ul (regulile, workflow-urile, tool-urile, guardrails) contează 90%, modelul LLM doar 10%.** Vibe coding e ok pentru prototipuri, dar agentic engineering — cu spec-uri clare, teste automate și un harness bine inginerit — este singura cale spre cod fiabil și cost-eficient pe termen lung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Puncte cheie
|
||||||
|
|
||||||
|
1. **AI coding e un spectru, nu un switch**
|
||||||
|
- Vibe coding → prompt casual, validare vizuală
|
||||||
|
- Structured AI assisted → prompts detaliate, spot-checking manual
|
||||||
|
- Agentic engineering → harness inginerit, evals automate, CI gates
|
||||||
|
|
||||||
|
2. **Harness = 90% din sistem**
|
||||||
|
- Instrucțiuni, MCP servers, guardrails, hooks, skills (workflows), sub-agenți, observabilitate
|
||||||
|
- Modelul LLM e doar 10% — poți face Sonnet să performeze ca Opus cu harness-ul potrivit
|
||||||
|
|
||||||
|
3. **Factory model**
|
||||||
|
- Tu proiectezi sistemul, agentul produce codul
|
||||||
|
- Planning agent separat de coding agent (evitare context rot + bias)
|
||||||
|
- Human review la final (cel puțin PR review)
|
||||||
|
|
||||||
|
4. **Static vs Dynamic context**
|
||||||
|
- Static = reguli de bază, system prompt — încărcat mereu (fiabil dar costisitor)
|
||||||
|
- Dynamic = skills, conventions per folder — încărcat on-demand (eficient dar riscul că agentul uită să le ceară)
|
||||||
|
- Trend: un singur agent generalist + skills specializate, nu zeci de sub-agenți specializați
|
||||||
|
|
||||||
|
5. **System evolution mindset**
|
||||||
|
- La fiecare problemă: nu doar fix-ul, ci și îmbunătățirea harness-ului
|
||||||
|
- Echivalentul `tasks/lessons.md` la scară industrială
|
||||||
|
|
||||||
|
6. **Token economics**
|
||||||
|
- Vibe coding: CAPEX mic, OPEX mare (arzi tokens pe cod slab)
|
||||||
|
- Agentic engineering: CAPEX mare inițial, OPEX mic — crossover-ul vine repede
|
||||||
|
- 3-10x mai fiabil și mai ieftin pe termen lung
|
||||||
|
|
||||||
|
7. **Conductor vs Orchestrator**
|
||||||
|
- Conductor = micro-management la nivel de fișier (modul vechi)
|
||||||
|
- Orchestrator = dai taskuri mari, revizuiești outcome-uri, agenți în paralel
|
||||||
|
- Google zice că mergi între cele două; autorul crede că cu harness bun rămâi mereu Orchestrator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quote-uri
|
||||||
|
|
||||||
|
> "The harness matters as much as the model." — Anthropic (citat în video)
|
||||||
|
|
||||||
|
> "The model is only 10%. Everything else — instructions, tools, context, guardrails, orchestration, observability — makes up the other 90%." — Google
|
||||||
|
|
||||||
|
> "Rather than embedding every piece of specialized knowledge into the agent system prompt, skills allow the agent to remain a lightweight generalist that flexes into specialist roles on demand through progressive disclosure."
|
||||||
|
|
||||||
|
> "Specification quality is the new bottleneck." — Google
|
||||||
|
|
||||||
|
> "Every single time you go through this process, you're making it more and more reliable. The harness is worth investing your time into."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relevanță pentru Echo / ROA
|
||||||
|
|
||||||
|
- Confirmă arhitectura Echo: personality/*.md (static) + skills gstack (dynamic) + lessons.md (evolution)
|
||||||
|
- Ralph workflow = factory model în practică: PRD → planning agent → coding agent → review
|
||||||
|
- `tasks/lessons.md` = system evolution mindset aplicat
|
||||||
|
- Validare că abordarea cu un singur agent + skills e corectă (nu zeci de sub-agenți)
|
||||||
@@ -49,6 +49,14 @@ VAD_WINDOW_BYTES = PACKET_BYTES * (VAD_WINDOW_MS // PACKET_MS)
|
|||||||
VAD_THRESHOLD = 0.5
|
VAD_THRESHOLD = 0.5
|
||||||
SILENCE_FLUSH_MS = 800
|
SILENCE_FLUSH_MS = 800
|
||||||
NO_SPEECH_DROP_THRESHOLD = 0.6
|
NO_SPEECH_DROP_THRESHOLD = 0.6
|
||||||
|
# Hallucination rejection (no re-decode). faster-whisper's default temperature
|
||||||
|
# is a fallback ladder [0.0..1.0]; on unclear audio it re-decodes the segment up
|
||||||
|
# to 6x, which is what produced the 16-24s outliers in voice_stt_log.jsonl
|
||||||
|
# against a >7s conversational-abort budget. We pin temperature=0.0 (no fallback)
|
||||||
|
# and instead REJECT low-quality segments using the avg_logprob / compression_ratio
|
||||||
|
# that faster-whisper already computes per segment — zero extra latency.
|
||||||
|
AVG_LOGPROB_DROP_THRESHOLD = -1.0 # drop seg if avg_logprob below this
|
||||||
|
COMPRESSION_RATIO_DROP_THRESHOLD = 2.4 # drop seg if gzip ratio above this (repetition/garbage)
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
LOGS_DIR = PROJECT_ROOT / "logs"
|
LOGS_DIR = PROJECT_ROOT / "logs"
|
||||||
@@ -83,19 +91,28 @@ _silero_lock = threading.Lock()
|
|||||||
|
|
||||||
|
|
||||||
def _get_whisper_model() -> Any:
|
def _get_whisper_model() -> Any:
|
||||||
"""Lazy-load faster-whisper ``small`` int8 with the spike-validated
|
"""Lazy-load faster-whisper int8 with the spike-validated ``cpu_threads=4``
|
||||||
``cpu_threads=4`` (see ``tasks/voice-bench-results.md``)."""
|
(see ``tasks/voice-bench-results.md``).
|
||||||
|
|
||||||
|
Model is configurable via ``voice.stt_model`` (default ``"small"``). It may be
|
||||||
|
a faster-whisper model name or a path to a local CT2 dir — e.g. the Romanian
|
||||||
|
Common-Voice finetune that halved WER and fixed number transcription in the
|
||||||
|
D1 spike (``tools/voice_stt_spike.py``). Custom paths still load with
|
||||||
|
``local_files_only=True`` since they live on disk."""
|
||||||
global _whisper_model
|
global _whisper_model
|
||||||
if _whisper_model is not None:
|
if _whisper_model is not None:
|
||||||
return _whisper_model
|
return _whisper_model
|
||||||
with _whisper_lock:
|
with _whisper_lock:
|
||||||
if _whisper_model is not None:
|
if _whisper_model is not None:
|
||||||
return _whisper_model
|
return _whisper_model
|
||||||
|
from src.config import Config
|
||||||
|
model_id = Config().get("voice.stt_model", "small") or "small"
|
||||||
from faster_whisper import WhisperModel
|
from faster_whisper import WhisperModel
|
||||||
_whisper_model = WhisperModel(
|
_whisper_model = WhisperModel(
|
||||||
"small", device="cpu", compute_type="int8", cpu_threads=4,
|
model_id, device="cpu", compute_type="int8", cpu_threads=4,
|
||||||
local_files_only=True,
|
local_files_only=True,
|
||||||
)
|
)
|
||||||
|
log.info("STT model loaded: %s", model_id)
|
||||||
return _whisper_model
|
return _whisper_model
|
||||||
|
|
||||||
|
|
||||||
@@ -145,6 +162,38 @@ def _pcm48_stereo_to_16_mono(pcm: bytes) -> np.ndarray:
|
|||||||
return np.ascontiguousarray(mono16, dtype=np.float32)
|
return np.ascontiguousarray(mono16, dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_segments(segments: Any) -> tuple[list[str], float]:
|
||||||
|
"""Keep transcribable segments, drop silence and hallucinations.
|
||||||
|
|
||||||
|
Pure + side-effect free (no model, no I/O) so the rejection thresholds are
|
||||||
|
unit-testable with fake segment objects. A segment is dropped when:
|
||||||
|
- ``no_speech_prob`` is high (silence/non-speech), OR
|
||||||
|
- ``avg_logprob`` is below ``AVG_LOGPROB_DROP_THRESHOLD`` (decoder unsure), OR
|
||||||
|
- ``compression_ratio`` exceeds ``COMPRESSION_RATIO_DROP_THRESHOLD`` (looped/garbage).
|
||||||
|
The avg_logprob/compression checks replace faster-whisper's temperature-fallback
|
||||||
|
re-decode (the source of the 16-24s latency outliers) with zero-cost rejection.
|
||||||
|
Returns ``(kept_text_parts, worst_no_speech_prob)``.
|
||||||
|
"""
|
||||||
|
text_parts: list[str] = []
|
||||||
|
worst_no_speech = 0.0
|
||||||
|
for seg in segments:
|
||||||
|
no_sp = float(getattr(seg, "no_speech_prob", 0.0) or 0.0)
|
||||||
|
if no_sp > worst_no_speech:
|
||||||
|
worst_no_speech = no_sp
|
||||||
|
if no_sp > NO_SPEECH_DROP_THRESHOLD:
|
||||||
|
continue
|
||||||
|
avg_lp = getattr(seg, "avg_logprob", None)
|
||||||
|
if avg_lp is not None and float(avg_lp) < AVG_LOGPROB_DROP_THRESHOLD:
|
||||||
|
continue
|
||||||
|
comp = getattr(seg, "compression_ratio", None)
|
||||||
|
if comp is not None and float(comp) > COMPRESSION_RATIO_DROP_THRESHOLD:
|
||||||
|
continue
|
||||||
|
seg_text = (getattr(seg, "text", "") or "").strip()
|
||||||
|
if seg_text:
|
||||||
|
text_parts.append(seg_text)
|
||||||
|
return text_parts, worst_no_speech
|
||||||
|
|
||||||
|
|
||||||
# ---------- VoiceSession ----------
|
# ---------- VoiceSession ----------
|
||||||
|
|
||||||
class VoiceSession:
|
class VoiceSession:
|
||||||
@@ -679,6 +728,7 @@ class EchoVoiceSink(AudioSink):
|
|||||||
model = _get_whisper_model()
|
model = _get_whisper_model()
|
||||||
segments, _info = model.transcribe(
|
segments, _info = model.transcribe(
|
||||||
mono16, language="ro", beam_size=5,
|
mono16, language="ro", beam_size=5,
|
||||||
|
temperature=0.0, # no fallback ladder — reject bad segments instead (see thresholds above)
|
||||||
initial_prompt=(
|
initial_prompt=(
|
||||||
"Conversatie in romana cu asistentul Eco (Echo Core). "
|
"Conversatie in romana cu asistentul Eco (Echo Core). "
|
||||||
"Marius i se adreseaza cu 'Salut, Eco', 'Eco' sau 'Echo Core' "
|
"Marius i se adreseaza cu 'Salut, Eco', 'Eco' sau 'Echo Core' "
|
||||||
@@ -689,20 +739,10 @@ class EchoVoiceSink(AudioSink):
|
|||||||
"F1, F2, F3, F4, F5. Exemple: vorbeste cu vocea M5, voce F3, "
|
"F1, F2, F3, F4, F5. Exemple: vorbeste cu vocea M5, voce F3, "
|
||||||
"treci pe vocea F1."
|
"treci pe vocea F1."
|
||||||
),
|
),
|
||||||
hotwords="Eco Echo Core Marius Bianca",
|
hotwords="Eco Echo Core Marius Bianca Bitcoin",
|
||||||
condition_on_previous_text=False,
|
condition_on_previous_text=False,
|
||||||
)
|
)
|
||||||
text_parts: list[str] = []
|
text_parts, worst_no_speech = _filter_segments(segments)
|
||||||
worst_no_speech = 0.0
|
|
||||||
for seg in segments:
|
|
||||||
no_sp = float(getattr(seg, "no_speech_prob", 0.0) or 0.0)
|
|
||||||
if no_sp > worst_no_speech:
|
|
||||||
worst_no_speech = no_sp
|
|
||||||
if no_sp > NO_SPEECH_DROP_THRESHOLD:
|
|
||||||
continue
|
|
||||||
seg_text = (getattr(seg, "text", "") or "").strip()
|
|
||||||
if seg_text:
|
|
||||||
text_parts.append(seg_text)
|
|
||||||
if not text_parts:
|
if not text_parts:
|
||||||
return
|
return
|
||||||
text = " ".join(text_parts).strip()
|
text = " ".join(text_parts).strip()
|
||||||
|
|||||||
@@ -51,3 +51,17 @@ Lecții capturate din corectările lui Marius. Citește acest fișier la începu
|
|||||||
**Greșeala:** Am editat index.json direct, cu o schemă diferită față de ce produce update_notes_index.py.
|
**Greșeala:** Am editat index.json direct, cu o schemă diferită față de ce produce update_notes_index.py.
|
||||||
**Regula:** Niciodată nu scriei manual în `memory/kb/index.json`. Fluxul corect: (1) creezi fișierul `.md` în `memory/kb/<categorie>/`, (2) rulezi `python3 tools/update_notes_index.py`. Dacă ai nevoie să salvezi o notiță din Facebook/video, folosești `scripts/transcribe_video.sh <URL> <lang> --save-kb` care face totul corect.
|
**Regula:** Niciodată nu scriei manual în `memory/kb/index.json`. Fluxul corect: (1) creezi fișierul `.md` în `memory/kb/<categorie>/`, (2) rulezi `python3 tools/update_notes_index.py`. Dacă ai nevoie să salvezi o notiță din Facebook/video, folosești `scripts/transcribe_video.sh <URL> <lang> --save-kb` care face totul corect.
|
||||||
**Când se aplică:** Orice salvare de notiță în KB (Facebook, YouTube, coaching, insights, orice). Dacă ești tentat să `json.dump` în index.json — stop, rulează scriptul.
|
**Când se aplică:** Orice salvare de notiță în KB (Facebook, YouTube, coaching, insights, orice). Dacă ești tentat să `json.dump` în index.json — stop, rulează scriptul.
|
||||||
|
|
||||||
|
## Verifică că modelul/tool-ul numit chiar are capabilitatea ÎNAINTE de a planifica în jurul lui
|
||||||
|
**Data:** 2026-06-27
|
||||||
|
**Context:** Marius a cerut să folosesc `gemma4:31b-cloud` (Ollama) pentru decodare audio ca alternativă la Whisper. Am verificat pe pagina oficială Ollama: variantele cloud (31b) suportă doar Text+Image — audio există DOAR pe E2B/E4B (edge, local), iar acela e stricat de o regresie upstream deschisă (issue #16584). Premisa cererii era infezabilă.
|
||||||
|
**Greșeala (evitată):** Dacă planificam direct integrarea fără să verific pagina modelului, scriam cod de cablare Ollama audio care n-ar fi funcționat niciodată. Search-ul generic spunea „Gemma 4 are audio" — adevărat la nivel de familie, fals pentru modelul cloud specific cerut.
|
||||||
|
**Regula:** Când userul numește un model/serviciu specific pentru o capabilitate (audio, vision, tool-use, context lung), verifică pagina/docs ACELUI model exact înainte de a planifica. Capabilitățile diferă per variantă (cloud vs edge, sizes). Fetch pagina oficială, nu te baza pe search agregat la nivel de familie.
|
||||||
|
**Când se aplică:** Orice task care pornește de la „folosește modelul X pentru Y". Confirmă X→Y pe sursa primară înainte de plan mode.
|
||||||
|
|
||||||
|
## Corecția post-STT de text e cosmetică dacă consumatorul e un LLM — fixează la sursă (model), nu cu dicționar
|
||||||
|
**Data:** 2026-06-27
|
||||||
|
**Context:** Plan inițial pentru curățarea transcrierii Whisper avea 4 piese, inclusiv dicționar de restaurare diacritice + canonicalizare wake-word. Două review-uri independente (/autoplan CEO+Eng) au arătat: textul transcris merge la Claude, care citește română fără diacritice perfect; NU există wake-word gate în cod (`on_segment_done` dispatch necondiționat); singurul consumator precis (`detect_voice_change`) e deja fuzz-hardenat. Un spike a confirmat că modelul RO-finetuned (`mikr/whisper-small-ro-cv11`) înjumătățește WER (24%→10%) și fixează numerele la SURSĂ, +0.33s latență.
|
||||||
|
**Greșeala (evitată):** Construirea unui strat de corecție hand-curat (întreținere perpetuă, risc de regresie pe cuvinte ambigue) când fix-ul real era un model finetuned cu același cost de inferență.
|
||||||
|
**Regula:** Înainte de a peticit output-ul unui model ML cu post-procesare rule-based, întreabă: (1) cine e CONSUMATORUL textului? (un LLM tolerează erori; un parser regex nu); (2) există un model finetuned care fixează la sursă cu același cost? Spike-uiește modelul ÎNAINTE de a scrie straturi de corecție. Verifică unde merge output-ul prin cod, nu presupune un gate care „pare" că există.
|
||||||
|
**Când se aplică:** Orice îmbunătățire de calitate STT/OCR/ML output. Tool de spike: `tools/voice_stt_spike.py`.
|
||||||
|
|||||||
61
tasks/voice-stt-quality.md
Normal file
61
tasks/voice-stt-quality.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Voice STT Quality — îmbunătățiri Whisper
|
||||||
|
|
||||||
|
Branch: `voice/stt-quality`. Origine: cererea de a folosi Gemma 4 cloud pentru audio
|
||||||
|
(infezabil — `gemma4:31b-cloud` n-are audio, E4B e stricat upstream, fără host de deploy).
|
||||||
|
Pivot la îmbunătățirea Whisper, validat prin `/autoplan` (CEO + Eng review).
|
||||||
|
|
||||||
|
## Ce s-a livrat
|
||||||
|
|
||||||
|
### 1. Gate rejection halucinații (cost zero latență) — `src/voice/pipeline.py`
|
||||||
|
- `model.transcribe(..., temperature=0.0)` — **dezactivează scara de fallback** a faster-whisper.
|
||||||
|
Codul vechi nu pasa `temperature`, deci folosea implicit `[0.0..1.0]` (6 pași) care re-decoda
|
||||||
|
segmentul pe audio prost → exact sursa latențelor de 24.4s / 16.7s din `voice_stt_log.jsonl`.
|
||||||
|
- `_filter_segments()` — funcție pură nouă care dropează segmentele cu `no_speech_prob` mare,
|
||||||
|
`avg_logprob < -1.0` (decoder nesigur) sau `compression_ratio > 2.4` (buclă/gunoi). Zero
|
||||||
|
re-decodare. Prinde „Care pune o zana judiciul tugea" / „Acest lucru a fost foarte mult".
|
||||||
|
- `hotwords += Bitcoin`. `initial_prompt` neatins (evită taxa de latență pe fiecare enunț).
|
||||||
|
- Teste: `tests/test_voice_pipeline_filter.py` (8 cazuri).
|
||||||
|
|
||||||
|
### 2. Unealtă de mining — `tools/voice_stt_mine.py`
|
||||||
|
- CLI read-only peste `voice_stt_log.jsonl`: frecvențe token, tokeni rari (candidați
|
||||||
|
hotwords/corecții), candidați diacritice lipsă, rânduri suspecte de halucinație.
|
||||||
|
- Tolerează rânduri fără `text_corrected` (citește `text`). Teste: `tests/test_voice_stt_mine.py` (13).
|
||||||
|
|
||||||
|
### 3. Spike model RO-finetuned (D1) — `tools/voice_stt_spike.py`
|
||||||
|
Compară modele faster-whisper pe audio RO sintetizat (Supertonic) cu ground-truth diacritizat.
|
||||||
|
|
||||||
|
**Rezultat (threads=4, beam=5):**
|
||||||
|
|
||||||
|
| Model | p50 | p95 | WER | Diacritice |
|
||||||
|
|-------|-----|-----|-----|-----------|
|
||||||
|
| `small` (baseline) | 2.59s | 3.04s | 24.2% | 12/20 |
|
||||||
|
| **`mikr/whisper-small-ro-cv11`** (CT2 int8) | 2.92s | 3.25s | **10.5%** | **17/20** |
|
||||||
|
|
||||||
|
- WER se înjumătățește; diacritice 60%→85%; numere PERFECTE (baseline: „120 si 3 delei"
|
||||||
|
→ finetuned: „o sută douăzeci și trei de lei"). Cost: +0.33s p50 (în bugetul 1.5-3s).
|
||||||
|
- Modelul CT2: `~/.cache/echo-ct2/whisper-small-ro-cv11-int8` (234M int8).
|
||||||
|
|
||||||
|
### 4. Model STT configurabil — `src/voice/pipeline.py::_get_whisper_model`
|
||||||
|
- Citește `voice.stt_model` din config (default `"small"`). Adopția finetuned = flip config,
|
||||||
|
nu cod. **Default rămâne `small`** până la decizia de adopție.
|
||||||
|
|
||||||
|
## Cum adopți modelul finetuned (când decizi)
|
||||||
|
```bash
|
||||||
|
# config.json → "voice": { ..., "stt_model": "/home/moltbot/.cache/echo-ct2/whisper-small-ro-cv11-int8" }
|
||||||
|
systemctl --user restart echo-core # reload model
|
||||||
|
```
|
||||||
|
Re-rulează spike-ul oricând: `python3 tools/voice_stt_spike.py --models "small,<path>" --threads 4`
|
||||||
|
|
||||||
|
## Decizii autoplan respinse (din review)
|
||||||
|
- ❌ `temperature=[0.0..0.6]` fallback → regresie latență pe worst-case. Înlocuit cu rejection.
|
||||||
|
- ❌ `canonicalize_wakeword` → **nu există wake gate** în cod (verificat); ar fi spart `detect_voice_change`.
|
||||||
|
- ❌ Dicționar diacritice pe calea Claude → Claude citește română stâlcită OK; finetuned-ul rezolvă la sursă.
|
||||||
|
- ⏸ `correct_vocab` / `src/voice/stt_correct.py` → deferate (21 mostre = anecdotă; mining adună întâi date).
|
||||||
|
|
||||||
|
## Note de mediu
|
||||||
|
- `transformers 5.12.1` instalat în `.venv` pentru conversia CT2 (one-time). A downgradat
|
||||||
|
`tokenizers` 0.23.1→0.22.2 (faster-whisper încă OK, pin `<1` respectat). Se poate `pip uninstall
|
||||||
|
transformers` dacă nu mai e nevoie de conversii.
|
||||||
|
- **Pre-existent, neatins de mine:** `tools/tts.py` modificat necommis sparge 2 teste din
|
||||||
|
`test_voice_normalize.py` (truncare 200 cuvinte). Confirmat: cu `tts.py` committed, testele trec.
|
||||||
|
```
|
||||||
85
tests/test_voice_pipeline_filter.py
Normal file
85
tests/test_voice_pipeline_filter.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
"""Tests for src/voice/pipeline.py::_filter_segments — STT hallucination gate.
|
||||||
|
|
||||||
|
The gate replaces faster-whisper's temperature-fallback re-decode (the source of
|
||||||
|
16-24s latency outliers) with zero-cost segment rejection on no_speech_prob,
|
||||||
|
avg_logprob, and compression_ratio.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
|
||||||
|
from src.voice.pipeline import ( # noqa: E402
|
||||||
|
AVG_LOGPROB_DROP_THRESHOLD,
|
||||||
|
COMPRESSION_RATIO_DROP_THRESHOLD,
|
||||||
|
NO_SPEECH_DROP_THRESHOLD,
|
||||||
|
_filter_segments,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FakeSeg:
|
||||||
|
text: str = ""
|
||||||
|
no_speech_prob: float = 0.0
|
||||||
|
avg_logprob: Optional[float] = 0.0
|
||||||
|
compression_ratio: Optional[float] = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_keeps_clean_segment():
|
||||||
|
parts, worst = _filter_segments([FakeSeg(text="salut eco", avg_logprob=-0.3, compression_ratio=1.5)])
|
||||||
|
assert parts == ["salut eco"]
|
||||||
|
assert worst == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_drops_high_no_speech():
|
||||||
|
seg = FakeSeg(text="hmm", no_speech_prob=NO_SPEECH_DROP_THRESHOLD + 0.1)
|
||||||
|
parts, worst = _filter_segments([seg])
|
||||||
|
assert parts == []
|
||||||
|
assert worst == NO_SPEECH_DROP_THRESHOLD + 0.1 # still tracked for logging
|
||||||
|
|
||||||
|
|
||||||
|
def test_drops_low_avg_logprob_hallucination():
|
||||||
|
# "Care pune o zana judiciul tugea" style: decoder unsure
|
||||||
|
seg = FakeSeg(text="zana judiciul tugea", avg_logprob=AVG_LOGPROB_DROP_THRESHOLD - 0.5)
|
||||||
|
parts, _ = _filter_segments([seg])
|
||||||
|
assert parts == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_drops_high_compression_ratio_loop():
|
||||||
|
seg = FakeSeg(text="da da da da da", compression_ratio=COMPRESSION_RATIO_DROP_THRESHOLD + 1.0)
|
||||||
|
parts, _ = _filter_segments([seg])
|
||||||
|
assert parts == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_keeps_when_metrics_missing():
|
||||||
|
# Older/edge segments may not expose avg_logprob/compression_ratio
|
||||||
|
seg = FakeSeg(text="ok", avg_logprob=None, compression_ratio=None)
|
||||||
|
parts, _ = _filter_segments([seg])
|
||||||
|
assert parts == ["ok"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_drops_empty_text():
|
||||||
|
parts, _ = _filter_segments([FakeSeg(text=" ", avg_logprob=-0.2)])
|
||||||
|
assert parts == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_worst_no_speech_is_max_across_segments():
|
||||||
|
segs = [
|
||||||
|
FakeSeg(text="a", no_speech_prob=0.1, avg_logprob=-0.2),
|
||||||
|
FakeSeg(text="b", no_speech_prob=0.4, avg_logprob=-0.2),
|
||||||
|
]
|
||||||
|
parts, worst = _filter_segments(segs)
|
||||||
|
assert parts == ["a", "b"]
|
||||||
|
assert worst == 0.4
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_keep_and_drop():
|
||||||
|
segs = [
|
||||||
|
FakeSeg(text="bun venit", avg_logprob=-0.3),
|
||||||
|
FakeSeg(text="garbage", avg_logprob=-3.0), # dropped: low logprob
|
||||||
|
FakeSeg(text="la revedere", avg_logprob=-0.5),
|
||||||
|
]
|
||||||
|
parts, _ = _filter_segments(segs)
|
||||||
|
assert parts == ["bun venit", "la revedere"]
|
||||||
100
tests/test_voice_stt_mine.py
Normal file
100
tests/test_voice_stt_mine.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Tests for tools/voice_stt_mine.py — STT log mining helpers.
|
||||||
|
|
||||||
|
Pure-function coverage: tokenize, token_frequency, rare_tokens,
|
||||||
|
missing_diacritic_candidates, suspect_rows, row_text (back-compat with rows
|
||||||
|
that predate the text_corrected field).
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||||
|
|
||||||
|
from tools.voice_stt_mine import ( # noqa: E402
|
||||||
|
missing_diacritic_candidates,
|
||||||
|
rare_tokens,
|
||||||
|
row_text,
|
||||||
|
suspect_rows,
|
||||||
|
token_frequency,
|
||||||
|
tokenize,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenize_lowercases_and_drops_punct():
|
||||||
|
assert tokenize("Salut, Eco!") == ["salut", "eco"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenize_keeps_diacritics():
|
||||||
|
assert tokenize("ședință și prețul") == ["ședință", "și", "prețul"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenize_drops_digits():
|
||||||
|
# M3, numbers etc. are not alphabetic word tokens
|
||||||
|
assert tokenize("M3 are 120 lei") == ["m", "are", "lei"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tokenize_empty_and_none():
|
||||||
|
assert tokenize("") == []
|
||||||
|
assert tokenize(None) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_text_prefers_raw_text_field():
|
||||||
|
# Mining always wants raw STT output (the `text` field), even once
|
||||||
|
# newer rows add `text_corrected`.
|
||||||
|
assert row_text({"text": "cat", "text_corrected": "cât"}) == "cat"
|
||||||
|
|
||||||
|
|
||||||
|
def test_row_text_missing_field():
|
||||||
|
assert row_text({}) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_frequency_counts_across_rows():
|
||||||
|
rows = [{"text": "eco eco"}, {"text": "Eco salut"}]
|
||||||
|
freq = token_frequency(rows)
|
||||||
|
assert freq["eco"] == 3
|
||||||
|
assert freq["salut"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_rare_tokens_returns_singletons_sorted():
|
||||||
|
rows = [{"text": "eco eco salut bitcoin"}]
|
||||||
|
rare = rare_tokens(token_frequency(rows))
|
||||||
|
assert rare == ["bitcoin", "salut"] # eco appears twice -> excluded
|
||||||
|
assert "eco" not in rare
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_diacritic_candidates_flags_ascii_words():
|
||||||
|
rows = [{"text": "pretul este mare"}, {"text": "ședință corectă"}]
|
||||||
|
cands = missing_diacritic_candidates(token_frequency(rows), min_len=4)
|
||||||
|
assert "pretul" in cands
|
||||||
|
assert "mare" in cands
|
||||||
|
# words carrying diacritics are NOT restore candidates
|
||||||
|
assert "ședință" not in cands
|
||||||
|
assert "corectă" not in cands
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_diacritic_respects_min_len():
|
||||||
|
rows = [{"text": "cat de bun"}]
|
||||||
|
cands = missing_diacritic_candidates(token_frequency(rows), min_len=4)
|
||||||
|
assert "cat" not in cands # len 3 < 4
|
||||||
|
assert "bun" not in cands
|
||||||
|
|
||||||
|
|
||||||
|
def test_suspect_rows_flags_high_latency():
|
||||||
|
rows = [
|
||||||
|
{"text": "ok", "stt_latency_s": 2.0, "no_speech_prob": 0.0},
|
||||||
|
{"text": "M3.", "stt_latency_s": 24.4, "no_speech_prob": 0.58},
|
||||||
|
]
|
||||||
|
suspects = suspect_rows(rows)
|
||||||
|
assert len(suspects) == 1
|
||||||
|
assert suspects[0]["text"] == "M3."
|
||||||
|
|
||||||
|
|
||||||
|
def test_suspect_rows_flags_borderline_no_speech():
|
||||||
|
rows = [{"text": "x", "stt_latency_s": 1.0, "no_speech_prob": 0.55}]
|
||||||
|
assert len(suspect_rows(rows)) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_suspect_rows_tolerates_missing_fields():
|
||||||
|
# rows without latency/no_speech must not crash
|
||||||
|
assert suspect_rows([{"text": "x"}]) == []
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"D100": "27cf97a4d10c8529669d95b2d96ca3c9b41f7e4e50091dce19cf8af117f0ac4a",
|
"D100": "ce27f72bc3fd5e3241480fcda3a3e14572cfbed1b26e43896037bb265d82e4f5",
|
||||||
"D101": "f72fc1c29657ea11e0238806a28f6abccf5b00e45904e1e0c9385cc64491fcaf",
|
"D101": "f72fc1c29657ea11e0238806a28f6abccf5b00e45904e1e0c9385cc64491fcaf",
|
||||||
"D300": "cb7b55b568ab893024884971eac0367fb6fe487c297e355d64258dae437f6ddd",
|
"D300": "cb7b55b568ab893024884971eac0367fb6fe487c297e355d64258dae437f6ddd",
|
||||||
"D394": "c4c4e62bda30032f12c17edf9a5087b6173a350ccb1fd750158978b3bd0acb7d",
|
"D394": "c4c4e62bda30032f12c17edf9a5087b6173a350ccb1fd750158978b3bd0acb7d",
|
||||||
"D406": "ca6103448d663ab16fcaef0f29f8933ef526cbf5aad12c7ff5dbd61b22ca9fc6",
|
"D406": "ca6103448d663ab16fcaef0f29f8933ef526cbf5aad12c7ff5dbd61b22ca9fc6",
|
||||||
"SIT_FIN_SEM_2025": "8164843431e6b703a38fbdedc7898ec6ae83559fe10f88663ba0b55f3091d5fe",
|
"SIT_FIN_SEM_2025": "8164843431e6b703a38fbdedc7898ec6ae83559fe10f88663ba0b55f3091d5fe",
|
||||||
"SIT_FIN_AN_2025": "accceef5b6585a3e901d83d23fc2e60f6562eac4a2ce00f943856232bed929d6",
|
"SIT_FIN_AN_2025": "accceef5b6585a3e901d83d23fc2e60f6562eac4a2ce00f943856232bed929d6",
|
||||||
"DESCARCARE_DECLARATII": "8cc082021edb0ae97686d73f8179369be33a68ef03ec791757460bb7fff99e34",
|
"DESCARCARE_DECLARATII": "b2a9534d4f64b828abdb97459b92be27ba26a0d9ba1a0f947ef4a37c968ef293",
|
||||||
"D205": "d3c20a7ae70f4c18bbb7add42af035e3746d323b2e6df37a4e31ed625ddb86d9",
|
"D205": "d3c20a7ae70f4c18bbb7add42af035e3746d323b2e6df37a4e31ed625ddb86d9",
|
||||||
"D390": "4726938ed5858ec735caefd947a7d182b6dc64009478332c4feabdb36412a84e",
|
"D390": "4726938ed5858ec735caefd947a7d182b6dc64009478332c4feabdb36412a84e",
|
||||||
"BILANT_2024": "fbb8d66c2e530d8798362992c6983e07e1250188228c758cb6da4cde4f955950",
|
"BILANT_2024": "fbb8d66c2e530d8798362992c6983e07e1250188228c758cb6da4cde4f955950",
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ valabil începand cu
|
|||||||
01/2024 - publicat în data de 09.02.2024
|
01/2024 - publicat în data de 09.02.2024
|
||||||
soft A
|
soft A
|
||||||
actualizat în data de
|
actualizat în data de
|
||||||
29.05.2026
|
17.06.2026
|
||||||
soft J*
|
soft J*
|
||||||
actualizat în data de
|
actualizat în data de
|
||||||
25.05.2026
|
25.05.2026
|
||||||
Anexa
|
Anexa
|
||||||
validări
|
validări
|
||||||
actualizat în data de
|
actualizat în data de
|
||||||
20.05.2026
|
17.06.2026
|
||||||
Schema
|
Schema
|
||||||
XSD
|
XSD
|
||||||
100
|
100
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Se transmit prin SPV
|
|||||||
F1129
|
F1129
|
||||||
- Ordinul de plată multiplu electronic (OPME) V.2.0.45 dată
|
- Ordinul de plată multiplu electronic (OPME) V.2.0.45 dată
|
||||||
actualizare
|
actualizare
|
||||||
25.11.2025
|
22.06.2026
|
||||||
Formularul se depune on-line prin Sistemul naţional de raportare
|
Formularul se depune on-line prin Sistemul naţional de raportare
|
||||||
FOREXEBUG
|
FOREXEBUG
|
||||||
de către instituţiile publice şi, respectiv, prin portalul
|
de către instituţiile publice şi, respectiv, prin portalul
|
||||||
@@ -273,6 +273,10 @@ D182
|
|||||||
,
|
,
|
||||||
408
|
408
|
||||||
,
|
,
|
||||||
|
409
|
||||||
|
,
|
||||||
|
410
|
||||||
|
,
|
||||||
700
|
700
|
||||||
,
|
,
|
||||||
710,
|
710,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"D100": {
|
"D100": {
|
||||||
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_290526.pdf",
|
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_170626.pdf",
|
||||||
"soft_a_date": "29.05.2026",
|
"soft_a_date": "17.06.2026",
|
||||||
"soft_j_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_22052026.zip",
|
"soft_j_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_22052026.zip",
|
||||||
"soft_j_date": "22.05.2026"
|
"soft_j_date": "22.05.2026"
|
||||||
},
|
},
|
||||||
|
|||||||
175
tools/ocr_bon.py
Normal file
175
tools/ocr_bon.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Extrage date din bon fiscal / factură / extras de cont via Ollama vision LLM.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tools/ocr_bon.py <pdf_sau_imagine> [--model minicpm-v] [--host http://10.0.20.161:11434]
|
||||||
|
|
||||||
|
Modele recomandate (trage cu: ollama pull <model>):
|
||||||
|
minicpm-v ~5GB, rapid, excelent pentru documente
|
||||||
|
llava:7b ~4GB, clasic, bun pe bonuri
|
||||||
|
llama3.2-vision ~8GB, cel mai precis
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import argparse
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import fitz # pymupdf
|
||||||
|
|
||||||
|
OLLAMA_HOST = "http://10.0.20.161:11434"
|
||||||
|
DEFAULT_MODEL = "minicpm-v"
|
||||||
|
|
||||||
|
PROMPT = """Ești un sistem OCR specializat pe documente financiare românești.
|
||||||
|
Extrage TOATE datele vizibile și returnează EXCLUSIV un JSON valid, fără text în afara JSON-ului.
|
||||||
|
|
||||||
|
Schema JSON:
|
||||||
|
{
|
||||||
|
"document_type": "bon_fiscal|factura|extras_cont|necunoscut",
|
||||||
|
"vendor": {
|
||||||
|
"name": "...",
|
||||||
|
"cui": "...",
|
||||||
|
"address": "..."
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"name": "...",
|
||||||
|
"cui": "..."
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"numar": "...",
|
||||||
|
"serie": "...",
|
||||||
|
"data": "YYYY-MM-DD",
|
||||||
|
"ora": "HH:MM:SS"
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"descriere": "...",
|
||||||
|
"cantitate": 0.0,
|
||||||
|
"unitate": "buc|l|kg|...",
|
||||||
|
"pret_unitar": 0.0,
|
||||||
|
"valoare": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tva": [
|
||||||
|
{
|
||||||
|
"cota_litera": "A|B|C",
|
||||||
|
"procent": 21.0,
|
||||||
|
"baza": 0.0,
|
||||||
|
"valoare_tva": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_fara_tva": 0.0,
|
||||||
|
"total_tva": 0.0,
|
||||||
|
"total": 0.0,
|
||||||
|
"plata": {
|
||||||
|
"metoda": "card|numerar|transfer",
|
||||||
|
"tip_card": "...",
|
||||||
|
"pan_masked": "...",
|
||||||
|
"suma": 0.0
|
||||||
|
},
|
||||||
|
"note": "orice info suplimentar relevant"
|
||||||
|
}
|
||||||
|
|
||||||
|
Omite câmpurile care nu există în document. Returnează DOAR JSON."""
|
||||||
|
|
||||||
|
|
||||||
|
def pdf_to_images_b64(pdf_path: Path, dpi: int = 150) -> list[str]:
|
||||||
|
"""Convertește fiecare pagină PDF la PNG base64."""
|
||||||
|
doc = fitz.open(str(pdf_path))
|
||||||
|
images = []
|
||||||
|
mat = fitz.Matrix(dpi / 72, dpi / 72)
|
||||||
|
for page in doc:
|
||||||
|
pix = page.get_pixmap(matrix=mat)
|
||||||
|
images.append(base64.b64encode(pix.tobytes("png")).decode())
|
||||||
|
doc.close()
|
||||||
|
return images
|
||||||
|
|
||||||
|
|
||||||
|
def image_to_b64(img_path: Path) -> str:
|
||||||
|
"""Citește imagine și returnează base64."""
|
||||||
|
return base64.b64encode(img_path.read_bytes()).decode()
|
||||||
|
|
||||||
|
|
||||||
|
def ask_ollama(model: str, images_b64: list[str], host: str) -> str:
|
||||||
|
"""Trimite imaginile la Ollama și returnează răspunsul."""
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"prompt": PROMPT,
|
||||||
|
"images": images_b64,
|
||||||
|
"stream": False,
|
||||||
|
"options": {
|
||||||
|
"temperature": 0.1,
|
||||||
|
"num_predict": 2048,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with httpx.Client(timeout=600) as client:
|
||||||
|
r = client.post(f"{host}/api/generate", json=payload)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()["response"]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_json(text: str) -> dict:
|
||||||
|
"""Extrage JSON din răspuns (poate fi înconjurat de markdown)."""
|
||||||
|
text = text.strip()
|
||||||
|
# Strip markdown code blocks
|
||||||
|
if "```" in text:
|
||||||
|
start = text.find("{", text.find("```"))
|
||||||
|
end = text.rfind("}") + 1
|
||||||
|
text = text[start:end]
|
||||||
|
return json.loads(text)
|
||||||
|
|
||||||
|
|
||||||
|
def process(file_path: Path, model: str, host: str) -> dict:
|
||||||
|
suffix = file_path.suffix.lower()
|
||||||
|
|
||||||
|
if suffix == ".pdf":
|
||||||
|
print(f" Conversie PDF → imagini...", file=sys.stderr)
|
||||||
|
images = pdf_to_images_b64(file_path)
|
||||||
|
print(f" {len(images)} pagini extrase", file=sys.stderr)
|
||||||
|
elif suffix in (".jpg", ".jpeg", ".png", ".webp"):
|
||||||
|
images = [image_to_b64(file_path)]
|
||||||
|
else:
|
||||||
|
# Încearcă să detecteze tipul din conținut
|
||||||
|
header = file_path.read_bytes()[:8]
|
||||||
|
if header[:4] == b'%PDF':
|
||||||
|
images = pdf_to_images_b64(file_path)
|
||||||
|
else:
|
||||||
|
images = [image_to_b64(file_path)]
|
||||||
|
|
||||||
|
print(f" Trimit la Ollama ({model})...", file=sys.stderr)
|
||||||
|
raw = ask_ollama(model, images, host)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return extract_json(raw)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f" Răspuns brut (nu e JSON valid):\n{raw}", file=sys.stderr)
|
||||||
|
return {"error": "json_parse_failed", "raw": raw}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="OCR bon fiscal via Ollama vision")
|
||||||
|
parser.add_argument("file", help="PDF sau imagine (jpg/png)")
|
||||||
|
parser.add_argument("--model", default=DEFAULT_MODEL, help=f"Model Ollama (default: {DEFAULT_MODEL})")
|
||||||
|
parser.add_argument("--host", default=OLLAMA_HOST, help=f"Ollama host (default: {OLLAMA_HOST})")
|
||||||
|
parser.add_argument("--pretty", action="store_true", help="JSON indentat")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
file_path = Path(args.file)
|
||||||
|
if not file_path.exists():
|
||||||
|
print(f"Fișierul nu există: {file_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"Procesez: {file_path.name}", file=sys.stderr)
|
||||||
|
result = process(file_path, args.model, args.host)
|
||||||
|
|
||||||
|
indent = 2 if args.pretty else None
|
||||||
|
print(json.dumps(result, ensure_ascii=False, indent=indent))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
18
tools/tts.py
18
tools/tts.py
@@ -35,10 +35,26 @@ _TTS_PUNCT_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Supertonic ONNX model hard limit: inputs longer than this trigger
|
||||||
|
# Mul node dimension mismatches in attention layers.
|
||||||
|
_MAX_TTS_CHARS = 400
|
||||||
|
|
||||||
|
|
||||||
def sanitize_for_supertonic(text: str) -> str:
|
def sanitize_for_supertonic(text: str) -> str:
|
||||||
"""Replace Unicode punctuation Supertonic rejects with ASCII equivalents."""
|
"""Replace Unicode punctuation and strip chars that crash Supertonic's ONNX model."""
|
||||||
for src, dst in _TTS_PUNCT_MAP.items():
|
for src, dst in _TTS_PUNCT_MAP.items():
|
||||||
text = text.replace(src, dst)
|
text = text.replace(src, dst)
|
||||||
|
# Strip emoji and high-codepoint chars (keep ASCII printable + Latin/Romanian diacritice)
|
||||||
|
cleaned = []
|
||||||
|
for ch in text:
|
||||||
|
cp = ord(ch)
|
||||||
|
if (32 <= cp <= 126) or (128 <= cp <= 591):
|
||||||
|
cleaned.append(ch)
|
||||||
|
else:
|
||||||
|
cleaned.append(' ')
|
||||||
|
text = ' '.join(''.join(cleaned).split())
|
||||||
|
if len(text) > _MAX_TTS_CHARS:
|
||||||
|
text = text[:_MAX_TTS_CHARS]
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
166
tools/voice_stt_mine.py
Normal file
166
tools/voice_stt_mine.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Mine logs/voice_stt_log.jsonl for STT correction candidates.
|
||||||
|
|
||||||
|
Read-only analysis tool. Surfaces what the always-on STT log has captured so
|
||||||
|
Marius can decide hotwords, spot recurring mistranscriptions, and judge whether
|
||||||
|
a model swap (e.g. a Romanian-finetuned Whisper) actually helps.
|
||||||
|
|
||||||
|
Pure helpers (tokenize / aggregate) are importable and tested; the CLI just
|
||||||
|
prints reports. Tolerates rows written before the `text_corrected` field
|
||||||
|
existed (falls back to `text`).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 tools/voice_stt_mine.py # full report
|
||||||
|
python3 tools/voice_stt_mine.py --tokens # token frequency only
|
||||||
|
python3 tools/voice_stt_mine.py --rare # one-off tokens (candidates)
|
||||||
|
python3 tools/voice_stt_mine.py --suspect # likely hallucination rows
|
||||||
|
python3 tools/voice_stt_mine.py --log PATH # custom log path
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable, Iterator
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
DEFAULT_LOG = PROJECT_ROOT / "logs" / "voice_stt_log.jsonl"
|
||||||
|
|
||||||
|
# Latency above this (s) almost always means the decoder thrashed on unclear
|
||||||
|
# audio — a strong hallucination signal worth reviewing. Mirrors the >7s
|
||||||
|
# conversational-abort budget from tasks/voice-bench-results.md.
|
||||||
|
SUSPECT_LATENCY_S = 7.0
|
||||||
|
SUSPECT_NO_SPEECH = 0.5
|
||||||
|
|
||||||
|
_TOKEN_RE = re.compile(r"[A-Za-zĂÂÎȘȚăâîșț]+", re.UNICODE)
|
||||||
|
# Romanian diacritic letters; a token with none of these is a diacritic-restore
|
||||||
|
# candidate worth a human glance (not auto-corrected — see plan D2).
|
||||||
|
_DIACRITICS = set("ĂÂÎȘȚăâîșț")
|
||||||
|
|
||||||
|
|
||||||
|
def read_log(path: Path) -> list[dict]:
|
||||||
|
"""Parse the JSONL log; skip malformed lines instead of crashing."""
|
||||||
|
rows: list[dict] = []
|
||||||
|
if not path.exists():
|
||||||
|
return rows
|
||||||
|
with path.open(encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
rows.append(json.loads(line))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def row_text(row: dict) -> str:
|
||||||
|
"""Raw transcript for a row. New rows may add `text_corrected`; mining
|
||||||
|
always wants the raw STT output, which lives in `text`."""
|
||||||
|
return (row.get("text") or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def tokenize(text: str) -> list[str]:
|
||||||
|
"""Split into alphabetic word tokens, lowercased. Drops digits/punct."""
|
||||||
|
return [t.lower() for t in _TOKEN_RE.findall(text or "")]
|
||||||
|
|
||||||
|
|
||||||
|
def token_frequency(rows: Iterable[dict]) -> Counter:
|
||||||
|
counter: Counter = Counter()
|
||||||
|
for row in rows:
|
||||||
|
counter.update(tokenize(row_text(row)))
|
||||||
|
return counter
|
||||||
|
|
||||||
|
|
||||||
|
def rare_tokens(freq: Counter, max_count: int = 1) -> list[str]:
|
||||||
|
"""Tokens seen at most `max_count` times — candidate mistranscriptions,
|
||||||
|
proper nouns to add as hotwords, or code-switch garbage."""
|
||||||
|
return sorted(t for t, c in freq.items() if c <= max_count)
|
||||||
|
|
||||||
|
|
||||||
|
def missing_diacritic_candidates(freq: Counter, min_len: int = 4) -> list[str]:
|
||||||
|
"""All-ASCII tokens (no Romanian diacritics) of reasonable length, sorted by
|
||||||
|
frequency. These are the words a diacritic-restore pass would target — kept
|
||||||
|
as a review list only (v1 does not auto-restore, per plan D2)."""
|
||||||
|
out = [
|
||||||
|
(t, c) for t, c in freq.items()
|
||||||
|
if len(t) >= min_len and not (set(t) & _DIACRITICS) and t.isalpha()
|
||||||
|
]
|
||||||
|
out.sort(key=lambda tc: (-tc[1], tc[0]))
|
||||||
|
return [t for t, _ in out]
|
||||||
|
|
||||||
|
|
||||||
|
def suspect_rows(rows: Iterable[dict]) -> list[dict]:
|
||||||
|
"""Rows that look like hallucinations: very high latency or borderline
|
||||||
|
no_speech_prob that still produced text."""
|
||||||
|
out = []
|
||||||
|
for row in rows:
|
||||||
|
lat = float(row.get("stt_latency_s") or 0.0)
|
||||||
|
nsp = float(row.get("no_speech_prob") or 0.0)
|
||||||
|
if lat >= SUSPECT_LATENCY_S or nsp >= SUSPECT_NO_SPEECH:
|
||||||
|
out.append(row)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_report(rows: list[dict]) -> Iterator[str]:
|
||||||
|
freq = token_frequency(rows)
|
||||||
|
yield f"entries: {len(rows)}"
|
||||||
|
if rows:
|
||||||
|
lats = [float(r.get("stt_latency_s") or 0.0) for r in rows]
|
||||||
|
yield f"latency: mean={sum(lats)/len(lats):.2f}s max={max(lats):.2f}s"
|
||||||
|
yield ""
|
||||||
|
yield "== top tokens =="
|
||||||
|
for tok, cnt in freq.most_common(20):
|
||||||
|
yield f" {cnt:>3} {tok}"
|
||||||
|
yield ""
|
||||||
|
yield "== rare tokens (<=1, candidate corrections / hotwords) =="
|
||||||
|
rare = rare_tokens(freq)
|
||||||
|
yield " " + (", ".join(rare) if rare else "(none)")
|
||||||
|
yield ""
|
||||||
|
yield "== missing-diacritic candidates (review only) =="
|
||||||
|
cands = missing_diacritic_candidates(freq)[:30]
|
||||||
|
yield " " + (", ".join(cands) if cands else "(none)")
|
||||||
|
yield ""
|
||||||
|
suspects = suspect_rows(rows)
|
||||||
|
yield f"== likely-hallucination rows ({len(suspects)}) =="
|
||||||
|
for r in suspects:
|
||||||
|
yield (f" lat={float(r.get('stt_latency_s') or 0):.1f}s "
|
||||||
|
f"nsp={float(r.get('no_speech_prob') or 0):.2f} "
|
||||||
|
f"{row_text(r)!r}")
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--log", type=Path, default=DEFAULT_LOG, help="path to voice_stt_log.jsonl")
|
||||||
|
ap.add_argument("--tokens", action="store_true", help="token frequency only")
|
||||||
|
ap.add_argument("--rare", action="store_true", help="one-off tokens only")
|
||||||
|
ap.add_argument("--suspect", action="store_true", help="likely-hallucination rows only")
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
rows = read_log(args.log)
|
||||||
|
if not rows:
|
||||||
|
print(f"no entries in {args.log}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
freq = token_frequency(rows)
|
||||||
|
if args.tokens:
|
||||||
|
for tok, cnt in freq.most_common():
|
||||||
|
print(f"{cnt:>4} {tok}")
|
||||||
|
elif args.rare:
|
||||||
|
print("\n".join(rare_tokens(freq)))
|
||||||
|
elif args.suspect:
|
||||||
|
for r in suspect_rows(rows):
|
||||||
|
print(f"lat={float(r.get('stt_latency_s') or 0):.1f}s "
|
||||||
|
f"nsp={float(r.get('no_speech_prob') or 0):.2f} {row_text(r)!r}")
|
||||||
|
else:
|
||||||
|
print("\n".join(_iter_report(rows)))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
180
tools/voice_stt_spike.py
Normal file
180
tools/voice_stt_spike.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""STT model spike — compare faster-whisper models on Romanian diacritic accuracy.
|
||||||
|
|
||||||
|
Answers the autoplan D1 question: does a Romanian-finetuned whisper-small beat the
|
||||||
|
generic `small` on diacritics WITHOUT regressing latency? Synthesizes clean RO
|
||||||
|
audio via Supertonic (ground-truth text known, with diacritics), runs each model,
|
||||||
|
and scores diacritic preservation + word error rate + latency.
|
||||||
|
|
||||||
|
This is an evaluation harness, not production code. Synthetic TTS audio is a clean
|
||||||
|
probe for diacritic behaviour specifically — it is NOT a proxy for real-mic acoustic
|
||||||
|
robustness, so weight latency + diacritics, not absolute WER.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 tools/voice_stt_spike.py --models small,/home/.../whisper-small-ro-cv11-int8
|
||||||
|
python3 tools/voice_stt_spike.py --models small,<path> --threads 4 --trials 2
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import statistics
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unicodedata
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
SUPERTONIC_URL = "http://127.0.0.1:7788"
|
||||||
|
|
||||||
|
# Ground truth with correct Romanian diacritics (mirrors tools/voice_bench.py).
|
||||||
|
UTTERANCES_RO: list[tuple[str, str]] = [
|
||||||
|
("short", "Salut, ce mai faci?"),
|
||||||
|
("conversational", "Stai puțin să mă gândesc la asta."),
|
||||||
|
("medium", "Am verificat în calendar și avem ședință cu echipa la trei după-amiază."),
|
||||||
|
("numbers", "Costul total este o sută douăzeci și trei de lei și cincizeci de bani."),
|
||||||
|
("question", "Marius, vrei să-ți pun pe agenda de mâine să suni la NOAA?"),
|
||||||
|
("longer", "Vreau să-mi reamintești diseară să verific dacă scriptul de backup a rulat corect."),
|
||||||
|
]
|
||||||
|
|
||||||
|
_DIACRITICS = set("ăâîșțĂÂÎȘȚ")
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_punct_lower(text: str) -> list[str]:
|
||||||
|
out = []
|
||||||
|
for raw in text.split():
|
||||||
|
w = "".join(c for c in raw if c.isalnum() or c in _DIACRITICS)
|
||||||
|
if w:
|
||||||
|
out.append(w.lower())
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _deaccent(s: str) -> str:
|
||||||
|
# Map RO diacritics to base letters for "ignoring diacritics" comparison.
|
||||||
|
table = str.maketrans("ăâîșțĂÂÎȘȚ", "aaistAAIST")
|
||||||
|
s = s.translate(table)
|
||||||
|
return "".join(c for c in unicodedata.normalize("NFD", s)
|
||||||
|
if unicodedata.category(c) != "Mn")
|
||||||
|
|
||||||
|
|
||||||
|
def wer(ref: list[str], hyp: list[str]) -> float:
|
||||||
|
"""Word error rate via Levenshtein on token lists."""
|
||||||
|
n, m = len(ref), len(hyp)
|
||||||
|
if n == 0:
|
||||||
|
return 0.0 if m == 0 else 1.0
|
||||||
|
dp = list(range(m + 1))
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
prev = dp[0]
|
||||||
|
dp[0] = i
|
||||||
|
for j in range(1, m + 1):
|
||||||
|
cur = dp[j]
|
||||||
|
cost = 0 if ref[i - 1] == hyp[j - 1] else 1
|
||||||
|
dp[j] = min(dp[j] + 1, dp[j - 1] + 1, prev + cost)
|
||||||
|
prev = cur
|
||||||
|
return dp[m] / n
|
||||||
|
|
||||||
|
|
||||||
|
def diacritic_score(ref: str, hyp: str) -> tuple[int, int]:
|
||||||
|
"""For each ground-truth word that carries a diacritic, did the hypothesis
|
||||||
|
contain that exact diacritized word? Returns (correct, total)."""
|
||||||
|
ref_words = _strip_punct_lower(ref)
|
||||||
|
hyp_words = set(_strip_punct_lower(hyp))
|
||||||
|
hyp_deaccent = {_deaccent(w) for w in hyp_words}
|
||||||
|
correct = total = 0
|
||||||
|
for w in ref_words:
|
||||||
|
if set(w) & _DIACRITICS:
|
||||||
|
total += 1
|
||||||
|
if w in hyp_words:
|
||||||
|
correct += 1
|
||||||
|
return correct, total
|
||||||
|
|
||||||
|
|
||||||
|
def synthesize(text: str, out_path: Path) -> float:
|
||||||
|
import wave
|
||||||
|
r = httpx.post(f"{SUPERTONIC_URL}/v1/audio/speech",
|
||||||
|
json={"model": "supertonic-3", "input": text, "voice": "M2",
|
||||||
|
"response_format": "wav", "lang": "ro"}, timeout=60.0)
|
||||||
|
r.raise_for_status()
|
||||||
|
out_path.write_bytes(r.content)
|
||||||
|
with wave.open(str(out_path), "rb") as wf:
|
||||||
|
return wf.getnframes() / float(wf.getframerate())
|
||||||
|
|
||||||
|
|
||||||
|
def run_model(model_ref: str, wavs: list[tuple[str, str, str, float]],
|
||||||
|
threads: int, trials: int) -> dict:
|
||||||
|
from faster_whisper import WhisperModel
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
model = WhisperModel(model_ref, device="cpu", compute_type="int8", cpu_threads=threads)
|
||||||
|
load_s = time.perf_counter() - t0
|
||||||
|
rows, lats = [], []
|
||||||
|
dia_c = dia_t = 0
|
||||||
|
wers = []
|
||||||
|
for name, ref_text, wav_path, _dur in wavs:
|
||||||
|
best_text = ""
|
||||||
|
for trial in range(trials):
|
||||||
|
t1 = time.perf_counter()
|
||||||
|
segments, _ = model.transcribe(wav_path, language="ro", beam_size=5,
|
||||||
|
temperature=0.0, condition_on_previous_text=False)
|
||||||
|
text = " ".join(s.text.strip() for s in segments).strip()
|
||||||
|
lats.append(time.perf_counter() - t1)
|
||||||
|
if trial == 0:
|
||||||
|
best_text = text
|
||||||
|
c, tt = diacritic_score(ref_text, best_text)
|
||||||
|
dia_c += c
|
||||||
|
dia_t += tt
|
||||||
|
w = wer(_strip_punct_lower(ref_text), _strip_punct_lower(best_text))
|
||||||
|
wers.append(w)
|
||||||
|
rows.append((name, ref_text, best_text, c, tt, w))
|
||||||
|
return {
|
||||||
|
"model": model_ref, "load_s": load_s, "rows": rows,
|
||||||
|
"p50": statistics.median(lats), "p95": sorted(lats)[max(0, int(0.95*(len(lats)-1)))],
|
||||||
|
"dia_correct": dia_c, "dia_total": dia_t,
|
||||||
|
"wer": statistics.mean(wers) if wers else 1.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--models", required=True, help="CSV of model names or CT2 dirs")
|
||||||
|
ap.add_argument("--threads", type=int, default=4)
|
||||||
|
ap.add_argument("--trials", type=int, default=2)
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
work = Path(tempfile.mkdtemp(prefix="stt_spike_"))
|
||||||
|
print(f"[spike] synth dir {work}", flush=True)
|
||||||
|
wavs = []
|
||||||
|
for name, text in UTTERANCES_RO:
|
||||||
|
p = work / f"{name}.wav"
|
||||||
|
dur = synthesize(text, p)
|
||||||
|
wavs.append((name, text, str(p), dur))
|
||||||
|
print(f"[spike] TTS {name}: {dur:.2f}s", flush=True)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for ref in args.models.split(","):
|
||||||
|
ref = ref.strip()
|
||||||
|
if not ref:
|
||||||
|
continue
|
||||||
|
print(f"[spike] running {ref} (threads={args.threads})…", flush=True)
|
||||||
|
results.append(run_model(ref, wavs, args.threads, args.trials))
|
||||||
|
|
||||||
|
print("\n" + "=" * 72)
|
||||||
|
print(f"{'model':<42} {'p50':>6} {'p95':>6} {'WER':>6} {'diacr':>8}")
|
||||||
|
print("-" * 72)
|
||||||
|
for r in results:
|
||||||
|
dia = f"{r['dia_correct']}/{r['dia_total']}"
|
||||||
|
label = Path(r["model"]).name if "/" in r["model"] else r["model"]
|
||||||
|
print(f"{label:<42} {r['p50']:>5.2f}s {r['p95']:>5.2f}s {r['wer']*100:>5.1f}% {dia:>8}")
|
||||||
|
print("=" * 72)
|
||||||
|
for r in results:
|
||||||
|
label = Path(r["model"]).name if "/" in r["model"] else r["model"]
|
||||||
|
print(f"\n### {label}")
|
||||||
|
for name, ref_text, hyp, c, tt, w in r["rows"]:
|
||||||
|
print(f" [{name}] ref: {ref_text}")
|
||||||
|
print(f" [{name}] hyp: {hyp} (diacr {c}/{tt}, wer {w*100:.0f}%)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user