# VERDICT BUGS AUDIT — Verified with Redis + PostgreSQL Data **Data**: 2026-02-26 **Status**: Auditat si confirmat cu date reale din Redis si PostgreSQL **Fisiere afectate**: - `src/components/pipeline/verdict-calculator.ts` — buguri #1, #2, #6, #7 - `src/components/techniques/executor.ts` — bug #3 - `src/components/claims/executor.ts` — bug #4 - `src/config/analysisLimits.ts` — context pentru bug #4 (MIN_CLAIMS_TEXT_LENGTH = 100) --- ## Sesiuni de referinta (din PostgreSQL bos_analysis) ### Sesiunea 7cc2cca8 — DOVADA BUG #1 ``` risk_score = 90 | risk_category = RELIABLE | risk_level = CRITICAL | severity = CRITICAL score_manipulation = 87 | score_claims = 75 | score_ai = 0 | score_source = NULL override_applied = true | override_type = SYNERGY | override_adjustment = 20 override_reason = "2 components with high risk (+10%); 2 severe techniques (+10%)" input_text = "UK and France plan to send nuclear weapons to Ukraine to fight Russian..." components_run = {ai_tampered, techniques, claims} ``` ### Sesiunea d07266ca — DOVADA BUG #3 ``` risk_score = 68 | risk_category = QUESTIONABLE | risk_level = HIGH manipulation_score = 100 | techniques_count = 1 | total_severity = 60 | dimensions_affected = {D8} credibility_score = 50 | total_claims = 2 | verified_false = 0 input_text = "In februarie 2026 in bucuresti a nins masiv si la 4 dimineata autoritatile au tr..." ``` ### Sesiunea 2adf7ace — DOVADA BUG #4 si #5 ``` risk_score = 0 | risk_category = RELIABLE | risk_level = VERY_LOW score_manipulation = 0 | score_claims = NULL | score_ai = 0 | score_source = NULL manipulation_score = 0 | techniques_count = 0 | dimensions_affected = {D7, D8} credibility_score = NULL | interpretation = "Text too short for claims analysis (54 chars)" input_text = "Uk and France plan to send nuclear weapons to ukraine." (54 chars) ``` ### Sesiunea 4fa38b53 — Context BUG #3 si LLM false positives ``` risk_score = 58 | risk_category = QUESTIONABLE | risk_level = HIGH | severity = MEDIUM manipulation_score = 100 | techniques_count = 3 | total_severity = 240 | dimensions_affected = {D1} credibility_score = 92 | total_claims = 10 | verified_true = 9 | verified_false = 0 override_applied = true | override_adjustment = 10 (CRITICAL: severe techniques) input_type = url | input_text = "Title: Nancy Guthrie's family offers $1m reward..." score_source = 30 (domain trusted) ``` ### Sesiunea ee58753d — DOVADA BUG #2 ``` risk_score = 80 | risk_category = UNRELIABLE | risk_level = VERY_HIGH | severity = MEDIUM manipulation_score = 100 | techniques_count = 13 | total_severity = 820 | dimensions_affected = {D1,D3,D7,D8} credibility_score = 45 | total_claims = 7 | verified_false = 1 override_applied = true | override_adjustment = 10 input_text = "Un studiu secret al Universitatii Harvard arata ca 80% dintre romani vor ramane..." ``` **Severity=MEDIUM la risk_score=80 e gresit.** Conform severity_assessment: 80 e in range CRITICAL (80-100). Floatul a fost probabil ~79.5 care a cazut in gap intre HIGH (60-79) si CRITICAL (80-100). --- ## Date framework din Redis SI PostgreSQL (identice in ambele) ### Verdict Categories (bos_parammgmt.verdict_category) ``` RELIABLE start=0 end=15 color=green MOSTLY_RELIABLE start=16 end=30 color=lightgreen MIXED start=31 end=55 color=yellow QUESTIONABLE start=56 end=75 color=orange UNRELIABLE start=76 end=90 color=red DISINFORMATION start=91 end=100 color=darkred ``` GAP-URI FLOAT: 15.x, 30.x, 55.x, 75.x, 90.x — niciun range nu acopera aceste valori. ### Risk Mappings (bos_parammgmt.risk_mapping) — 6 nivele, NU 4 ca in cod defaults ``` VERY_LOW start=0 end=10 level=1 color=green LOW start=11 end=25 level=2 color=lightgreen MEDIUM start=26 end=50 level=3 color=yellow HIGH start=51 end=75 level=4 color=orange VERY_HIGH start=76 end=90 level=5 color=red CRITICAL start=91 end=100 level=6 color=darkred ``` GAP-URI FLOAT: 10.x, 25.x, 50.x, 75.x, 90.x Cod defaults au 4 nivele (LOW/MODERATE/HIGH/CRITICAL), Redis are 6 (VERY_LOW/LOW/MEDIUM/HIGH/VERY_HIGH/CRITICAL). Fallback in cod: `risk_mappings[length - 1]` = ultimul element = CRITICAL (cel mai sever). ### Severity Assessments (bos_parammgmt.severity_assessment) ``` LOW start=0 end=29 MEDIUM start=30 end=59 HIGH start=60 end=79 CRITICAL start=80 end=100 ``` GAP-URI FLOAT: 29.x, 59.x, 79.x Fallback in cod: `severity_assessments[length - 1]` = ultimul element. NOTA: Redis are trailing whitespace pe valori (`"CRITICAL "`), codul face `.trim()` la linia 318. ### Component Weights (bos_parammgmt.component_weight) ``` manipulation = 35 (cod default: 0.35) claims = 25 (cod default: 0.25) source = 20 (cod default: 0.20) ai = 10 (cod default: 0.10) context = 10 (cod default: 0.10) ``` Unitati diferite (35 vs 0.35) dar FARA impact — `calculateWeightedRisk` imparte la totalWeight, deci se normalizeaza. ### Multipliers (doar in Redis, cod defaults = array gol) ``` Topic (type 1): elections = 150 (intentie: 1.5x) security = 140 (intentie: 1.4x) public_health = 130 dimension_d4 = 130 dimension_d8 = 130 dimension_d5 = 120 dimension_d7 = 110 dimension_d6 = 90 Temporal (type 2): election_window = 140 crisis_window = 130 Reach (type 3): high_reach = 125 medium_reach = 110 low_reach = 100 ``` ### Verdict Config (Redis didi:config:pipeline:v1:verdict_config) ``` synergy: enabled=true, threshold=70, bonus_per_component=5, max_bonus=15 false_claims: enabled=true, threshold=3, bonus_per_claim=5, max_bonus=20 severe_techniques: enabled=true, threshold=2, bonus=10 undisclosed_ai: enabled=true, bonus=15 untrusted_domain: enabled=true, untrusted_bonus=20, suspicious_bonus=10, blacklisted_bonus=25 domain_red_flags: enabled=true, threshold=2, bonus_per_flag=5, max_bonus=15 confidence: base_per_component=12.5, domain_strong=15, domain_weak=8, techniques_max=12 ai_high=12, ai_medium=8, ai_low=4, claims_max=11 confidence_levels: HIGH=75, MEDIUM=50, LOW=0 ``` ### Scenarii redistribuire (doar Redis, ignorate de cod) ``` Full Analysis: manipulation=35, claims=25, source=20, ai=10, context=10 No Source: manipulation=44, claims=31, source=0, ai=12, context=13 No Claims: manipulation=44, claims=0, source=25, ai=12, context=19 Text Only: manipulation=47, claims=33, source=0, ai=0, context=20 Minimal: manipulation=70, claims=0, source=0, ai=0, context=30 ``` ### Techniques Scoring Config (Redis didi:config:techniques:v3:scoring_config) ``` manipulation_levels: LOW=0-0.25, MEDIUM=0.25-0.5, HIGH=0.5-0.75, CRITICAL=0.75-1.0 bonuses: count_bonus: threshold_3=+5%, threshold_5=+10% dimension_bonus: per_dimension=2%, max_bonus=15% intensity_bonus: formula = (avgIntensity - 1) * 0.05 ``` --- ## BUGURI CONFIRMATE — DETALII TEHNICE ### BUG #1 — CRITIC: Float gap in mapToVerdictCategory **Fisier**: `src/components/pipeline/verdict-calculator.ts` **Linii**: 292 (mapare), 313 (rotunjire), 568-573 (functia) **Problema**: Scorul e un float (ex: 90.4). Categoriile au ranguri integer (UNRELIABLE: 76-90, DISINFORMATION: 91-100). Valoarea 90.4 nu se potriveste nicaieri. Fallback = `categories[0]` = RELIABLE. **Cod buguit**: ```typescript // Linia 292 — mapare pe float NEROTUNJIT const riskCategory = this.mapToVerdictCategory(riskScore); const riskLevel = this.mapToRiskLevel(riskScore); const severity = this.mapToSeverity(riskScore); // Linia 313 — rotunjire DUPA mapare risk_score: Math.round(riskScore), // Linia 568-573 — functia cu fallback RELIABLE private mapToVerdictCategory(score: number): VerdictCategory { for (const cat of this.verdicts.categories) { if (score >= cat.start_range && score <= cat.end_range) return cat; } return this.verdicts.categories[0]; // RELIABLE — cel mai bun! } ``` **Fallback-uri contradictorii**: - mapToVerdictCategory: `categories[0]` = RELIABLE (cel mai bun) - mapToRiskLevel: `risk_mappings[last]` = CRITICAL (cel mai rau) - mapToSeverity: `severity_assessments[last]` = CRITICAL (cel mai rau) Rezultat posibil: risk_category=RELIABLE + risk_level=CRITICAL simultan. **Fix**: ```typescript // INAINTE de liniile 292-294, adauga: const roundedScore = Math.round(riskScore); // Apoi foloseste roundedScore in loc de riskScore: const riskCategory = this.mapToVerdictCategory(roundedScore); const riskLevel = this.mapToRiskLevel(roundedScore); const severity = this.mapToSeverity(roundedScore); // La linia 313 foloseste roundedScore: risk_score: roundedScore, ``` **Dovada din PG**: Sesiunea 7cc2cca8: risk_score=90, risk_category=RELIABLE, risk_level=CRITICAL. --- ### BUG #2 — CRITIC: Severity float gap (aceeasi cauza ca #1) **Fisier**: `src/components/pipeline/verdict-calculator.ts` **Linii**: 582-587 **Dovada din PG**: Sesiunea ee58753d: risk_score=80, severity=MEDIUM. Ar trebui CRITICAL (80-100). **Fix**: Acelasi ca #1 — rotunjire inainte de mapare. **Nota**: Ordinea elementelor in array-ul severity_assessments din Redis poate afecta fallback-ul. Trebuie verificat ordinea exacta daca se vrea un fallback inteligent. --- ### BUG #3 — MARE: manipulation_score=100 pe 1 tehnica **Fisier**: `src/components/techniques/executor.ts` **Linii**: 575-612 **Problema**: Formula `baseScore = sum(severity * confidence/100) / sum(severity)` se simplifica la `confidence/100` cand e o singura tehnica (severity se anuleaza). Cu confidence=95% si bonusuri: 0.95 + 0.02 + 0.10 = 1.07 → clamped la 1.0 → manipulation_score=100. **Cod buguit**: ```typescript private calculateManipulationScore(techniques: DetectedTechnique[], config: any): number { if (techniques.length === 0) return 0; let severitySum = 0; let weightedSum = 0; for (const t of techniques) { severitySum += t.severity; weightedSum += t.severity * (t.confidence / 100); } let baseScore = severitySum > 0 ? weightedSum / severitySum : 0; // Cu 1 tehnica: baseScore = (S * C/100) / S = C/100 // ...bonusuri... const finalScore = baseScore + dimensionBonus + countBonus + intensityBonus; return Math.round(Math.max(0, Math.min(1, finalScore)) * 100); } ``` **Fix propus** — Scalare bazata pe numar tehnici (cap maxim per count): ```typescript // Dupa calculul baseScore, inainte de bonusuri: // Penalizare count mic: o singura tehnica nu poate justifica scor maxim const countScaler = Math.min(1.0, techniques.length / 3); // 1 tehnica -> max 33%, 2 tehnici -> max 67%, 3+ -> 100% baseScore = baseScore * countScaler; ``` Alternativ, un cap per count: 1 tehnica → max 50, 2 tehnici → max 70, 3+ → max 100. **Dovada din PG**: Sesiunea d07266ca: techniques_count=1, total_severity=60, manipulation_score=100. --- ### BUG #4 — MARE: Claims credibility_score=NULL pierde ponderea **Fisiere**: - `src/components/claims/executor.ts` linii 695-716 (buildSkippedResult) - `src/components/pipeline/verdict-calculator.ts` linii 358-360 (extractComponentScores) - `src/config/analysisLimits.ts` linia 21 (MIN_CLAIMS_TEXT_LENGTH = 100) **Problema**: Text intre 50-99 chars trece validarea hard (MIN_TEXT_LENGTH=50) dar claims e skip-uit (MIN_CLAIMS_TEXT_LENGTH=100). buildSkippedResult returneaza `credibility_score: null`. VerdictCalculator: null → -1 → component indisponibil → weight redistribuit. Diferenta comportament: - Text < 100 chars: claims skip → credibility=NULL → weight redistribuit (claims ignorat) - Text >= 100 chars, 0 claims: buildEmptyResult → credibility=50 → participa la scor - Text >= 100 chars, claims gasite: credibility calculat 0-100 **Cod relevant**: ```typescript // claims/executor.ts linia 695 private buildSkippedResult(startTime: number, textLength: number): ClaimsResult { return { credibility_score: null, // <-- NULL interpretation: `Text too short for claims analysis (${textLength} chars)`, // ... }; } // claims/executor.ts linia 672 private buildEmptyResult(...): ClaimsResult { return { credibility_score: 50, // <-- DEFAULT 50 interpretation: 'No claims to verify', // ... }; } // verdict-calculator.ts linia 358 const claimsRisk = claims && claims.credibility_score != null && claims.credibility_score >= 0 ? Math.round(100 - claims.credibility_score) : -1; // -1 = indisponibil, weight redistribuit ``` **Fix propus**: In buildSkippedResult, returneaza `credibility_score: 50` in loc de `null` (neutral, ca si 0 claims). Sau: in verdict-calculator, trateaza null ca 50 in loc de -1. **Dovada din PG**: Sesiunea 2adf7ace: credibility_score=NULL, interpretation="Text too short for claims analysis (54 chars)". --- ### BUG #5 — MARE: Text scurt vs lung inconsistenta dramatica **Cauza**: Combinatie buguri #3 + #4 + dampening din verdict-calculator. **Mecanism**: 1. Text scurt (54 chars) → screening LLM nu detecteaza tehnici → manipulation=0 2. 54 < 100 → claims skip → credibility=NULL → weight redistribuit 3. dampenBenignContent (verdict-calculator.ts:455-469): techniques=0, ai=0 → maxStrongSignal=0 < 15 → cap = 0 + 15 = 15 4. Rezultat: risk_score capeat la max 15 → in practica 0 **Cod dampening**: ```typescript // verdict-calculator.ts linia 455 private dampenBenignContent(rawScore: number, scores: ComponentScores): number { const BENIGN_THRESHOLD = 15; const strongSignals = [scores.manipulation, scores.ai].filter(s => s >= 0); if (strongSignals.length === 0) return rawScore; const maxStrongSignal = Math.max(...strongSignals); if (maxStrongSignal >= BENIGN_THRESHOLD) return rawScore; const cap = maxStrongSignal + BENIGN_THRESHOLD; return Math.min(rawScore, cap); } ``` **Dovada din PG**: - 2adf7ace (54 chars): risk_score=0, RELIABLE - 7cc2cca8 (aceeasi info, ~200+ chars): risk_score=90, RELIABLE (bug #1, ar trebui UNRELIABLE) --- ### BUG #6 — MARE (LATENT): Multiplicatori ×150 in loc de ×1.5 **Fisier**: `src/components/pipeline/verdict-calculator.ts` **Linii**: 279-289 **Problema**: Redis stocheaza multiplicatori ca procente (elections=150 = 1.5x). Codul aplica direct: `riskScore * 150` in loc de `riskScore * 1.5`. **Cod buguit**: ```typescript // Linia 284 if (options?.topic) { const topicMultiplier = this.getMultiplier('topic', options.topic); if (topicMultiplier) { riskScore = Math.min(100, riskScore * topicMultiplier.multiplier); // riskScore * 150 = scor × 150, nu × 1.5 // Math.min(100, ...) salveaza de catastrofa dar orice scor > 0.67 devine 100 } } ``` **Date Redis**: ``` elections=150, security=140, public_health=130, dimension_d4=130, dimension_d8=130 election_window=140, crisis_window=130, high_reach=125, medium_reach=110, low_reach=100 ``` **Fix**: ```typescript riskScore = Math.min(100, riskScore * (topicMultiplier.multiplier / 100)); ``` **Status**: Bug latent — multiplicatorii se aplica doar daca `options.topic` e setat, ceea ce pare sa nu fie folosit activ momentan. Dar cand se va activa, va fi catastrofal. --- ### BUG #7 — MIC: Scenarii Redis ignorate **Fisier**: `src/components/pipeline/verdict-calculator.ts` **Linia**: 385 **Problema**: Parametrul `_scenario` (cu underscore = nefolosit) in `calculateWeights`. Codul face redistribuire proportionala proprie in loc sa foloseasca scenariile pre-definite din Redis. **Redis contine 5 scenarii**: ``` Full Analysis: manipulation=35, claims=25, source=20, ai=10, context=10 No Source: manipulation=44, claims=31, source=0, ai=12, context=13 No Claims: manipulation=44, claims=0, source=25, ai=12, context=19 Text Only: manipulation=47, claims=33, source=0, ai=0, context=20 Minimal: manipulation=70, claims=0, source=0, ai=0, context=30 ``` **Impact**: Redistribuirea proportionala din cod poate diferi de scenariile gandite de admin. Nu e un bug functional, dar e un feature ignorat. --- ### BUG #8 — MIC: coupling_context flags din screening, nu deep analysis **Fisier**: `src/components/techniques/executor.ts` **Linii**: 664-666 **Problema**: `dimensionsAffected` vine din screening (linia 544), nu din tehnicile confirmate in deep analysis. Daca screening detecteaza D7/D8 dar deep analysis nu gaseste tehnici → coupling_context flags raman true cu 0 tehnici. **Cod buguit**: ```typescript // Linia 664-666 in buildCouplingContext for_context_analysis: { narrative_manipulation_detected: dimensionsAffected.includes('D7'), // din SCREENING context_manipulation_detected: dimensionsAffected.includes('D8'), // din SCREENING amplification_detected: dimensionsAffected.includes('D6'), suspicious_patterns: warningFlags, }, ``` **Fix**: Foloseste dimensiunile din tehnicile efectiv detectate (deep analysis) in loc de cele din screening: ```typescript const confirmedDimensions = new Set(techniques.map(t => t.dimension)); // apoi: confirmedDimensions.has('D7') in loc de dimensionsAffected.includes('D7') ``` **Dovada din PG**: Sesiunea 2adf7ace: dimensions_affected={D7,D8}, techniques_count=0. --- ## PRIORITIZARE FIX-URI | Prioritate | Bug | Efort | Impact | |-----------|-----|-------|--------| | 1 | #1 + #2: Rotunjire inainte de mapare | 1 linie | Elimina bug CRITIC (RELIABLE la risk 90) | | 2 | #3: Formula manipulation_score | ~10 linii | Elimina scor 100% pe 1 tehnica | | 3 | #6: Multiplicatori /100 | 1 linie | Previne bug catastrofal cand se activeaza topics | | 4 | #4: Claims null → 50 | 1 linie | Elimina pierdere ponderi pe text scurt | | 5 | #8: coupling_context flags | ~5 linii | Corecteaza metadata inconsistenta | | 6 | #7: Scenarii Redis | ~20 linii | Feature, nu bug critic | --- ## REFERINTE FISIERE ``` verdict-calculator.ts — src/components/pipeline/verdict-calculator.ts Linia 292-294: mapare categorii (BUG #1, #2) Linia 279-289: multiplicatori (BUG #6) Linia 313: Math.round output Linia 358-360: claims null handling (BUG #4) Linia 385: _scenario ignorat (BUG #7) Linia 455-469: dampenBenignContent (context BUG #5) Linia 568-573: mapToVerdictCategory cu fallback RELIABLE Linia 575-579: mapToRiskLevel cu fallback CRITICAL (ultimul) Linia 582-587: mapToSeverity cu fallback ultimul techniques/executor.ts — src/components/techniques/executor.ts Linia 575-612: calculateManipulationScore (BUG #3) Linia 664-666: coupling_context flags (BUG #8) claims/executor.ts — src/components/claims/executor.ts Linia 695-716: buildSkippedResult credibility=null (BUG #4) Linia 672-693: buildEmptyResult credibility=50 analysisLimits.ts — src/config/analysisLimits.ts Linia 14: MIN_TEXT_LENGTH = 50 Linia 21: MIN_CLAIMS_TEXT_LENGTH = 100 ```