11 KiB
Lessons Learned
Lecții capturate din corectările lui Marius. Citește acest fișier la începutul oricărei sesiuni de cod (înainte de plan mode) și aplică lecțiile relevante. Iterează neobosit pentru a evita rate drop-uri pe greșeli repetate.
Format per lecție:
## <titlu scurt>
**Data:** YYYY-MM-DD
**Context:** ce făceam când a apărut corectarea
**Greșeala:** ce am făcut greșit
**Regula:** ce să fac în schimb, în viitor
**Când se aplică:** trigger-uri concrete (fișiere, task-uri, situații)
Transliterează și numele proprii pe turnuri [tts-lang:en] — un singur diacritic declanșează fallback pe Supertonic
Data: 2026-07-11
Context: Marius a întrebat vremea la Constanța (turn de voce, vocea activă = pocket-tts, marker [tts-lang:en]). Am scris răspunsul în engleză dar am păstrat "Constanța" cu ț original. Rezultatul audio a venit cu o voce Supertonic, nu cu Marius 4 (pocket-tts) cum se aștepta Marius.
Greșeala: Am presupus greșit că "engleză" înseamnă doar propoziții englezești — am lăsat un nume propriu (Constanța) netranslit. tools/tts.py::looks_romanian() (folosit în src/voice/tts_stream.py:213) verifică prezența oricărui caracter din _RO_DIACRITICS (ă/â/î/ș/ț), indiferent de context — UN SINGUR caracter e suficient. Codul a funcționat corect conform design-ului: a detectat diacriticul, a presupus că tot blocul e română netradusă, și a căzut intenționat pe Supertonic (mai bine o voce RO decât tăcere totală) — vezi comentariul din tts_stream.py:213-222. Deci nu a fost bug de cod, ci scriere incompletă din partea mea.
Regula: Pe orice turn cu marker [tts-lang:en], transliterează ȘI numele proprii/toponimele (Constanța → Constanta, Brașov → Brasov, etc.) — nu doar propozițiile. Verifică explicit că răspunsul nu conține NICIUN caracter din ăâîșțĂÂÎȘȚşţŞŢ înainte de a trimite, altfel guard-ul din tts_stream.py face fallback silențios pe Supertonic și vocea aleasă de Marius (pockettts) nu se aude deloc.
Când se aplică: Orice răspuns pe un turn de voce cu vocea activă pockettts (Marius 1-4, Paula 1-3, alba) și marker [tts-lang:en] în prefix. Verifică inclusiv toponime, nume, abrevieri românești din text.
Intră în plan mode ÎNAINTE de a executa orice modificare de cod
Data: 2026-05-28
Context: Marius a descris o cerință de îmbunătățire a comenzii /audio cu URL (chunk by chunk). Am implementat direct fără plan mode.
Greșeala: Am sărit peste pasul de planificare și am modificat fișierele fără aprobarea lui Marius.
Regula: Pentru orice modificare de cod (nu doar task-uri cu 3+ pași), intră în plan mode, prezintă planul, și AȘTEAPTĂ aprobarea înainte de a atinge vreun fișier.
Când se aplică: Orice cerere de cod/implementare, indiferent de simplitate aparentă. Dacă e tentant să implementezi direct pentru că pare simplu — e exact momentul să te oprești și să planifici.
Supertonic rejectează ghilimelele curly (Unicode) cu HTTP 500
Data: 2026-05-27
Context: Marius a dat o comandă audio pe Discord cu un URL, iar răspunsul lui Claude conținea „foo" (ghilimele românești curly). Supertonic a returnat HTTP 500: synthesis failed: Found 1 unsupported character(s): ['„'] și răspunsul nu s-a mai auzit. Fără retry logic vizibil în UX — pur și simplu tace.
Greșeala: Am presupus că normalize_for_tts produce text deja "TTS-safe" pentru Supertonic. În realitate strip_markdown păstrează ghilimelele Unicode („ U+201E, " U+201D, — U+2014, … U+2026, etc.) pe care Supertonic le refuză.
Regula: Înainte de orice apel HTTP la Supertonic, sanitizează punctuația Unicode la echivalentele ASCII („ " " → ", ' ' ‚ → ', – — → -, … → ..., « » → "). Funcția sanitize_punctuation în src/voice/normalize.py face asta și e apelată chiar după strip_markdown în pipeline. Dacă apar caractere noi care crapă Supertonic (ex: simboluri matematice, săgeți), adaugă-le în _TTS_PUNCT_MAP.
Când se aplică: Orice cod care trimite text la Supertonic (tools/tts.py, src/voice/tts_stream.py). Inclusiv testare manuală cu curl — folosește text românesc realistic (include „foo", em-dash —, ellipsis …).
Mai multe threads ≠ mai rapid — fitează cpu_threads pe physical cores, nu logical
Data: 2026-05-27
Context: Benchmark tools/voice_bench.py pentru faster-whisper small int8 pe i7-6700T (4 physical / 8 logical cores). Marius a urcat VM-ul de la 2 → 4 → 6 cores online, așteptând că mai multe = mai rapid.
Greșeala: Presupoziție implicită că cpu_threads=N scalează liniar cu N. La 6 threads small.p50 a regresat la 2.79s vs 2.25s la 4 threads (+24% MAI LENT). Era ușor de ratat dacă rulam doar un singur pass.
Regula: Pentru workload-uri compute-bound (int8/fp16 ML inference, video encode, criptografie) setează cpu_threads = numărul de PHYSICAL cores, NU logical. Hyperthreads adaugă synchronization overhead și memory bandwidth contention fără paralelism real. Sweet spot tipic: min(num_physical_cores, $optimal_threads). Verifică cu lscpu (Core(s) per socket × Socket(s) = physical; CPU(s) = logical). Dacă faci benchmark, rulează SWEEP nu single point — 2/4/6/8 threads să vezi unde e curba reală.
Când se aplică: Configurare cpu_threads, OMP_NUM_THREADS, MKL_NUM_THREADS, torch.set_num_threads(), ffmpeg -threads, sau orice runtime ML/inference. Mai ales pe Proxmox VM-uri unde "more cores online" sună ca îmbunătățire. Întreabă-te: e workload compute-bound (yes → physical only) sau IO-bound (yes → logical OK)?
Nu șterge crontab-uri din sistem fără confirmare explicită
Data: 2026-05-20
Context: Marius a cerut să șteargă "newsletter test din cron jobs". Am interpretat că check_newsletter_cercetasi.py din crontab de sistem face parte din "newsletter test".
Greșeala: Am inclus în scop un crontab de sistem care nu fusese menționat explicit. "newsletter test" se referea doar la job-ul newsletter-test din cron/jobs.json.
Regula: Crontab-ul de sistem (crontab -l) este separat de cron/jobs.json. Nu îl modifica fără instrucțiuni explicite. Dacă scope-ul nu e clar, întreabă înainte de a acționa pe crontab sistem.
Când se aplică: Orice task care implică ștergerea sau modificarea cron jobs — distinge întotdeauna între cron/jobs.json (APScheduler) și crontab-ul de sistem.
Nu scrie manual în index.json — rulează update_notes_index.py
Data: 2026-04-29
Context: Salvam o notiță din Facebook reel în memory/kb/. Am adăugat manual o intrare în index.json cu schema greșită (id + path în loc de file), ceea ce a blocat notes.html pe "Se încarcă..." cu un TypeError în renderNoteCard.
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.
Fallback-urile nu trebuie să schimbe identitatea percepută (vocea) — degradează în interiorul aceleiași identități
Data: 2026-07-11
Context: Fix pentru voice mode: când modelul scăpa diacritice RO pe o voce pocket-tts (English-only), am ales fallback pe Supertonic M2 (altă voce, română) ca să evit tăcerea. Un singur nume propriu („Constanța") într-un răspuns altfel englezesc a comutat vocea întregului bloc — Marius a perceput asta ca bug mai grav decât problema inițială.
Greșeala: Am optimizat pentru „să se audă ceva" fără să întreb ce dimensiune e prioritară pentru user. Vocea clonată E produsul; schimbarea ei e regresia maximă percepută.
Regula: Când un input parțial invalid ajunge la un sistem cu identitate configurată de user (voce, persona, stil), remediază conținutul ca să treacă (transliterare, sanitizare), nu comuta pe altă identitate. Fallback pe alt engine/voce doar la eșec TEHNIC (server picat), nu la eșec de conținut.
Când se aplică: Orice pipeline TTS/persona cu fallback. Implementare: fold_ro_diacritics în tools/tts.py + push_text în src/voice/tts_stream.py.