flowchart TB
A["1 · Discovery"] --> B["2 · Raw collection"]
B --> C["3 · Dedupe Jaccard"]
C --> D["4 · Diarize LLM"]
D --> E["5 · Clean stopwords"]
E --> F["6 · Project scris/vorbit"]
F --> G["7 · Analyze basic+TF-IDF"]
G --> H["8 · BERTopic"]
G --> I["9 · Promise Tracker"]
G --> J["10 · Avansate (complexitate, ezitare, drift, NER, sentiment)"]
J --> K["11 · ND-Bolojan"]
H --> L["Interpret"]
I --> L
K --> L
Metoda
Pipeline reproducible. Cod public. Date verificabile.
Filosofie
Datele și codul sunt publice. Toate concluziile pot fi reproduse rulând scripturile din repository.
Singurele dependențe externe:
- Cheie API Gemini (~5€ total pentru toate clasificările LLM)
- Cheie API Webshare proxy (pentru bypass YouTube rate limit)
- Cheie API Apify (pentru scraping Facebook)
Cele 11 etape
1-2 · Colectare brută
9 surse: Facebook NicusorDan.ro (prin Apify) + 8 canale YouTube (prin yt-dlp + youtube_transcript_api cu rotație proxy Webshare IP 1-200).
Output: 1.525 documente cu YAML frontmatter complet.
3 · Deduplicate
Jaccard similarity ≥ 0,70 pe token sets per dată. Cluster cu single-link, canonical pick prioritar (Adm. Prezidențială > Privesc.Eu > NicusorDan.ro > manual > TV).
Output: 1.110 docs canonice (415 dups eliminate).
4 · Diarizare LLM
Gemini 2.5 Flash cu thinking_budget=0 și HttpOptions timeout 5min. Prompt în română pentru etichetare [ND], [JURNALIST], [ANCHOR], [MODERATOR], [OFICIAL: nume], [UNKNOWN].
Audit dublu (3 seed-uri × 60 fișiere): 95-97% acuratețe, zero cazuri de non-ND etichetat fals ca ND.
5-6 · Clean + Project
spaCy ro_core_news_sm + stopwordsiso + speech-filler stopwords custom (ă, ăă, hm, mhm, muzică, aplauze).
3 proiecții: - overall (1.062 docs) — toate sursele - scris (724 docs) — doar Facebook - vorbit (338 docs) — doar video
7 · Statistici de bază + TF-IDF
Wordclouds, top 30 cuvinte, TF-IDF temporal pe 6 perioade (precampanie → criză Q2 2026).
8 · BERTopic
Multilingual-e5-large fp16 (rulat pe Colab T4 GPU) + UMAP (5-dim, cosinus) + HDBSCAN (min_cluster_size=8) + c-TF-IDF.
26 topice descoperite în overall (+ 7 scris + 8 vorbit).
9 · Promise Tracker
Sub-pipeline cu 4 etape:
- Extract cu Gemini din 319 docs campanie → 150 promisiuni
- Dedup cu paraphrase-multilingual-mpnet cosinus (prag 0,85) → 131 unice
- Match cu top-8 paragrafe relevante din mandat
- Classify cu LLM în {KEPT, IN_PROGRESS, REFRAMED, ABANDONED, CONTRADICTED, NO_MENTION}
Audit manual stratificat (5 per status × 2 seed-uri = 32 sample-uri): 75% spot-on, 9% greșite (toate “pre-mandate fulfilled” — eroare metodologică sistematică).
Fact-check web independent pe 10 promisiuni selectate pentru vizibilitate (5 PMB + 5 policy internă): 6 din 10 au fost executate concret prin acțiuni guvernamentale (decrete, promulgări, numiri) chiar dacă clasificatorul ND-centric le-a marcat NO_MENTION/IN_PROGRESS. Pattern documentat — nu extrapolabil exact pe toate 131 fără verificare sistematică.
10 · Analize avansate
5 analize SotA rulate pe toate 3 proiecții:
| # | Analiză | Tehnică |
|---|---|---|
| 5 | Discourse complexity | spaCy parser → sentence length, tree depth, MTLD, TTR |
| 6 | Ezitare / nuanțare în limbaj (hedging) | Lexicon RO custom 35+ markeri nuanțare, 24 certitudine |
| 7 | Semantic drift | mpnet centroid per (topic × perioadă), cosine drift |
| 8 | NER entități | GLiNER multi-v2.1 zero-shot |
| 9 | Sentiment per entitate | Gemini 2.5 Flash pe paragrafe relevante |
11 · Raportul ND-Bolojan
Extract Bolojan/premier/prim-ministru + Ciolacu cu pas de disambiguare LLM (scripts/utils_entity_disambiguation.py): 119 candidați → 45 spans validate (74 false positives eliminate prin Gemini “este paragraful efectiv despre Bolojan?”) + 33 spans Ciolacu. Clasificare Gemini ton relațional per perioadă. Fact-check web pe toate 6 perioade complementar.
Audit de control pe entități non-Bolojan (TRUMP, PSD, SIMION, 41 passages): 0% false positive rate — GLiNER-based matching e robust; doar regex-ul pe termeni generici (premier/prim-ministru) avea bug.
Limitări recunoscute
Nu măsoară acțiuni reale. Pentru proiectele PMB (executate de succesori), e blind. Pentru acțiuni prezidențiale concrete (decrete, promulgări), e fidel.
Mitigat prin fact-check web independent.
Clasificatorul caută evidence în corpus mandat. Promisiuni îndeplinite înainte (ex: “voi candida independent”) nu mai apar în mandat → fals NO_MENTION.
Eroare sistematică izolată la topic justiție electorală. Nu schimbă concluziile narative.
GLiNER ratează mențiuni unde Bolojan e numit doar “premierul” (fără nume). Soluție: regex pattern dedicat + pas de disambiguare LLM (scripts/utils_entity_disambiguation.py) — fiindcă regex-ul simplu pe “premier” capta și paragrafe despre Ciolacu, Ponta (2014), Tusk. Rezultat: 119 candidați → 45 validate (74 false positives eliminate, 62% rate).
Pe perioade lungi cu mențiuni diverse, sentimentul detectat e o medie. Q3 2025 a apărut “colaborativ” deși conținea și dezacord TVA — surface pozitivul dominant.
Mitigat prin fact-check web.
Costuri reale
| Etapă | Cost |
|---|---|
| Apify scraping FB | ~$10 |
| Webshare proxy (lună) | $3 |
| Gemini API (toate clasificările + diarize + extract promisiuni) | ~$0,50 total |
| Colab T4 (BERTopic) | gratis |
| GLiNER local (NER) | gratis |
| Storage GitHub | gratis |
Total proiect: ~$15 + ~50 ore muncă.
Reproducibilitate
Toate scripturile au:
- Argumente CLI documentate
- Cache deterministic (acelaiași prompt = acelaiași output cu temperature=0.1)
- Audit trail (toate clasificările LLM salvate ca JSONL)
- Tests pentru funcțiile critice
Pentru a reproduce de la zero:
git clone https://github.com/radusqrt/evolutie-discurs-nicusor-dan
cd evolutie-discurs-nicusor-dan
pip install -r requirements.txt
cp .env.example .env # adaugă cheile API
python scripts/list_candidates.py
python scripts/fetch_from_candidates.py
python scripts/fb_apify_collect.py
python scripts/03_dedupe.py
python scripts/04d_diarize_llm_v2.py
python scripts/05_project.py
# ... etcTotal wall-clock pentru o rulare completă: ~6-8 ore (majoritar Gemini calls + Colab BERTopic).