Compare commits

...

4 Commits

Author SHA1 Message Date
738dc0181a chore: auto-commit from dashboard 2026-07-10 15:12:22 +00:00
7b44749e5a fix(newsletter): elimină jobul APScheduler intern duplicat și bugged
Jobul intern rula pe zile greșite (vineri/sâmbătă/marți în loc de
joi/vineri/luni) din cauza convenției diferite de day-of-week dintre
APScheduler și crontab standard. Scriptul standalone via OS crontab
e mecanismul corect și funcțional — rămâne singura sursă.

chore: sync stare working-tree — cron, KB notes, anaf-monitor snapshots
2026-07-09 14:54:15 +00:00
d175d5ba5a chore: working-tree state — anaf snapshots, cron state, KB notes, tools
Pre-existing uncommitted changes swept in with the STT work:
anaf-monitor snapshots/versions, cron job + newsletter state, 9 youtube KB
notes, tools/ocr_bon.py, and tools/tts.py.

Note: the tts.py change breaks 2 truncation tests in test_voice_normalize.py
(sanitize word-count) — flagged for a separate follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-27 18:16:31 +00:00
ce273d14db feat(voice): improve Romanian STT — hallucination gate + finetuned model
Gemma 4 cloud audio was infeasible (31b-cloud has no audio; E4B broken
upstream, no deploy host), so improve faster-whisper instead.

- Pin temperature=0.0 to disable the fallback ladder that re-decoded unclear
  audio up to 6x (source of the 16-24s latency outliers); reject hallucinated
  segments via avg_logprob/compression_ratio in the new pure _filter_segments.
- Adopt mikr/whisper-small-ro-cv11 (CT2 int8) via configurable voice.stt_model:
  spike showed WER 24%->10%, numbers fixed at source, +0.33s p50 (in budget).
- Add tools/voice_stt_mine.py (log mining) + tools/voice_stt_spike.py (model
  eval with diacritic scoring) + tests for the gate and miner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-27 18:16:16 +00:00
46 changed files with 2575 additions and 277 deletions

1
.gitignore vendored
View File

@@ -29,3 +29,4 @@ memory.bak/
approved-tasks.json
dashboard/status.json
tools/anaf-monitor/monitor.log
models/

View File

@@ -60,11 +60,6 @@
"git": 14400
}
},
"newsletter_cercetasi": {
"enabled": true,
"cron": "0 17 * * 4,5,1",
"channel": "echo-core"
},
"allowed_tools": [
"Read",
"Edit",
@@ -109,8 +104,9 @@
"949388626146517022"
],
"user_name": "Marius",
"default_voice": "F1",
"auto_leave_minutes": 5
"default_voice": "M1",
"auto_leave_minutes": 5,
"stt_model": "/home/moltbot/echo-core/models/whisper-small-ro-cv11-int8"
},
"paths": {
"personality": "personality/",
@@ -118,5 +114,10 @@
"memory": "memory/",
"logs": "logs/",
"sessions": "sessions/"
},
"roa2web": {
"base_url": "https://roa2web.romfast.ro/roa2web/api",
"default_company": "114",
"default_company_name": "ROMFAST SRL"
}
}

View File

@@ -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",
"kind": "shell",
@@ -23,9 +11,9 @@
"report_on": "changes",
"timeout": 120,
"enabled": true,
"last_run": "2026-06-06T16:00:00.002312+00:00",
"last_run": "2026-07-10T10:00:00.001575+00:00",
"last_status": "ok",
"next_run": "2026-06-09T10:00:00+00:00"
"next_run": "2026-07-10T16:00:00+00:00"
},
{
"name": "security-audit-daily",
@@ -38,10 +26,10 @@
],
"report_on": "changes",
"timeout": 180,
"enabled": true,
"last_run": "2026-06-09T03:00:00.002688+00:00",
"enabled": false,
"last_run": "2026-06-21T03:00:00.004155+00:00",
"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",
@@ -55,9 +43,9 @@
"report_on": "never",
"timeout": 120,
"enabled": true,
"last_run": "2026-06-09T03:30:00.002397+00:00",
"last_run": "2026-07-10T03:30:00.002026+00:00",
"last_status": "ok",
"next_run": "2026-06-10T03:30:00+00:00"
"next_run": "2026-07-11T03:30:00+00:00"
},
{
"name": "archive-tasks-daily",
@@ -71,9 +59,9 @@
"report_on": "changes",
"timeout": 60,
"enabled": true,
"last_run": "2026-06-09T03:00:00.002281+00:00",
"last_run": "2026-07-10T03:00:00.002135+00:00",
"last_status": "ok",
"next_run": "2026-06-10T03:00:00+00:00"
"next_run": "2026-07-11T03:00:00+00:00"
},
{
"name": "backup-config",
@@ -87,9 +75,9 @@
"report_on": "never",
"timeout": 120,
"enabled": true,
"last_run": "2026-06-09T02:00:00.002899+00:00",
"last_run": "2026-07-10T02:00:00.002082+00:00",
"last_status": "ok",
"next_run": "2026-06-10T02:00:00+00:00"
"next_run": "2026-07-11T02:00:00+00:00"
},
{
"name": "insights-extract",
@@ -248,31 +236,31 @@
"next_run": null
},
{
"name": "heartbeat-2h",
"cron": "0 6-18/2 * * *",
"name": "heartbeat-4h",
"cron": "0 6-18/4 * * *",
"channel": "echo-work",
"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.",
"allowed_tools": [],
"enabled": true,
"last_run": "2026-06-09T08:00:00.001362+00:00",
"last_run": "2026-07-10T14:00:00.002633+00:00",
"last_status": "ok",
"next_run": "2026-06-09T10:00:00+00:00"
"next_run": "2026-07-10T18:00:00+00:00"
},
{
"name": "night-execute",
"cron": "0 23 * * *",
"channel": "echo-work",
"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",
"allowed_tools": [
"Bash",
"Read",
"Write"
],
"last_run": "2026-06-08T23:00:00.001531+00:00",
"last_status": "ok",
"next_run": "2026-06-09T23:00:00+00:00"
"last_run": "2026-06-20T23:00:00.001763+00:00",
"last_status": "error",
"next_run": "2026-06-21T23:00:00+00:00"
}
]

View File

@@ -1,5 +1,5 @@
{
"last_sent": 21,
"last_sent": 26,
"year": 2026,
"last_sent_at": "2026-06-04T19:53:04.648928+00:00"
"last_sent_at": "2026-07-09T15:01:09.525808+00:00"
}

View File

@@ -0,0 +1,60 @@
# Cele 6 Nevoi Fundamentale — Tony Robbins
**Sursa:** https://www.facebook.com/share/v/18up5bw3Co/
**Data:** 2026-06-29
**Tags:** @coaching @growth @psychology
---
## TL;DR
Tony Robbins explica cele 6 nevoi fundamentale ale omului si cum primele doua (top two needs) iti guverneaza toate deciziile. Exercitiu practic: identifica-ti top 2 nevoi actuale, costul lor, si ce nevoi ti-ar trebui pentru a trece la urmatorul nivel.
---
## Cele 6 Nevoi Fundamentale (Human Needs Psychology)
1. **Certainty** (siguranta, confort, predictibilitate)
2. **Uncertainty / Variety** (noutate, surpriza, adrenalina)
3. **Significance** (a fi unic, important, special, remarcat)
4. **Love & Connection** (iubire, apartenta, legatura cu ceilalti)
5. **Growth** (crestere, evolutie, progres)
6. **Contribution** (a contribui la ceva mai mare decat tine)
---
## Povestea ilustrativa — fata goth de 13 ani
Tony vede o fata cu look goth (13 cercelusi, machiaj intens) care urmareste un baiat. Conversatie rapida dezvaluie:
- **Nevoie #1: Significance** — 13 cercelusi, "nu sunt ca ceilalti, sunt unica, speciala, diferita"
- **Nevoie #2: Certainty / Comfort** — breloc Tigger de la tata, cearceafuri Tigger, tapet Tigger — confortul copilariei ca ancora
Observatie: toata "duritatea" exterioara era o strategie de significance. Sub ea, nevoia de confort era la fel de puternica.
---
## Exercitiu practic (din video)
### Intrebarea 1 — Nevoi actuale (cum ai trait pana acum)
- Care sunt top 2 nevoi **dupa care ai trait** (nu ce ai vrut, ci cum ai functionat)?
- Ce te-a costat sa ai aceste nevoi in top? Ce ai pierdut? Ce relatii/sanatate/energie a suferit?
### Intrebarea 2 — Nevoi pentru urmatorul nivel
- Care ar trebui sa fie top 2 nevoi pentru a merge la urmatorul nivel al vietii?
- Daca ai face schimbarea, cum s-ar schimba viata in bine?
---
## Insight-uri cheie
- **Nu ce vrei, ci cum traiesti** — toata lumea "vrea" iubire, dar multi pun certainty sau significance inaintea ei, deci de fapt iubirea nu e prima nevoie operationala.
- **Top 2 nevoi iti ghideaza toate deciziile** — cand stii ale tale si ale celorlalti, stii ce probleme vor aparea.
- **Schimbarea de nevoi = schimbare de identitate** — nu e mintala, e somatica ("inside your body, not just your mind").
---
## Idei acționabile
- [ ] Scrie top 2 nevoi dupa care ai trait pana acum — fii sincer, nu ce vrei sa fie @coaching
- [ ] Scrie costul concret: ce ai ratat/stricat avand acele nevoi in top @growth
- [ ] Decide top 2 nevoi pentru urmatorul nivel si descrie cum s-ar schimba viata @growth

View File

@@ -1,6 +1,6 @@
# Index — facebook/
> 10 note. Citește acest index întâi; deschide doar fișierele relevante.
> 11 note. Citește acest index întâi; deschide doar fișierele relevante.
- **[Love Yourself Even If They Don't](2026-04-29_julien-blanc-love-yourself.md)** `#self-love #self-esteem #social-approval #mindset`
Julien Blanc face un exercițiu pe stradă: îl întreabă pe un trecător care e artistul lui preferat. Răspunsul: Glenn Gould. Nimeni din grup n
@@ -22,3 +22,5 @@
Creierul tău poate schimba starea în 15 secunde fără să-ți schimbi viața. Trei trucuri: (1) **Face feedback** — relaxează fața ușor 15 sec,
- **[7 Micro Habits That Rewire Your Happiness](2026-06-07_7-micro-habits-rewire-happiness.md)**
7 micro-obiceiuri bazate pe psihologie pentru a reprograma creierul spre fericire: zâmbetul de 3 secunde (declanșează dopamină/serotonină),
- **[Cele 6 Nevoi Fundamentale — Tony Robbins](2026-06-29_tony-robbins-top-two-needs.md)** `@growth`
Tony Robbins explica cele 6 nevoi fundamentale ale omului si cum primele doua (top two needs) iti guverneaza toate deciziile. Exercitiu prac

View File

@@ -1,5 +1,122 @@
{
"notes": [
{
"file": "notes-data/youtube/2026-07-10_opencode-token-usage-96-percent.md",
"title": "I Cut My OpenCode Token Usage by 96% - Here's How",
"date": "2026-07-10",
"tags": [],
"domains": [
"work",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Autorul folosește un proxy man-in-the-middle pentru a vedea exact ce trimite OpenCode (agent de coding AI) către LLM la un simplu \"hello\" — și găsește 8.000 de tokeni: system prompt de 9.500 caractere..."
},
{
"file": "notes-data/youtube/2026-07-08_fine-tune-modele-mari-kimi-k27.md",
"title": "Fine-Tune the biggest open-source models (even with a bad PC)",
"date": "2026-07-08",
"tags": [],
"domains": [
"work",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Tutorial complet despre cum se fine-tunează modele AI masive (ex: Kimi K2.7 — 1 trilion parametri) pe date proprii, fără hardware de sute de mii de dolari, folosind platforma **Fireworks AI** și tehni..."
},
{
"file": "notes-data/youtube/2026-07-04_anti-aging-mitochondria-martin-picard.md",
"title": "Anti-Aging Expert: This Reverses Gray Hairs, Restores Your Youth & Boosts Your Energy!",
"date": "2026-07-04",
"tags": [],
"domains": [
"health",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Dr. Martin Picard explică de ce mitochondriile — nu genele — determina cat de repede imbatranesti. Secretul anti-aging nu e o suplimentare sau dieta, ci **alocarea eficienta a energiei**. Stresul cron..."
},
{
"file": "notes-data/youtube/2026-07-04_multi-millionaire-10x-income-side-hustles.md",
"title": "Multi-Millionaire: Cele mai bune side hustles pentru 10x Income",
"date": "2026-07-04",
"tags": [],
"domains": [
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Chris Koerner explică cum pornești o afacere cu $1k-$5k și ce principii l-au dus la succes cu 35+ proprietăți RV și multiple business-uri. Esența: alege unsexy, caută unfair advantage personal, evită ..."
},
{
"file": "notes-data/youtube/2026-07-04_hiking-food-250g-protein-endurance.md",
"title": "Hiking Food: 250g Protein Secret for Endurance",
"date": "2026-07-04",
"tags": [],
"domains": [
"health",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Un hiker cu 20.000 de mile pe trasee explică sistemul lui de nutriție optimizat pentru endurance: proteina ridicată (1.3-1.5g/kg corp), grăsimi ca sursă primară de energie, carbohidrați naturali doar ..."
},
{
"file": "notes-data/facebook/2026-06-29_tony-robbins-top-two-needs.md",
"title": "Cele 6 Nevoi Fundamentale — Tony Robbins",
"date": "2026-06-29",
"tags": [],
"domains": [
"growth"
],
"types": [
"coaching"
],
"category": "facebook",
"project": null,
"subdir": null,
"video": "",
"tldr": "Tony Robbins explica cele 6 nevoi fundamentale ale omului si cum primele doua (top two needs) iti guverneaza toate deciziile. Exercitiu practic: identifica-ti top 2 nevoi actuale, costul lor, si ce ne..."
},
{
"file": "notes-data/youtube/2026-06-29_launch-million-dollar-startup-ai.md",
"title": "Watch me launch a million dollar startup with AI (live)",
"date": "2026-06-29",
"tags": [
"antreprenoriat",
"ai-marketing",
"validare",
"startup"
],
"domains": [
"work",
"growth"
],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Un playbook de 9 prompts copy-paste care duce de la idee la landing page live cu reclame Meta, în sub 30 de minute. Cheia: \"vibe marketing\" (nu coding) + validare pre-vânzare ÎNAINTE să construiești p..."
},
{
"file": "notes-data/youtube/2026-06-27_jeremy-grantham-ai-bubble-warning.md",
"title": "Billionaire's WARNING: I'm SELLING. The Crash Is Already Here! — Jeremy Grantham",
@@ -22,7 +139,38 @@
"project": null,
"subdir": null,
"video": "https://www.youtube.com/watch?v=32u5T6lO8qk",
"tldr": "*Notă: Sumarizarea va fi adăugată de Echo.*"
"tldr": "Jeremy Grantham — 87 ani, 60 ani experiență, 165 miliarde $ gestionați — avertizează că suntem în cea mai mare bulă investițională din istoria SUA, generată de AI. Sfatul său: vinde actiunile US tech ..."
},
{
"file": "notes-data/youtube/2026-06-27_djokovic-discipline-changes-everything.md",
"title": "Discipline Changes Everything | Novak Djokovic",
"date": "2026-06-27",
"tags": [],
"domains": [
"growth",
"health"
],
"types": [
"coaching"
],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Djokovic vorbeste despre transformarea sa prin disciplina, nutritie si mentalitate. Punctul central: limitele sunt constructe mentale, nu realitate. Mediul inconjurator face sau desface un obicei. Rel..."
},
{
"file": "notes-data/youtube/2026-06-27_cody-sanchez-unwanted-land.md",
"title": "Why I'm Buying UNWANTED Land (And You Should Too) | Cody Sanchez",
"date": "2026-06-27",
"tags": [],
"domains": [],
"types": [],
"category": "youtube",
"project": null,
"subdir": null,
"video": "",
"tldr": "Cody Sanchez cumpara 10 acri in West Texas pentru sub $10,000 in mai putin de o saptamana — ca experiment sa vada daca visul american e inca accesibil la pret mic. Concluzia: poti cumpara teren fara m..."
},
{
"file": "notes-data/youtube/2026-06-27_google-open-knowledge-format.md",
@@ -10060,11 +10208,11 @@
}
],
"stats": {
"total": 581,
"total": 590,
"by_domain": {
"work": 199,
"health": 101,
"growth": 267,
"work": 202,
"health": 104,
"growth": 275,
"sprijin": 39,
"scout": 9
},
@@ -10074,14 +10222,14 @@
"conversations": 0,
"emails": 22,
"exercitii": 4,
"facebook": 10,
"facebook": 11,
"health": 6,
"insights": 46,
"projects": 234,
"reflectii": 3,
"retete": 1,
"tools": 7,
"youtube": 151,
"youtube": 159,
"memory": 44
}
},

View File

@@ -7,14 +7,14 @@
- **[conversations/](conversations/index.md)** — 1 note
- **[emails/](emails/index.md)** — 22 note
- **[exercitii/](exercitii/index.md)** — 4 note
- **[facebook/](facebook/index.md)** — 10 note
- **[facebook/](facebook/index.md)** — 11 note
- **[health/](health/index.md)** — 6 note
- **[insights/](insights/index.md)** — 46 note
- **[projects/](projects/index.md)** — 234 note
- **[reflectii/](reflectii/index.md)** — 3 note
- **[retete/](retete/index.md)** — 1 note
- **[tools/](tools/index.md)** — 7 note
- **[youtube/](youtube/index.md)** — 151 note
- **[youtube/](youtube/index.md)** — 159 note
## Note la rădăcină

View File

@@ -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

View 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

View 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

View 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...

View File

@@ -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

View 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

View 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).

View File

@@ -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.

View File

@@ -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)

View File

@@ -0,0 +1,84 @@
# Why I'm Buying UNWANTED Land (And You Should Too) | Cody Sanchez
**Sursa:** https://youtu.be/qewYYObfsiA
**Data:** 2026-06-27
**Durata:** 21:00
**Tags:** @money @real-estate @business
---
## TL;DR
Cody Sanchez cumpara 10 acri in West Texas pentru sub $10,000 in mai putin de o saptamana — ca experiment sa vada daca visul american e inca accesibil la pret mic. Concluzia: poti cumpara teren fara multi bani, mai repede decat ti se spune, si fara permisiunea altora.
---
## Cum a facut-o — framework in 3 pasi
1. **Criterii** — ce vrei? (locatie, suprafata, restrictii)
2. **Budget** — cat max? (al lui: $10K)
3. **Timeline** — cand inchei? (al lui: 1 saptamana)
---
## Cum a gasit terenul
- Platforma: **land.com** — favoriteaza listinguri, filtrezi dupa locatie + pret
- Strategia de locatie: West Texas (langa Marfa) = teren ieftin + aproape de national parks = turisti
- **Realtor specialist** in teren raw, nu generic — Phil i-a gasit in final ce voia
- Regula de trei la apeluri: suni 3 realtor-i, astepti sa te sune unul inapoi
---
## Costuri reale (all-in)
| Item | Cost |
|------|------|
| 10 acri teren | $9,000 |
| Benzina/transport | $200 |
| Title check | $500 |
| Comision realtor | $300 |
| **Total** | **~$10,000** |
---
## Modele de profit pe teren raw
- **Glamping / camping** — rulota/camper turnkey de pe Facebook Marketplace (~$16K), inchiriata la $80150/noapte
- **Yurt-uri** (Mongolian gur) — $125150K per unitate construit; un complex de 10 yurturi face $800K gross / $300K net pe an
- **TP tents / containere** — setup ieftin ($1030K)
- **Agricultural exemptions** — taxe mai mici daca ai vaci, albine, wildlife monitoring (vanatoare/numaratoare cerbi) — minim 10 acri
---
## 5 scam-uri de evitat la teren
1. **Vanzatorul nu detine terenul** → verifica intotdeauna titlul (title policy ~$500)
2. **Terenul nu e ce zic** → inspectie in option period (10 zile)
3. **Owner financing abuziv** → clauze care permit recuperarea integrala a terenului la o singura intarziere de plata
4. **Flood planes** — chiar si in desert, drenajul conteaza
5. **Mineral rights** — o companie energetica poate detine drepturi sa foreze pe terenul tau
---
## Hack-uri practice
- **Locatie**: cumpara ieftin langa un oras turistic (Marfa, Big Bend) — turistii vin, dar se intorc la tine sa "scape de multime"
- **Option period**: negociaza 10 zile sa verifici: apa (foraj put), septic, electricitate
- **Survey**: cere title company sa comande un survey — mizeaza steak-uri la colturile terenului
- **Parteneri**: poti pune un camper/rulota pe terenul altcuiva — impart profitul
- **Procese, nu job**: checklist-uri per unitate + staff = business, nu job glorificat
---
## Quote
> "Those who say it's impossible should get out of the way of those of us who are doing it."
---
## Idei acționabile
- [ ] Exploreaza land.com pentru terenuri in zone rurale ieftine langa atractii turistice @real-estate
- [ ] Calculeaza: 1 rulota/camper $16K → $100/noapte × 200 nopti = $20K/an = breakeven in 1 an @money
- [ ] La orice cumparare teren: title policy + option period 10 zile + verify water/electric/septic @real-estate

View File

@@ -0,0 +1,52 @@
# Discipline Changes Everything | Novak Djokovic
**Sursa:** https://youtu.be/Gj8TdHnIfuk
**Data:** 2026-06-27
**Durata:** 15:24
**Tags:** @growth @health @coaching
---
## TL;DR
Djokovic vorbeste despre transformarea sa prin disciplina, nutritie si mentalitate. Punctul central: limitele sunt constructe mentale, nu realitate. Mediul inconjurator face sau desface un obicei. Relatia cu tatalsau si sentimentul de "a nu fi de ajuns" l-au condus mai departe decat succesul in sine. La 38 de ani, inca testeaza limite — din curiozitate, nu din ego.
---
## Puncte cheie
- **Mediul e totul** — stiinta spune 21 de zile pentru un nou obicei, dar fara mediu suportiv e aproape imposibil. Suntem fiinte sociale, tribale — avem nevoie sa apartinem chiar si unui singur om.
- **Limitele sunt constructe mentale** — nu se considera ca are limite. Crede ca oamenii isi construiesc propriile plafoane in minte.
- **Nutritia l-a transformat** — dupa 3 ani fara slam, ajustarea nutritiei a schimbat totul. Urca in sus.
- **Analiza infrângerilor** — Kobe Bryant il sfatuia sa revada intervale specifice din meciurile pierdute, indiferent de disconfort. Acolo e lectia reala.
- **Sentimentul de "nu sunt de ajuns"** — vine din relatia cu tatal sau. Il motiveaza, dar e si o batalie zilnica. Nu cauta sa-l elimine, cauta echilibrul.
- **Ego vs umilinta** — dupa ce "umblai pe apa", egoul te duce in locuri din care e greu sa revii. Lectia: ce a functionat 20 de ani nu e garantat sa functioneze acum.
- **Plictiseala ca instrument** — fiului sau ii spune: sta cu gandurile tale. Nu pune ecranul imediat. Plictiseala e momentul cel mai creativ.
- **Sportul ca viata condensata** — un meci de tenis e o viata intr-o ora: esti egal la start, treci prin indoieli, frici, ups and downs, si la final câstigi sau pierzi. De aceea oamenii se identifica cu atletii.
- **Transitia dupa sport** — adrenalina trebuie re-canalizata, nu pur si simplu oprita. Isi pregateste urmatoarea pasiune: sanatate, wellness, wellbeing.
- **Battlefield = crestere** — a ales LA tocmai pentru ca acolo vede cel mai bun si cel mai rau din el. Slabiciunea te tine pe drum, forta te ajuta sa impartasesti.
---
## Quote-uri
> "Not having success is not an option. It's basically a matter of existence, a survival of my family."
> "The limits are normally constructs in our mind."
> "I feel like I can use tennis as a platform to evolve into a better human being."
> "When you are bored, you don't have to instantly take a book or a screen. You need to also learn how to be with your thoughts."
> "On the battlefield, you see the best of yourself and the worst of yourself. And the worst of myself reminds me to keep going."
> "If you are only reminded of your strength, you just have your ego. If you were only reminded of your weaknesses, you would be depressed. It's that dichotomy."
---
## Idei acționabile
- [ ] **Audit mediu** — cine sunt oamenii din jur? Sustin schimbarile pe care vrei sa le faci? @growth
- [ ] **Revede un esec recent** — un interval specific dintr-o situatie pierduta. Ce ai putea face diferit? @coaching
- [ ] **10 minute fara ecran** — dimineata sau seara, sta cu gandurile. Nu cauta sa le umpli. @health
- [ ] **Battlefield personal** — ce situatie/mediu te scoate din zona de confort si te face sa cresti? Mergi acolo mai des. @growth

View File

@@ -0,0 +1,64 @@
# Watch me launch a million dollar startup with AI (live)
**URL:** https://youtu.be/Tz881ee0f8M
**Durata:** 21:59
**Canal:** L7V (Michia — serial entrepreneur, VC)
**Data:** 2026-06-29
**Tags:** @work @growth #antreprenoriat #ai-marketing #validare #startup
---
## TL;DR
Un playbook de 9 prompts copy-paste care duce de la idee la landing page live cu reclame Meta, în sub 30 de minute. Cheia: "vibe marketing" (nu coding) + validare pre-vânzare ÎNAINTE să construiești produsul. Tools: AppMagic (research piață), Claude Code + MCP-uri (Higgsfield, Meta Ads, Vercel). Filosofia centrală: găsești o categorie care deja câștigă bani → cauți "blue ocean" în acea categorie → lansezi landing page → testezi cu reclame plătite → construiești doar dacă oamenii își dau emailul.
---
## Puncte cheie
- **Validare înainte de construire** — cheltuie bani pe reclame să testezi ideea ÎNAINTE să scrii cod. E mai ieftin să afli că nimeni nu vrea produsul decât să-l construiești și să afli după.
- **AppMagic.rocks** (gratuit) — vezi ce aplicații câștigă deja bani în categoria ta. Nu ghici piața, caut-o în date reale.
- **Blue ocean în categorii dovedite** — nu concura direct, găsește un unghi nou în nișa care deja are tracțiune (ex: plant identifier → AI garden designer cu before/after viral).
- **9 prompts, tot playbook-ul** — disponibile gratuit la l7v.com/ai, mereu actualizate cu cele mai noi modele.
- **MCP-uri conectate la Claude** — Higgsfield (imagini + video ads), Meta Ads Manager, Vercel. Claude orchestrează totul fără să scrii cod.
- **Higgsfield Marketing Studio** — template-uri de ad-uri dovedite (UGC, before/after, comparison table). Nu inventezi formate, le folosești pe cele care funcționează.
- **Pixel Meta din prima zi** — setezi tracking înainte să pornești reclame ca Meta's AI să învețe cine convertește și să găsească mai mulți.
- **Self-improving loop** — ultimul prompt: extrage ad-urile cu cel mai mic cost per conversie → iterează cu Higgsfield → trimite noi teste. Se îmbunătățește singur în timp.
- **Vercel gratuit** pentru deployment — URL-ul de vercel.app convertește, nu e nevoie de domeniu propriu în faza de validare.
---
## Quotes notabile
> "Forget vibe coding. Vibe marketing is printing millionaires faster than any time in history."
> "AI does the work, we get to have a bit more fun in life. That's the whole point, right?"
> "If you are not taking advantage of this, you are being left behind by the guys that are."
> "It's much cheaper for you to validate people will buy what you're building than build it and then hope they will buy."
---
## Flow complet (9 pași)
1. **AppMagic** → găsești categoria cu tracțiune + top apps
2. **Prompt 1 Claude** → analizează app-ul, propune 5 blue ocean ideas
3. **Prompt 2** → alegi idea, Claude generează product spec complet (UX, culori, viral hooks)
4. **Prompt 3 + Higgsfield MCP** → generează mockup-uri și screenshot-uri app
5. **Prompt 4** → landing page cu quiz + email capture + dashboard admin
6. **Meta Ads Manager** → setezi pixel de conversie
7. **Prompt 5** → Claude adaugă pixelul în landing page via Meta MCP
8. **Higgsfield Marketing Studio prompt** → 20 reclame (UGC, before/after, video)
9. **Vercel MCP** → deploy landing page live
10. **Meta Ads prompt** → Claude uploadează și configurează campaniile automat
11. **Self-improve prompt** → rulezi periodic, extrage winners, iterează
---
## Aplicabilitate pentru Marius
- Playbook util pentru validarea rapidă a oricărui SaaS/tool înainte de investit timp în dev
- Pattern "găsești ce câștigă deja → blue ocean" — aplicabil și pentru features ERP ROA
- MCP-uri pentru Claude Code — model de referință pentru integrări externe
- l7v.com/ai — bookmark, prompts actualizate automat

View File

@@ -0,0 +1,54 @@
# Anti-Aging Expert: This Reverses Gray Hairs, Restores Your Youth & Boosts Your Energy!
**Sursa:** https://m.youtube.com/watch?v=6xlmaorRY0w
**Invitat:** Dr. Martin Picard — Profesor Behavioral Medicine, Columbia; Director Mitochondrial Psychobiology Group
**Durata:** 2h 38min
**Tags:** @health @growth @antrenament @energie @longevitate
---
## TL;DR
Dr. Martin Picard explică de ce mitochondriile — nu genele — determina cat de repede imbatranesti. Secretul anti-aging nu e o suplimentare sau dieta, ci **alocarea eficienta a energiei**. Stresul cronic fura 60% din energia zilnica. Scopul de viata, miscarrea si somnul sunt cele mai puternice "medicamente" pentru mitocondrii. Parul alb poate fi reversibil.
---
## Puncte cheie
- **Suntem energie**: mitochondriile genereaza ATP — tot ce facem (gandire, emotii, miscare) consuma aceasta energie dintr-un buget finit.
- **Stresul = furt de energie**: hormonii de stres cresc consumul energetic cu **60%** — energia e furata de la procesele care te mentin tanar (imunitate, reparare ADN, productie de colagen).
- **Nu stresul, ci raspunsul la stres**: problema nu e stresul acut (e sanatos), ci stresul cronic si **rezistenta la stres** (a nu accepta ce nu poti controla).
- **Parul alb e reversibil**: cercetarile Picard arata ca stresul intens albesteste parul, iar reducerea stresului poate inversa procesul — dovada directa ca imbatranirea nu e unidirectionala.
- **Rezistenta la energie**: diabetul, Alzheimer, cancerul sunt boli de "rezistenta energetica" — celulele nu mai pot procesa energia eficient (similar cu rezistenta la insulina).
- **Scopul de viata = mitocondrii mai puternice**: studii pe creiere de oameni decedati arata ca cei cu sens puternic de viata au mitocondrii mai eficiente.
- **Mancatul mai mult NU = mai multa energie**: calitatea si sincronizarea conteaza mai mult decat cantitatea; breakfast nu e obligatoriu pentru toata lumea.
- **Maratonul dubleaza mitochondriile**: antrenamentul de rezistenta este cel mai puternic stimul pentru biogeneza mitocondrala.
- **Dementa = "Diabet tip 3"**: creierul isi pierde capacitatea de a procesa glucoza; comunitatile cu stil de viata activ + social au rate mult mai mici de dementa.
- **Red light therapy**: are potential pentru mitochondrii (fotobiomodulare), dar calitatea dispozitivelor variaza enorm — alege cu atentie.
---
## Quotes
> "It's not the stress that burns us down, it's the **response to stress**."
> "We are really in service of the mitochondria. They allow us to be alive."
> "The secret to anti-aging is the **proper allocation of energy**."
> "Energy is real — it's the difference between feeling like you can change the world or feeling completely drained."
---
## Idei actionabile
- [ ] **Reduceti rezistenta**: cand simti tensiune (trafic, conflict, deadline) — nu lupta, accepta si redirecționeaza energia spre ce controlezi @health
- [ ] **Miscare aerobica regulata**: minim 3x/saptamana, zona 2 (conversation pace) — cel mai bun stimul mitocondrial @health
- [ ] **Dormit serios**: somnul e cand mitochondriile se repara — prioritate, nu optiune @health
- [ ] **Sens de viata clar**: oamenii cu scop puternic au mitocondrii mai eficiente — nu e woo, e biologie @growth
- [ ] **Stres acut DA, stres cronic NU**: expunere voluntara (sport, dus rece, post) = antrenament mitocondrial. Stres cronic (ruminate, ingrijorare) = furt de energie @health
- [ ] **Analize recomandate**: lactate la efort, GDF-15 (marker inflamatie mitocondriale), HbA1c
---
*Salvat: 2026-07-04*

View File

@@ -0,0 +1,62 @@
# Hiking Food: 250g Protein Secret for Endurance
**Sursa:** https://youtu.be/-cKnhhJsaRs?si=ZFbpiFVGGGZ2tyDt
**Durata:** 19:22
**Data:** 2026-07-04
**Tags:** @health @growth
---
## TL;DR
Un hiker cu 20.000 de mile pe trasee explică sistemul lui de nutriție optimizat pentru endurance: proteina ridicată (1.3-1.5g/kg corp), grăsimi ca sursă primară de energie, carbohidrați naturali doar ca supliment în momentele de efort intens. Mâncare reală, neprocesată, puțini ingredienți. Plus suplimente esențiale: creatină, omega-3, magneziu, teanină pentru somn.
---
## Puncte cheie
- **Proteina e prioritatea #1** — 1.3-1.5g per livră de greutate corporală la eforturi intense. La 175 lbs = ~250g proteină/zi. Lipsa proteinei duce la catabolism muscular, oboseală și dureri.
- **Grăsimile = combustibil de durata lungă** — Zone 1-2 de frecvența cardiacă (sub 150 bpm) = ardere predominant de grăsimi. Fat-adapted = poți merge ore fără a mânca, corpul folosește rezervele proprii.
- **Carbohidrații = supliment, nu aliment de bază** — Zahăr natural (curmale, miere, fructe) din 90 în 90 calorii pe oră, nu bulk. Prea mult deodată → insulina → oboseală. RX Bars (5 ingrediente: curmale, albuș, migdale, caju, fructe) > geluri procesate.
- **Mâncarea de pe traseu a lui:**
- Dimineața: pui/vită freeze-dried (2 cups reconstituit) + miere (12g zahăr natural) + bone broth + 1oz ulei de măsline
- Ziua: fistic (1800 cal/zi), beef jerky zero zahăr (3oz), 4-5 RX Bars (tăiate în jumătate, 1 jumate/oră)
- Seara: același meniu ca dimineața
- **Suplimente zilnice:**
- Dimineața: greens powder (vitamine/minerale/probiotice) + creatina monohidrat 10g + vitamina D3 10.000UI + K2
- Ziua: omega-3 3g + cofeina 100mg de 3x (300mg total)
- Seara: teanina + magneziu 1500mg (recuperare și frecventa cardiacă)
- **Zone de antrenament:** 90% zone 1-2, 1x/săptămână V2max (sprinturi în urcare). Zone 3 = "zona gri" — nu antrenează nici endurance nici putere.
- **Fat adaptation** — Keto/carnivore 1.5 ani → corp adaptat să ardă grăsimi primul. Fructele au revenit ca sursă de carbohidrați. Body fat 14% → 7-9%.
- **App recomandat:** Cronometer — pentru calculat macros
- **Carti recomandate:** "Training for the Uphill Athlete" și "The PE Diet"
---
## Citate
> "Most hikers are eating low protein, high carb, poisonous, pre-made meals with tons of ingredients you can't pronounce."
> "Carbs are not required, but they can be very useful when used as a supplement for different activities."
> "If you're finishing hiking days off sore, stiff, and exhausted, there's a simple fix."
> "Your body is going to want to use fats as a great energy source for long endurance."
---
## Idei acționabile
- [ ] Dacă faci drumetii/sport de anduranta: prioritizeaza proteina peste carbohidrati
- [ ] Creatina monohidrat — simpla, ieftina, eficienta pentru creier + muschi + energie
- [ ] Magneziu + teanina seara = somn mai bun dupa efort fizic
- [ ] Mancare reala > mese procesate pre-ambalate (citeste ingredientele)
- [ ] Principiu aplicabil si non-hiking: carbohidratii naturali ca supliment, nu baza

View File

@@ -0,0 +1,96 @@
# Multi-Millionaire: Cele mai bune side hustles pentru 10x Income
**Sursa:** https://youtu.be/0K5a1ql3Oc0?si=Uqfps8H9NZr12oQL
**Durata:** 21:49
**Invitat:** Chris Koerner — multi-milionar antreprenor, RV parks, e-commerce, ~$3-4M/an venit
**Data:** 2026-07-04
**Tags:** @growth @business @money
---
## TL;DR
Chris Koerner explică cum pornești o afacere cu $1k-$5k și ce principii l-au dus la succes cu 35+ proprietăți RV și multiple business-uri. Esența: alege unsexy, caută unfair advantage personal, evită "mirage opportunities", găsește operatori buni și plătești-i din profit (nu equity).
---
## Idei cu $1.000
### Wedding arch rental
- Construiești un arc din lemn ($300 materiale) → îl închiriezi la nunți pentru ~$1.000/eveniment
- NU mergi direct la mirese — mergi la **wedding planners** (suprafață mare, ating zeci de nunți lunar)
- Găsești planners pe: The Knot, Zola, WeddingWire — scrapezi emailuri și telefoane, toate publice
- 1 contact = potențial 1 nuntă/săptămână
- Alte idei similare: photo walls, charcuterie carts, custom pizza, aranjamente funerale
### Email newsletter local
- Facebook/Instagram ad: "Afla ce se întâmplă în [Oraș], un email/săptămână"
- Cost achiziție subscriber: ~$0.50-$1
- Cu $1.000 → 1.000 subscribers
- Monetizare: vânzare publicitate la $500/trimitere (= $500/lună din 1.000 sub-uri)
- ROI în 2 luni; scale prin copy-paste în alte orașe
- **Scrie personal, NU cu ChatGPT** — trebuie să sune ca un local autentic
- 10% din subscribers sunt business owners = potențiali sponsori chiar în lista ta
---
## Idei cu $5.000
### RV Parks (micro, 3-10 locuri)
- De 3-10x mai profitabile decât case de închiriat, la același preț de cumpărare
- Risc distribuit pe mai mulți chiriași vs. un singur chiriaș
- **Cum găsești:** Google Maps, suni 100 parcuri, 2-3 sunt deal-uri bune
- **Cum cumperi:** seller financing — vânzătorul "ține nota", tu plătești din profit lunar
- Pitch: "Dacă nu funcționează, îți iei parcul înapoi + tot ce am plătit până acum"
- $2.000 pentru due diligence (inspecții + title policy)
- $3.000 capital operațional (curent, curățenie, lansare)
- Valorizare: cap rate = profit/preț. Cumperi la 10% cap rate → crești ocupancy → valoarea urcă la 8% cap rate → parc de $200k ajunge $360k
---
## Principii de business
### Unfair advantage
> "I will only start businesses today where I have an unfair advantage."
- Fiecare are un avantaj specific: cunoștințe, rețea, audiență, experiență de nișă
- Nu e rezervat celor bogați sau educați — un teenager poate ști Roblox mai bine decât oricine
- Prioritizezi ideile unde ai un "right to win"
### Mirage opportunity
> "An industry that never seems to work for anybody but seems so obvious."
- Evitare industrii unde toată lumea încearcă dar nimeni nu reușește
- Exemple: grup apps (WhatsApp replacement), password managers, campus events apps, tools pentru podcasters (piață care renunță 95% în 6 luni)
- Dacă nu înțelegi DE CE nu funcționează → stai departe
### Fishing analogy — testezi mult
- Antreprenoriatul = un ocean imens; cei mai mulți aruncă de câteva ori și se întorc acasă
- Secretul: testezi mai mult, nu mai adânc pe o singură idee
- Fiecare test mic (Facebook ads) îți dă semnale. Urmărești semnalele.
### Unsexy = mai puțin competitiv
> "Everyone wants sexy means sexy is the most competitive."
- RV parks, wedding arches, email newsletters = nimeni nu se uită la ele
- Oportunitate tocmai pentru că nu e atrăgătoare
### Operatori buni > equity
- Succesul vine din a găsi oameni buni care să conducă business-ul
- **Equity e supraestimată** — 90% din equity merge la zero
- Plătești operatorul din cash flow (trimestrial) → se simte antreprenor, e motivat real
- A greșit în 20s: promitea equity, era sincer, dar e de puțin folos dacă business-ul nu face exit
---
## Cifre Chris Koerner
- Net profit RV parks + mobile home parks: $400k-$600k total în 7 ani
- Revenue: $3-4M/an
- 35+ proprietăți implicate
---
## Citate
> "Equity is overrated. I put a lot more emphasis on cash flow now in my 30s."
> "I love the framework: 10,000 baby boomers are retiring every single day — buy their businesses or implement AI into them."
> "The unsexy things are the most undercompeted."

View File

@@ -0,0 +1,67 @@
# Fine-Tune the biggest open-source models (even with a bad PC)
**URL:** https://youtu.be/kxstlfc8Lw4
**Durata:** 29:08
**Autor:** David Andre
**Data salvat:** 2026-07-08
**Tags:** @work @growth @ai @llm @fine-tuning
---
## TL;DR
Tutorial complet despre cum se fine-tunează modele AI masive (ex: Kimi K2.7 — 1 trilion parametri) pe date proprii, fără hardware de sute de mii de dolari, folosind platforma **Fireworks AI** și tehnica **LoRA**. Cost real pentru un fine-tune: ~$131.
---
## Puncte cheie
- **Kimi K2.7** este comparat cu Opus 4 și GPT-5, dar de 6-8x mai ieftin — unul dintre cele mai bune modele open-source acum
- **3 ingrediente** pentru fine-tuning: un model open-source, GPU-uri (prin platformă), un dataset de calitate
- **LoRA (Low Rank Adaptation)** = tehnica principă: nu antrenezi toți parametrii (imposibil), ci un "adapter" mic peste modelul înghețat — mult mai ieftin și rapid
- **HuggingFace** = sursa principală pentru datasets; recomandă dataset-uri generate de modele mai mari (ex: Fable) dacă fine-tunezi un model mare
- **Fireworks AI** = platforma folosită pentru training și deployment — interfață simplă, suportă LoRA pe Kimi K2.7, cu GPU-uri Nvidia B300 Blackwell (288 GB VRAM)
- **Costul real**: fine-tune pe dataset Fable 5 (~4600 rânduri, 1 epocă) = **$131**; un dataset mai mare a costat $800
- **Format dataset**: JSONL în formatul specific Fireworks; autorul oferă un "skill" gratuit pentru conversia oricărui dataset HuggingFace la formatul corect
- **Agenți AI în workflow**: David folosește Pi Agent, Codex, Claude Code pentru a automatiza pașii: download dataset, conversie format, verificare, wiring backend
- **Comparație fine-tuned vs default**: versiunea fine-tunată pe Fable afișează chain-of-thought (gândire vizibilă), răspunsuri mai bine formatate — deoarece Fable include thinking traces în dataset
- **Deployment simplu pe Fireworks**: alegi modelul custom, region global, GPU-uri B300, autoscaling opțional — gata să servești inferențe
---
## Pași practici (flux)
1. Alege model open-source (ex: Kimi K2.7 code)
2. Alege dataset de pe HuggingFace (de preferință de la model mai mare decât cel pe care îl fine-tunezi)
3. Descarcă dataset cu HuggingFace CLI (`huggingface-cli download ...`) — necesită token HF
4. Convertește dataset în format JSONL compatibil Fireworks (min 1000 rânduri recomandat)
5. Pe Fireworks AI: Fine-tuning → Supervised → selectezi Kimi K2.7 code LoRA → uploadeazi dataset → start
6. Când e gata (~câteva ore): Deployments → Create → selectezi modelul custom → deploy pe B300
7. Testează comparând răspunsurile față de modelul default
---
## Quote-uri relevante
> "If you want to fine-tune powerful models such as the ones with one trillion parameters, you'll need a lot of cutting-edge GPUs... each of these GPUs is like $40,000 and you can even buy eight at minimum." — de aceea folosești o platformă cloud
> "High-quality data is one of the most important factors. If you have a shitty dataset, you're going to have a shitty model."
> "A lot of you are wasting money on running everything on Opus or GPT-5, while you could get much better usage if you had the most powerful model for the planning and then the smaller open-source models that are faster."
---
## Idei acționabile pentru Marius
- [ ] Kimi K2.7 merită testat ca alternativă la Claude pentru task-uri repetitive (cost 7x mai mic) @work
- [ ] Fireworks AI = opțiune concretă dacă vrei să fine-tunezi un model pe date ROA/ERP (ex: răspunsuri standardizate pentru chatbot Maria) @work
- [ ] Dataset-ul pentru fine-tune chatbot Maria s-ar putea construi din întrebările frecvente ale clienților — deja ai sursa @work
---
## Surse menționate
- Fireworks AI: https://fireworks.ai
- HuggingFace datasets: https://huggingface.co/datasets
- Kimi K2.7 API: (primul link din video)
- Skill conversie dataset: (al doilea link din video — davidonj.com/skills)

View File

@@ -0,0 +1,48 @@
# I Cut My OpenCode Token Usage by 96% - Here's How
**URL:** https://youtu.be/FX7jcd3GYtI
**Durata:** 9:42
**Data salvat:** 2026-07-10
**Tags:** @work @growth @ai @agents @llm
---
## TL;DR
Autorul folosește un proxy man-in-the-middle pentru a vedea exact ce trimite OpenCode (agent de coding AI) către LLM la un simplu "hello" — și găsește 8.000 de tokeni: system prompt de 9.500 caractere + 11 definiții de tool-uri (bash, task, edit, read etc.) trimise la FIECARE request, plus un al doilea apel LLM ascuns doar pentru titlul conversației. Soluția: creează un agent custom minimal (fără tools, system prompt scurt) → 300-500 tokeni, reducere ~90-96%. Recomandare: pornește minimal, adaugă tools doar când chiar ai nevoie de ele.
---
## Puncte cheie
- **8.000 tokeni pentru "hello"** — 0,025% e mesajul propriu-zis, restul e scaffolding (system prompt + tool definitions)
- OpenCode face **2 apeluri LLM** per mesaj: unul ascuns doar ca să genereze titlul conversației (~2.000 tokeni, pt un titlu de 1 cuvânt)
- System prompt default: ~114 linii, spune LLM-ului tot ce poate/nu poate face, format, emoji, etc.
- 11 tool definitions trimise mereu — bash tool singur = 4.700 caractere, task tool = 3.000 caractere
- **Cache-ul providerului NU te ajută pe tine** — reduce costul $ pentru provider, dar tot trimiți full payload pe rețea, tot consumi context window și latență
- Soluție: agenți custom minimali (folder `.opencode/agents/*.md`) cu system prompt scurt și zero/puține tools, setați ca `primary` — switch cu Tab între agent complet (build) și agent lean
- Trade-off: agentul lean nu are task/sub-agents, web fetch, etc. — dar poți adăuga selectiv doar ce ai nevoie
- Filosofie: **start minimal, expand when necessary** — nu monta toate tool-urile/MCP-urile by default
---
## Quote-uri relevante
> "8,000 tokens. That's what it cost me to say hello to an AI coding agent."
> "Cached tokens are a cost optimization for the provider, not you. For you, it's still 8,000 tokens of latency and context window usage."
> "Start minimal and expand when necessary."
---
## Idei acționabile pentru Marius
- [ ] Relevant pentru Claude Code / echo-core: verifică dacă subagenții custom (`Agent` tool) pot fi definiți cu scope minim (fără tool-uri inutile) pentru task-uri simple, ca să reduci tokeni pe apelurile dese (heartbeat, routine checks) @work
- [ ] Ideea de "agent lean vs agent complet, switch la nevoie" se mapează pe strategia ta Haiku/Sonnet din AGENTS.md — poate merită un al treilea nivel: agent minimal fără tools pentru întrebări simple @growth
---
## Surse menționate
- Repo GitHub cu scriptul proxy + config agent (menționat, link nu apare explicit în transcript)

View File

@@ -1,6 +1,6 @@
# Index — youtube/
> 151 note. Citește acest index întâi; deschide doar fișierele relevante.
> 159 note. Citește acest index întâi; deschide doar fișierele relevante.
- **[Talk to Claude on 3CX Phone System Tutorial (Full Setup)](2025-02-13_talk-to-claude-3cx-phone.md)** `@work`
Tutorial complet pentru a vorbi cu Claude Code prin telefon, folosind 3CX (sistem telefonic cloud gratuit) + un proiect GitHub custom. Setup
@@ -298,9 +298,25 @@
Tony Robbins (neidentificat explicit, dar stilul și conținutul sunt clare) explică de ce oamenii eșuează să aibă un breakthrough: atacă prob
- **[Google Just Dropped a Masterclass on Agentic Engineering](2026-06-25_google-agentic-engineering-masterclass.md)** `@work @growth`
Google a publicat un ghid de 51 de pagini despre AI-driven SDLC (Software Development Life Cycle). Concluzia centrală: **harness-ul (regulil
- **[Why I'm Buying UNWANTED Land (And You Should Too) | Cody Sanchez](2026-06-27_cody-sanchez-unwanted-land.md)**
Cody Sanchez cumpara 10 acri in West Texas pentru sub $10,000 in mai putin de o saptamana — ca experiment sa vada daca visul american e inca
- **[Discipline Changes Everything | Novak Djokovic](2026-06-27_djokovic-discipline-changes-everything.md)** `@growth @health`
Djokovic vorbeste despre transformarea sa prin disciplina, nutritie si mentalitate. Punctul central: limitele sunt constructe mentale, nu re
- **[Google's New Release Just Fixed AI Systems (Open Knowledge Format)](2026-06-27_google-open-knowledge-format.md)** `@work @growth`
Un video prezintă **Open Knowledge Format (OKF)** — un format *propus* pentru organizarea knowledge base-urilor astfel încât agenții AI să n
- **[Billionaire's WARNING: I'm SELLING. The Crash Is Already Here! — Jeremy Grantham](2026-06-27_jeremy-grantham-ai-bubble-warning.md)** `@growth @work #youtube #to-summarize #investitii #bubble #AI #sanatate #economie`
*Notă: Sumarizarea va fi adăugată de Echo.*
Jeremy Grantham — 87 ani, 60 ani experiență, 165 miliarde $ gestionați — avertizează că suntem în cea mai mare bulă investițională din istor
- **[Watch me launch a million dollar startup with AI (live)](2026-06-29_launch-million-dollar-startup-ai.md)** `@work @growth #antreprenoriat #ai-marketing #validare #startup`
Un playbook de 9 prompts copy-paste care duce de la idee la landing page live cu reclame Meta, în sub 30 de minute. Cheia: "vibe marketing"
- **[Anti-Aging Expert: This Reverses Gray Hairs, Restores Your Youth & Boosts Your Energy!](2026-07-04_anti-aging-mitochondria-martin-picard.md)** `@health @growth`
Dr. Martin Picard explică de ce mitochondriile — nu genele — determina cat de repede imbatranesti. Secretul anti-aging nu e o suplimentare s
- **[Hiking Food: 250g Protein Secret for Endurance](2026-07-04_hiking-food-250g-protein-endurance.md)** `@health @growth`
Un hiker cu 20.000 de mile pe trasee explică sistemul lui de nutriție optimizat pentru endurance: proteina ridicată (1.3-1.5g/kg corp), grăs
- **[Multi-Millionaire: Cele mai bune side hustles pentru 10x Income](2026-07-04_multi-millionaire-10x-income-side-hustles.md)** `@growth`
Chris Koerner explică cum pornești o afacere cu $1k-$5k și ce principii l-au dus la succes cu 35+ proprietăți RV și multiple business-uri. E
- **[Fine-Tune the biggest open-source models (even with a bad PC)](2026-07-08_fine-tune-modele-mari-kimi-k27.md)** `@work @growth`
Tutorial complet despre cum se fine-tunează modele AI masive (ex: Kimi K2.7 — 1 trilion parametri) pe date proprii, fără hardware de sute de
- **[I Cut My OpenCode Token Usage by 96% - Here's How](2026-07-10_opencode-token-usage-96-percent.md)** `@work @growth`
Autorul folosește un proxy man-in-the-middle pentru a vedea exact ce trimite OpenCode (agent de coding AI) către LLM la un simplu "hello" —
- **[thinking-on-paper](thinking-on-paper.md)** `@growth`
Metoda "Thinking on Paper" — 3 principii: **Make it Wrong** (scrie repede, fără perfecționism), **Make it Shorter** (doar keywords), **Make

View File

@@ -787,6 +787,11 @@ def cmd_audio(args: list[str]) -> str:
if len(text) > _MAX_TTS_CHARS:
text = text[:_MAX_TTS_CHARS] + "..."
# Normalizare RO pentru TTS (numere, monedă, unități, abrevieri) —
# aceeași expandare ca voice mode live, fără trunchierea la 200 cuvinte
from src.voice.normalize import expand_for_tts
text = expand_for_tts(text)
# Apel TTS (import direct din tools/tts.py)
result = _tts_synthesize(text, voice)
if result.get("ok"):

View File

@@ -83,27 +83,6 @@ def main():
scheduler = Scheduler(send_callback=_send_to_channel, config=config)
client.scheduler = scheduler # type: ignore[attr-defined]
# Newsletter Cercetași checker (optional)
newsletter_config = config.get("newsletter_cercetasi", {})
if newsletter_config.get("enabled"):
from src.newsletter_cercetasi import check_and_send as check_newsletter
from apscheduler.triggers.cron import CronTrigger as _CronTrigger
async def _newsletter_tick() -> None:
try:
await check_newsletter(config, _send_to_channel)
except Exception as exc:
logger.error("Newsletter checker failed: %s", exc)
nl_cron = newsletter_config.get("cron", "0 9 * * *")
scheduler._scheduler.add_job(
_newsletter_tick,
trigger=_CronTrigger.from_crontab(nl_cron),
id="__newsletter_cercetasi__",
max_instances=1,
)
logger.info("Newsletter Cercetasi checker registered (cron: %s)", nl_cron)
# Telegram bot (optional — only if telegram_token exists)
telegram_token = get_secret("telegram_token")
telegram_app = None

View File

@@ -1,155 +0,0 @@
"""Newsletter Cercetași checker — detects new editions and sends WhatsApp summaries."""
import asyncio
import json
import logging
import subprocess
from datetime import datetime, timezone
from pathlib import Path
from typing import Awaitable, Callable
import httpx
from src.claude_session import CLAUDE_BIN, PROJECT_ROOT, _safe_env, build_system_prompt
log = logging.getLogger("echo-core.newsletter-cercetasi")
NEWSLETTER_BASE_URL = "https://cercetaiis-newsletter.beehiiv.com/p/newsletter-{n}-din-{year}"
STATE_FILE = PROJECT_ROOT / "cron" / "newsletter-cercetasi-state.json"
KB_PROMPT_FILE = (
PROJECT_ROOT / "memory" / "kb" / "projects" / "grup-sprijin" / "prompt-newsletter-cercetasi.md"
)
CLAUDE_TIMEOUT = 300
def _read_state() -> dict:
try:
return json.loads(STATE_FILE.read_text())
except (FileNotFoundError, json.JSONDecodeError):
return {"last_sent": 0, "year": datetime.now(timezone.utc).year}
def _write_state(state: dict) -> None:
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n")
async def _newsletter_exists(n: int, year: int) -> bool:
"""Return True if newsletter #{n}/{year} exists (HTTP 200, no redirect to 404 page).
Beehiiv redirects non-existent newsletters: /p/newsletter-N-din-YEAR → /?404=... (302)
A real newsletter returns 200 directly without redirect.
"""
url = NEWSLETTER_BASE_URL.format(n=n, year=year)
try:
async with httpx.AsyncClient(follow_redirects=False) as client:
resp = await client.get(url, timeout=10)
return resp.status_code == 200
except Exception as e:
log.debug("Newsletter #%d/%d check failed: %s", n, year, e)
return False
def _generate_summary(n: int, year: int) -> str | None:
"""Run Claude CLI to generate summary for newsletter #{n}/{year}. Returns text or None."""
url = NEWSLETTER_BASE_URL.format(n=n, year=year)
try:
kb_prompt = KB_PROMPT_FILE.read_text()
except FileNotFoundError:
log.error("KB prompt file not found: %s", KB_PROMPT_FILE)
return None
prompt = (
f"Newsletter-ul Cercetașilor #{n}/{year} este disponibil la: {url}\n\n"
f"Urmează instrucțiunile de mai jos pentru a genera rezumatul:\n\n"
f"{kb_prompt}"
)
cmd = [
CLAUDE_BIN, "-p", prompt,
"--model", "sonnet",
"--output-format", "json",
"--allowedTools", "WebFetch",
]
try:
system_prompt = build_system_prompt()
cmd += ["--system-prompt", system_prompt]
except FileNotFoundError:
pass
try:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=CLAUDE_TIMEOUT,
env=_safe_env(),
cwd=PROJECT_ROOT,
)
if proc.returncode != 0:
log.error("Claude CLI error (exit %d): %s", proc.returncode, proc.stderr[:300])
return None
data = json.loads(proc.stdout)
return data.get("result", "").strip() or None
except subprocess.TimeoutExpired:
log.error("Claude CLI timed out for newsletter #%d", n)
return None
except (json.JSONDecodeError, Exception) as e:
log.error("Failed to generate newsletter summary: %s", e)
return None
async def check_and_send(config, send_callback) -> None:
"""Check for new newsletter; if found, generate summary and send via callback."""
state = _read_state()
current_year = datetime.now(timezone.utc).year
# New year → reset counter
if state.get("year", current_year) != current_year:
log.info("New year detected (%d%d), resetting newsletter counter", state["year"], current_year)
state = {"last_sent": 0, "year": current_year}
next_n = state["last_sent"] + 1
log.info("Checking for Cercetasi newsletter #%d/%d...", next_n, current_year)
if not await _newsletter_exists(next_n, current_year):
log.info("Newsletter #%d/%d not yet available", next_n, current_year)
return
log.info("Newsletter #%d/%d found! Generating summary...", next_n, current_year)
summary = await asyncio.to_thread(_generate_summary, next_n, current_year)
if not summary:
log.error("Failed to generate summary for newsletter #%d/%d", next_n, current_year)
return
channel = config.get("newsletter_cercetasi.channel", "echo-core")
sent = False
try:
await send_callback(channel, summary)
sent = True
log.info("Newsletter #%d/%d summary sent to channel '%s'", next_n, current_year, channel)
except Exception as e:
log.error("Send failed for newsletter #%d/%d — will retry next check: %s", next_n, current_year, e)
# Trimite și pe WhatsApp dacă e configurat
owner = config.get("whatsapp.owner", "")
if owner and sent:
owner_jid = f"{owner}@s.whatsapp.net"
bridge_url = config.get("whatsapp.bridge_url", "http://127.0.0.1:8098")
try:
async with httpx.AsyncClient() as client:
for chunk in [summary[i:i+4096] for i in range(0, len(summary), 4096)]:
await client.post(f"{bridge_url}/send", json={"to": owner_jid, "text": chunk}, timeout=15)
log.info("Newsletter #%d/%d summary sent to WhatsApp", next_n, current_year)
except Exception as e:
log.error("WhatsApp send failed for newsletter #%d/%d: %s", next_n, current_year, e)
if sent:
state["last_sent"] = next_n
state["year"] = current_year
state["last_sent_at"] = datetime.now(timezone.utc).isoformat()
_write_state(state)

View File

@@ -201,8 +201,12 @@ _CURRENCY_SUB = {
}
_CURRENCY_PATTERNS = [
# RON suffix (case-insensitive: RON, ron, lei)
(re.compile(r'(?<!\w)(\d+(?:\.\d+)?)\s+(?:RON|lei)\b', re.IGNORECASE), 'RON'),
# RON suffix (case-insensitive: RON, ron, lei). Accepts either a
# dot-decimal fraction ("12.50 lei") or, since Romanian accounting
# format uses comma for decimals ("53.600,00 lei" — the dot-thousands
# group is already collapsed by normalize_thousands upstream), a
# comma-decimal fraction in its own capture group.
(re.compile(r'(?<!\w)(\d+(?:\.\d+)?)(?:,(\d{1,2}))?\s+(?:RON|lei)\b', re.IGNORECASE), 'RON'),
# Prefix currencies
(re.compile(r'\$(\d+(?:\.\d+)?)'), 'USD'),
(re.compile(r'€(\d+(?:\.\d+)?)'), 'EUR'),
@@ -222,25 +226,28 @@ def _format_currency_unit(n: int, singular: str, plural: str) -> str:
return f"{word} {plural}"
def _format_currency(amount: str, code: str) -> str:
def _format_currency(amount: str, code: str, comma_frac: str | None = None) -> str:
main_sg, main_pl = _CURRENCY_MAIN[code]
if '.' in amount:
if comma_frac is not None:
whole_s, frac_s = amount, comma_frac
elif '.' in amount:
whole_s, frac_s = amount.split('.', 1)
# Normalize fractional part to 2 digits so "12.5 RON" reads as
# 50 bani, not 5 bani.
if len(frac_s) == 1:
frac_s = frac_s + '0'
elif len(frac_s) > 2:
frac_s = frac_s[:2]
whole = int(whole_s)
frac = int(frac_s)
whole_part = _format_currency_unit(whole, main_sg, main_pl)
if frac == 0:
return whole_part
sub_sg, sub_pl = _CURRENCY_SUB[code]
frac_part = _format_currency_unit(frac, sub_sg, sub_pl)
return f"{whole_part} și {frac_part}"
return _format_currency_unit(int(amount), main_sg, main_pl)
else:
return _format_currency_unit(int(amount), main_sg, main_pl)
# Normalize fractional part to 2 digits so "12.5 RON" reads as
# 50 bani, not 5 bani.
if len(frac_s) == 1:
frac_s = frac_s + '0'
elif len(frac_s) > 2:
frac_s = frac_s[:2]
whole = int(whole_s)
frac = int(frac_s)
whole_part = _format_currency_unit(whole, main_sg, main_pl)
if frac == 0:
return whole_part
sub_sg, sub_pl = _CURRENCY_SUB[code]
frac_part = _format_currency_unit(frac, sub_sg, sub_pl)
return f"{whole_part} și {frac_part}"
def expand_currency(text: str) -> str:
@@ -251,7 +258,10 @@ def expand_currency(text: str) -> str:
bani / cenți / pence).
"""
for pattern, code in _CURRENCY_PATTERNS:
text = pattern.sub(lambda m, c=code: _format_currency(m.group(1), c), text)
def _sub(m, c=code):
comma_frac = m.group(2) if m.re.groups >= 2 else None
return _format_currency(m.group(1), c, comma_frac)
text = pattern.sub(_sub, text)
return text
@@ -296,12 +306,13 @@ _MAX_WORDS = 200
_TRUNCATE_SUFFIX = "Restul l-am scris în chat."
def normalize_for_tts(text: str) -> str:
"""Apply the full normalization pipeline and truncate to 200 words.
def expand_for_tts(text: str) -> str:
"""Apply the full normalization pipeline (markdown strip, abbreviations,
numbers, currency, units, symbols) WITHOUT the 200-word truncation.
If the text exceeds 200 words, the first 200 are kept and the suffix
"Restul l-am scris în chat." is appended so the listener knows the
response continues in the text channel mirror.
Use this for one-shot TTS generation (e.g. /audio command) where the
"Restul l-am scris în chat." suffix from normalize_for_tts() would be
misleading (no live chat mirror exists for that flow).
"""
text = strip_markdown(text)
text = sanitize_punctuation(text)
@@ -312,6 +323,17 @@ def normalize_for_tts(text: str) -> str:
text = expand_units(text)
text = expand_numbers_ro(text)
text = expand_symbols(text)
return text.strip()
def normalize_for_tts(text: str) -> str:
"""Apply the full normalization pipeline and truncate to 200 words.
If the text exceeds 200 words, the first 200 are kept and the suffix
"Restul l-am scris în chat." is appended so the listener knows the
response continues in the text channel mirror.
"""
text = expand_for_tts(text)
words = text.split()
if len(words) > _MAX_WORDS:
text = ' '.join(words[:_MAX_WORDS]) + f" {_TRUNCATE_SUFFIX}"

View File

@@ -49,6 +49,14 @@ VAD_WINDOW_BYTES = PACKET_BYTES * (VAD_WINDOW_MS // PACKET_MS)
VAD_THRESHOLD = 0.5
SILENCE_FLUSH_MS = 800
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
LOGS_DIR = PROJECT_ROOT / "logs"
@@ -83,19 +91,28 @@ _silero_lock = threading.Lock()
def _get_whisper_model() -> Any:
"""Lazy-load faster-whisper ``small`` int8 with the spike-validated
``cpu_threads=4`` (see ``tasks/voice-bench-results.md``)."""
"""Lazy-load faster-whisper int8 with the spike-validated ``cpu_threads=4``
(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
if _whisper_model is not None:
return _whisper_model
with _whisper_lock:
if _whisper_model is not None:
return _whisper_model
from src.config import Config
model_id = Config().get("voice.stt_model", "small") or "small"
from faster_whisper import 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,
)
log.info("STT model loaded: %s", model_id)
return _whisper_model
@@ -145,6 +162,38 @@ def _pcm48_stereo_to_16_mono(pcm: bytes) -> np.ndarray:
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 ----------
class VoiceSession:
@@ -679,6 +728,7 @@ class EchoVoiceSink(AudioSink):
model = _get_whisper_model()
segments, _info = model.transcribe(
mono16, language="ro", beam_size=5,
temperature=0.0, # no fallback ladder — reject bad segments instead (see thresholds above)
initial_prompt=(
"Conversatie in romana cu asistentul Eco (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, "
"treci pe vocea F1."
),
hotwords="Eco Echo Core Marius Bianca",
hotwords="Eco Echo Core Marius Bianca Bitcoin",
condition_on_previous_text=False,
)
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
seg_text = (getattr(seg, "text", "") or "").strip()
if seg_text:
text_parts.append(seg_text)
text_parts, worst_no_speech = _filter_segments(segments)
if not text_parts:
return
text = " ".join(text_parts).strip()

View File

@@ -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.
**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.
## 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`.

View 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.
```

View File

@@ -78,6 +78,28 @@ def test_expand_currency(text, expected):
assert expand_currency(text) == expected
# ============================================================
# Currency — Romanian accounting format (comma decimal, dot
# thousands already collapsed upstream by normalize_thousands)
# ============================================================
@pytest.mark.parametrize("text,expected", [
("53600,00 lei", "cincizeci și trei de mii șase sute de lei"),
("17600,50 lei", "șaptesprezece mii șase sute de lei și cincizeci de bani"),
("100,00 lei", "o sută de lei"),
("17600,05 lei", "șaptesprezece mii șase sute de lei și cinci bani"),
])
def test_expand_currency_comma_decimal(text, expected):
assert expand_currency(text) == expected
@pytest.mark.parametrize("text,expected", [
("53.600,00 lei", "cincizeci și trei de mii șase sute de lei"),
("17.600,50 lei", "șaptesprezece mii șase sute de lei și cincizeci de bani"),
])
def test_normalize_for_tts_currency_ro_accounting_format(text, expected):
assert normalize_for_tts(text) == expected
# ============================================================
# Symbols (4)
# ============================================================

View 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"]

View 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"}]) == []

View File

@@ -1,14 +1,14 @@
{
"D100": "27cf97a4d10c8529669d95b2d96ca3c9b41f7e4e50091dce19cf8af117f0ac4a",
"D100": "db8fc40765fd2e3b5fc1f0f900e160e6c45f625f093cf1f3bc17ea3a40d88380",
"D101": "f72fc1c29657ea11e0238806a28f6abccf5b00e45904e1e0c9385cc64491fcaf",
"D300": "cb7b55b568ab893024884971eac0367fb6fe487c297e355d64258dae437f6ddd",
"D394": "c4c4e62bda30032f12c17edf9a5087b6173a350ccb1fd750158978b3bd0acb7d",
"D406": "ca6103448d663ab16fcaef0f29f8933ef526cbf5aad12c7ff5dbd61b22ca9fc6",
"SIT_FIN_SEM_2025": "8164843431e6b703a38fbdedc7898ec6ae83559fe10f88663ba0b55f3091d5fe",
"SIT_FIN_AN_2025": "accceef5b6585a3e901d83d23fc2e60f6562eac4a2ce00f943856232bed929d6",
"DESCARCARE_DECLARATII": "8cc082021edb0ae97686d73f8179369be33a68ef03ec791757460bb7fff99e34",
"DESCARCARE_DECLARATII": "1abf61068fc38e76963520f455a87635efdcf6c126fbf913d8d860478d3fc5ba",
"D205": "d3c20a7ae70f4c18bbb7add42af035e3746d323b2e6df37a4e31ed625ddb86d9",
"D390": "4726938ed5858ec735caefd947a7d182b6dc64009478332c4feabdb36412a84e",
"D390": "31d0bf3f98eda3556039cc7e186bfcaf71bdb962459a2cf54f4d03d1d57053fc",
"BILANT_2024": "fbb8d66c2e530d8798362992c6983e07e1250188228c758cb6da4cde4f955950",
"BILANT_2025": "115531c0d6f20d84c38ebb6f621d57ecc151128ce115fe5b303160a9e2f4655a"
}

View File

@@ -62,14 +62,14 @@ valabil începand cu
01/2024 - publicat în data de 09.02.2024
soft A
actualizat în data de
29.05.2026
29.06.2026
soft J*
actualizat în data de
25.05.2026
Anexa
validări
actualizat în data de
20.05.2026
17.06.2026
Schema
XSD
100

View File

@@ -16,6 +16,7 @@ publicat în data de
soft A
actualizat în data de
07.05.2024
Formular web
soft J*
actualizat în data de
25.06.2025

View File

@@ -51,7 +51,7 @@ Se transmit prin SPV
F1129
-  Ordinul de plată multiplu electronic (OPME) V.2.0.45 dată
actualizare
25.11.2025
22.06.2026
Formularul se depune on-line prin Sistemul naţional de raportare
FOREXEBUG
de către instituţiile publice şi, respectiv, prin portalul
@@ -273,6 +273,10 @@ D182
,
408
,
409
,
410
,
700
,
710,
@@ -393,6 +397,11 @@ Raportări contabile semestriale 2025
18.07.2025
Se transmit prin portalul
e-guvernare.ro
Raportări contabile semestriale 2026
- publicat în data de
08.07.2026
Se transmit prin portalul
e-guvernare.ro
Situaţii financiare anuale/Raportări anuale an 2022
- publicat în data de
20.01.2023
@@ -498,6 +507,11 @@ L153
Se transmit prin
portalul
e-guvernare.ro
Formularul
S007
Se transmit prin
portalul
e-guvernare.ro
C801
- Cerere de atribuire a numărului unic de identificare din aplicaţia
informatică a ANAF - publicat în data de

View File

@@ -1,7 +1,7 @@
{
"D100": {
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_290526.pdf",
"soft_a_date": "29.05.2026",
"soft_a_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_710_XML_0126_290626.pdf",
"soft_a_date": "29.06.2026",
"soft_j_url": "http://static.anaf.ro/static/10/Anaf/Declaratii_R/AplicatiiDec/D100_22052026.zip",
"soft_j_date": "22.05.2026"
},

175
tools/ocr_bon.py Normal file
View 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()

214
tools/roa2web_client.py Normal file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
Client generic pentru API-ul roa2web — autentificare + query-uri rapoarte.
Autentificare: user/parolă Oracle (ca la login-ul normal ROA) + 2FA email la
prima logare. După primul login cu trust_device=True, sesiunile următoare
folosesc refresh_token din keyring — fără OTP.
Credențiale (keyring, service "echo-core", vezi src/credential_store.py):
roa2web_user, roa2web_pass, roa2web_server,
roa2web_access_token, roa2web_refresh_token, roa2web_trusted_device_token
Firma implicită și base_url sunt în config.json -> "roa2web".
Usage:
python3 tools/roa2web_client.py summary [--company COD_SAU_NUME] [--luna N] [--an N]
python3 tools/roa2web_client.py companies
python3 tools/roa2web_client.py get <path> [--company COD_SAU_NUME] [--param k=v ...]
python3 tools/roa2web_client.py verify-2fa <cod> <email>
"""
import argparse
import json
import sys
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit
import requests
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from src.credential_store import get_secret, set_secret
from src.config import Config
class ROA2WebError(Exception):
pass
class ROA2WebClient:
def __init__(self):
self.config = Config()
self.base_url = self.config.get("roa2web.base_url", "https://roa2web.romfast.ro/roa2web/api")
self.session = requests.Session()
self._token = get_secret("roa2web_access_token")
parsed = urlsplit(self.base_url)
self._origin = f"{parsed.scheme}://{parsed.netloc}"
# IIS/ARR reverse-proxy-ul din fata backend-ului serveste totul sub /roa2web,
# dar FastAPI nu stie de acest prefix -> redirect-urile lui (307, ex. trailing
# slash pe rutele definite la "/") vin fara el si pica in 404. Il reinjectam.
self._prefix = parsed.path[: -len("/api")] if parsed.path.endswith("/api") else parsed.path
@property
def default_company(self) -> str:
return self.config.get("roa2web.default_company", "114")
def _store_tokens(self, data: dict) -> None:
set_secret("roa2web_access_token", data["access_token"])
if data.get("refresh_token"):
set_secret("roa2web_refresh_token", data["refresh_token"])
if data.get("trusted_device_token"):
set_secret("roa2web_trusted_device_token", data["trusted_device_token"])
self._token = data["access_token"]
def _login_password(self) -> None:
user = get_secret("roa2web_user")
pw = get_secret("roa2web_pass")
server = get_secret("roa2web_server") or "romfast"
if not user or not pw:
raise ROA2WebError("Lipsesc roa2web_user/roa2web_pass din keyring (eco secrets set ...)")
payload = {"username": user, "password": pw, "server_id": server}
trusted = get_secret("roa2web_trusted_device_token")
if trusted:
payload["trusted_device_token"] = trusted
r = self.session.post(f"{self.base_url}/auth/login", json=payload, timeout=15)
r.raise_for_status()
data = r.json()
if data.get("requires_2fa"):
raise ROA2WebError(
f"Necesar cod OTP nou, trimis pe {data.get('masked_email')}. "
"Cere-i lui Marius codul, apoi ruleaza verify_2fa(code, email)."
)
self._store_tokens(data)
def _refresh(self) -> bool:
refresh_token = get_secret("roa2web_refresh_token")
if not refresh_token:
return False
r = self.session.post(
f"{self.base_url}/auth/refresh",
json={"refresh_token": refresh_token},
timeout=15,
)
if r.status_code != 200:
return False
self._store_tokens(r.json())
return True
def verify_2fa(self, code: str, email: str, server_id: str | None = None) -> dict:
server = server_id or get_secret("roa2web_server") or "romfast"
r = self.session.post(
f"{self.base_url}/auth/verify-2fa-code",
json={"code": code, "email": email, "server_id": server, "trust_device": True},
timeout=15,
)
r.raise_for_status()
data = r.json()
self._store_tokens(data)
return data
def request(self, method: str, path: str, params: dict | None = None, _retried: bool = False) -> dict:
if not self._token and not self._refresh():
self._login_password()
url = f"{self.base_url}{path}" if path.startswith("/") else f"{self.base_url}/{path}"
headers = {"Authorization": f"Bearer {self._token}"}
r = self.session.request(method, url, params=params, headers=headers, timeout=30, allow_redirects=False)
if r.status_code in (307, 308) and "Location" in r.headers:
loc = urlsplit(r.headers["Location"])
loc_path = loc.path
if self._prefix and not loc_path.startswith(self._prefix):
loc_path = self._prefix + loc_path
fixed_url = urlunsplit((urlsplit(self.base_url).scheme, urlsplit(self.base_url).netloc, loc_path, loc.query, ""))
r = self.session.request(method, fixed_url, headers=headers, timeout=30)
if r.status_code == 401 and not _retried:
if self._refresh() or (self._login_password() or True):
return self.request(method, path, params=params, _retried=True)
r.raise_for_status()
return r.json()
def get(self, path: str, params: dict | None = None) -> dict:
return self.request("GET", path, params=params)
def companies(self) -> list:
return self.get("/companies").get("companies", [])
def dashboard_summary(self, company: str | None = None, luna: int | None = None, an: int | None = None) -> dict:
params = {"company": company or self.default_company}
if luna:
params["luna"] = luna
if an:
params["an"] = an
return self.get("/reports/dashboard/summary", params)
def resolve_company(client: ROA2WebClient, value: str | None) -> str:
"""Acceptă cod numeric sau (parte din) numele firmei; implicit firma din config."""
if not value:
return client.default_company
if value.isdigit():
return value
needle = value.lower()
matches = [c for c in client.companies() if needle in c["name"].lower()]
if not matches:
raise ROA2WebError(f"Firma '{value}' nu a fost gasita in lista de firme.")
if len(matches) > 1:
names = ", ".join(f"{c['id_firma']}={c['name']}" for c in matches)
raise ROA2WebError(f"'{value}' e ambiguu, se potriveste cu mai multe firme: {names}")
return str(matches[0]["id_firma"])
def main():
parser = argparse.ArgumentParser(description="Client CLI pentru roa2web API")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("summary", help="Dashboard financiar (trezorerie, clienti, furnizori)")
p.add_argument("--company", help="Cod sau nume firma (implicit: cea din config.json)")
p.add_argument("--luna", type=int)
p.add_argument("--an", type=int)
sub.add_parser("companies", help="Lista firmelor disponibile")
p = sub.add_parser("get", help="Query generic pe orice endpoint /api/...")
p.add_argument("path", help='ex: "/reports/invoices" sau "/reports/treasury/bank-cash-register"')
p.add_argument("--company", help="Daca e dat, se adauga ca param 'company'")
p.add_argument("--param", action="append", default=[], metavar="k=v", help="Parametru query, repetabil")
v = sub.add_parser("verify-2fa", help="Finalizeaza login-ul cu un cod OTP primit pe email")
v.add_argument("code")
v.add_argument("email")
args = parser.parse_args()
client = ROA2WebClient()
try:
if args.cmd == "summary":
company = resolve_company(client, args.company)
data = client.dashboard_summary(company=company, luna=args.luna, an=args.an)
elif args.cmd == "companies":
for c in client.companies():
print(f"{c['id_firma']}\t{c['name']}")
return
elif args.cmd == "get":
params = dict(kv.split("=", 1) for kv in args.param)
if args.company:
params["company"] = resolve_company(client, args.company)
data = client.get(args.path, params or None)
elif args.cmd == "verify-2fa":
raw = client.verify_2fa(args.code, args.email)
data = {k: v for k, v in raw.items() if k not in ("access_token", "refresh_token", "trusted_device_token")}
except ROA2WebError as e:
print(f"Eroare: {e}", file=sys.stderr)
sys.exit(1)
print(json.dumps(data, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()

View File

@@ -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:
"""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():
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

166
tools/voice_stt_mine.py Normal file
View 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
View 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())