livrare lot 2

This commit is contained in:
EVOTECH IT SRL 2026-07-10 03:39:53 -07:00
commit 8ecc78e729
763 changed files with 164593 additions and 0 deletions

View file

@ -0,0 +1,421 @@
# HIL Moderation System — Design Doc
Status: DRAFT 2026-04-30
Owner: tehnic@finesynergy.eu
Companion doc: BRAIN_V2_DESIGN.md (atomi knowledge — separat)
---
## Goal
Adăugăm un layer Human-in-the-Loop peste pipeline-ul existent: 1-2 moderatori validează/corectează 10-20 sesiuni/zi (cele cu confidence scăzut, în topicuri sensibile sau flagged de useri). Verdictul final livrat userului e instant; corecțiile vin post-fapt și se reflectă în extensie/dashboard cu badge "Verified by analyst". Corecțiile validate alimentează brain v2 (knowledge cache).
**Out of scope** pentru acest doc: tot ce ține de brain (atomi, embeddings, cache propagation). Vezi BRAIN_V2_DESIGN.md.
---
## Decizii agreate
| # | Decizie |
|---|---|
| 1 | SLA = instant cu corecție post-fapt. User vede verdictul în 5s; corecțiile sunt async |
| 2 | Triage v1 strict (10-20 sesiuni/zi pentru 1-2 moderatori). Auto-tunable |
| 3 | Operational data (coadă, status, audit) în DIDI PG schema `bos_analysis` — NU în brain |
| 4 | Brain primește atomi gold doar la trigger din moderation (`POST /v1/analysis_atom` PATCH cu `human_validated=true`) |
| 5 | Modificări UI într-un modul nou `admin-dashboard/src/components/Moderation/`, NU subtab |
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Pipeline existent (neschimbat) │
│ POST /api/v3/pipeline/analyze │
│ → ComponentRunner.runAll() → VerdictCalculator → persist │
│ → response 200 cu AnalysisSession (instant, ≤5s) │
└────────────────────────────┬────────────────────────────────────┘
│ după persist
┌────────────────────────────────────┐
│ triage.shouldEnqueueForReview() │ ← nou
│ (apelat din pipeline/executor.ts) │
└────────────────┬───────────────────┘
needs_review?
┌────┴────┐
yes no
│ │
▼ (nimic — sesiunea e finală)
INSERT INTO moderation_queue
(priority, session_id, status='pending')
┌─────────────────────────────────────┐
│ Moderator UI (/moderation) │
│ - listează queue │
│ - opens detail │
│ - approve / edit / reject │
└────────────┬────────────────────────┘
┌───────────────────────┴──────────────────────┐
│ PUT /api/v3/moderation/queue/:id/resolve │
│ body: { action, corrections, notes } │
└─────────────────────┬────────────────────────┘
┌─────────────┼──────────────┐
▼ ▼ ▼
UPDATE analysis_session UPDATE POST brain
(human_corrected=true, moderation_ /v1/analysis_atom
human_corrections={...}, queue (PATCH gold)
verified_by, verified_at) (status= (vezi doc B)
'resolved')
```
---
## Data Model
### Schema modifications — `bos_analysis`
```sql
-- Migration: agent-v3/sql/migrations/010_add_moderation.sql
-- 1. Coloane noi pe analysis_session pentru a trace human review
ALTER TABLE bos_analysis.analysis_session
ADD COLUMN review_status TEXT DEFAULT 'none'
CHECK (review_status IN ('none', 'pending', 'in_review', 'resolved', 'declined')),
ADD COLUMN human_corrected BOOLEAN DEFAULT false,
ADD COLUMN human_corrections JSONB NULL,
ADD COLUMN verified_by TEXT NULL, -- keycloak_id moderator
ADD COLUMN verified_at TIMESTAMP NULL,
ADD COLUMN review_notes TEXT NULL;
CREATE INDEX idx_analysis_session_review_status
ON bos_analysis.analysis_session(review_status)
WHERE review_status != 'none';
-- 2. Tabel nou: queue moderare
CREATE TABLE bos_analysis.moderation_queue (
queue_id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL REFERENCES bos_analysis.analysis_session(session_id) ON DELETE CASCADE,
priority INTEGER NOT NULL DEFAULT 5, -- 1=highest (user_flagged), 5=lowest (low_confidence)
enqueue_reason TEXT NOT NULL, -- 'low_confidence', 'flagged', 'sensitive_topic', 'mixed'
enqueue_meta JSONB NULL, -- raw scores, topic detected, flag reason
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'in_review', 'resolved', 'declined', 'auto_closed')),
assigned_to TEXT NULL, -- keycloak_id moderator
assigned_at TIMESTAMP NULL,
resolved_at TIMESTAMP NULL,
resolved_by TEXT NULL,
resolution_action TEXT NULL -- 'approved' (no change), 'corrected', 'rejected'
CHECK (resolution_action IS NULL OR resolution_action IN ('approved', 'corrected', 'rejected')),
time_in_queue_ms INTEGER NULL, -- enqueue → start review
time_in_review_ms INTEGER NULL, -- start review → resolved
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_moderation_queue_status_priority
ON bos_analysis.moderation_queue(status, priority, created_at)
WHERE status IN ('pending', 'in_review');
CREATE INDEX idx_moderation_queue_session
ON bos_analysis.moderation_queue(session_id);
CREATE INDEX idx_moderation_queue_assigned
ON bos_analysis.moderation_queue(assigned_to)
WHERE status = 'in_review';
```
### `human_corrections` JSONB shape
Diff-style — doar ce s-a schimbat, NU întreaga sesiune:
```json
{
"verdict": {
"risk_score": { "from": 67, "to": 45 },
"risk_category": { "from": "QUESTIONABLE", "to": "MIXED" },
"severity": { "from": "MEDIUM", "to": "LOW" }
},
"techniques": {
"removed": ["false_dilemma_42"],
"added": [],
"score_override": { "from": 78, "to": 55 }
},
"ai_tampered": null,
"claims": {
"status_changes": [
{ "claim_id": "c1", "from": "UNVERIFIED", "to": "VERIFIED_TRUE" }
]
}
}
```
Permite UI să arate "ce a corectat moderatorul" + permite brain să primească diff pentru gold atom.
---
## Triage Logic
Locație: `agent-v3/src/components/moderation/triage.ts` (modul nou)
```typescript
// Pseudo-cod, nu pentru implementare directă
interface TriageInput {
session: AnalysisSession;
userFlagged: boolean; // din input request, opțional
}
interface TriageOutput {
needsReview: boolean;
priority: 1 | 2 | 3 | 4 | 5;
reason: 'flagged' | 'low_confidence' | 'sensitive_topic' | 'mixed' | 'none';
meta: Record<string, unknown>;
}
// Reguli (citite din Redis pentru a putea ajusta fără rebuild)
const SENSITIVE_TOPICS = ['elections', 'health', 'war', 'covid']; // configurable
const CONFIDENCE_LOW_THRESHOLD = 50; // configurable
const RISK_GREY_ZONE = [45, 60]; // configurable
const QUEUE_AUTO_RELAX_THRESHOLD = 50; // dacă pending > 50, drop topic filter
const QUEUE_AUTO_STRICT_THRESHOLD = 5; // dacă pending < 5, ridica confidence threshold
function shouldEnqueueForReview(input: TriageInput): TriageOutput {
// 1. Flagged de user — priority maxim, întotdeauna
if (input.userFlagged) {
return { needsReview: true, priority: 1, reason: 'flagged', meta: {...} };
}
// 2. Confidence foarte scăzut — priority mediu
if (input.session.confidence < CONFIDENCE_LOW_THRESHOLD) {
return { needsReview: true, priority: 3, reason: 'low_confidence', meta: { confidence: input.session.confidence } };
}
// 3. Risk în zona gri + topic sensibil — priority scăzut (dar nenul)
const inGreyZone = RISK_GREY_ZONE[0] <= input.session.risk_score && input.session.risk_score <= RISK_GREY_ZONE[1];
const isSensitive = SENSITIVE_TOPICS.includes(input.session.topic_applied);
if (inGreyZone && isSensitive) {
return { needsReview: true, priority: 4, reason: 'sensitive_topic', meta: {...} };
}
// 4. Default — nu intră în review
return { needsReview: false, priority: 5, reason: 'none', meta: {} };
}
// Auto-tuning daily cron (apelat din didi-framework sau scheduler nou)
async function autoTuneTriageThresholds() {
const pending = await query("SELECT COUNT(*) FROM moderation_queue WHERE status='pending'");
if (pending > QUEUE_AUTO_RELAX_THRESHOLD) {
// Drop SENSITIVE_TOPICS filter — doar low_confidence + flagged se mai pun în coadă
} else if (pending < QUEUE_AUTO_STRICT_THRESHOLD) {
// Lower CONFIDENCE_LOW_THRESHOLD la 65 — mai multe sesiuni intră
}
// Salvează în Redis: didi:config:moderation:v1:thresholds
}
```
Triage e apelat din `pipeline/executor.ts` DUPĂ ce sesiunea e persistată (sync trec, async după aggregator finalizează).
---
## API Endpoints (agent-v3)
Locație: `agent-v3/src/api/moderation-routes.ts` (modul nou)
```
GET /api/v3/moderation/queue
?status=pending&priority=1,2,3&assigned_to=me&limit=20&offset=0
→ { items: [{ queue_id, session_id, priority, reason, created_at, age_minutes }], total }
GET /api/v3/moderation/queue/:queueId
→ { queue_entry, session: AnalysisSession (full) }
POST /api/v3/moderation/queue/:queueId/claim
→ { success, assigned_to, assigned_at }
(atomic claim: UPDATE ... WHERE status='pending' RETURNING ...)
PUT /api/v3/moderation/queue/:queueId/resolve
body: {
action: 'approved' | 'corrected' | 'rejected',
corrections?: HumanCorrectionsDiff, // doar dacă action='corrected'
notes?: string,
trigger_brain_write?: boolean // default true pe 'corrected', false pe 'approved'
}
→ { success, session_updated, brain_atom_written }
POST /api/v3/moderation/flag
body: { session_id, user_id, reason: 'wrong_verdict' | 'missing_techniques' | 'other', notes? }
→ { success, queue_id }
(apelat de extensia browser când user dă click pe "report")
GET /api/v3/moderation/stats
?period=7d
→ {
pending_count,
resolved_today: { count, avg_time_ms },
resolved_period: { count, by_action: { approved, corrected, rejected } },
avg_corrections_per_session,
brain_writes_period: { gold, silver }
}
```
Auth: toate rutele necesită JWT cu rol `moderator` sau `senior_moderator`. Excepție `/flag` — necesită doar JWT user normal.
---
## Keycloak
Realm `didi-clients` modificare:
```
Roluri noi:
- moderator (poate face claim/resolve pe queue)
- senior_moderator (poate face escalate, override decisions)
Grup nou:
- moderators-team (atribuit roluri: moderator)
- senior-moderators-team (atribuit roluri: moderator + senior_moderator)
```
Verificare middleware în `moderation-routes.ts`: read JWT, check `realm_access.roles.includes('moderator')`. Pe orice ruta nu-moderator → 403.
---
## UI — Admin Dashboard
Locație: `admin-dashboard/src/components/Moderation/` (modul nou)
```
Moderation/
├── ModerationQueue.tsx # /moderation
│ - Listă paginată cu filtre (priority, reason, age, assigned_to=me|all)
│ - Click pe row → ModerationDetail
│ - Auto-refresh la 30s
├── ModerationDetail.tsx # /moderation/:queueId
│ - Side-by-side:
│ * Stânga: input (text/url/media), metadata sesiune
│ * Dreapta: tab-uri Verdict / Techniques / AI / Claims
│ - Pe fiecare tab: fields editabile (toggle technique on/off, change claim status)
│ - Buton "Claim review" → POST /claim
│ - Butoane finale: "Approve as is" | "Save corrections" | "Reject (low quality input)"
├── ModerationStats.tsx # /moderation/stats
│ - Cards: pending count, resolved today, avg time, brain writes
│ - Chart: trend 7d (recharts)
├── api.ts # fetch helpers
└── index.ts # exports
```
App.tsx adaugă rută `/moderation` cu `<ProtectedRoute requiredRole="moderator">`.
ServicesDashboard.tsx (sidebar): adaugă link "Moderation" dacă userul are rol moderator.
---
## Modificări în agent-v3 (existing files)
### 1. `pipeline/executor.ts` — apel triage post-persist
```typescript
// Diff conceptual
import { shouldEnqueueForReview } from '../moderation/triage';
import { enqueueForReview } from '../moderation/queue-manager';
// În execute(), după PersistService.persist():
const triage = shouldEnqueueForReview({ session, userFlagged: input.userFlagged ?? false });
if (triage.needsReview) {
await enqueueForReview({ session_id: session.session_id, ...triage });
}
return session;
```
### 2. `api/pipeline-routes.ts` — primește user_flagged opțional
```typescript
// Body request /api/v3/pipeline/analyze
{ text, user_id, media_type, user_flagged?: boolean }
```
### 3. `queue/aggregator.ts` (pentru flow async) — apel triage post-aggregation
Identic cu pipeline/executor.ts dar în path-ul async. Triage se apelează DUPĂ ce verdict-ul e calculat și persistat, indiferent dacă e sync sau async.
---
## Rollout Plan
### Faza 1 — Foundation (1-2 zile)
- [ ] Migration SQL `010_add_moderation.sql` aplicat manual pe cluster
- [ ] Rol `moderator` + grup `moderators-team` în Keycloak (realm import)
- [ ] Modul nou `agent-v3/src/components/moderation/` (triage + queue-manager) — fără triage activ încă
- [ ] Modul nou `agent-v3/src/api/moderation-routes.ts` — endpoint-uri minimale (queue list, detail, claim, resolve fără brain write)
- [ ] Test endpoint-uri cu Postman/curl
### Faza 2 — UI (2-3 zile)
- [ ] `admin-dashboard/src/components/Moderation/` — Queue + Detail + Stats
- [ ] Rută `/moderation` cu role guard
- [ ] Sidebar link
- [ ] Test E2E pe staging cu user moderator dummy
### Faza 3 — Triage activation (1 zi)
- [ ] Apel `shouldEnqueueForReview()` din `pipeline/executor.ts` și `aggregator.ts`
- [ ] Configurabilitate threshold-uri prin Redis (`didi:config:moderation:v1:thresholds`)
- [ ] CRUD UI pentru threshold-uri în admin dashboard (tab nou în `/llm-components` sau pagina nouă)
- [ ] Auto-tuning cron (zilnic 03:00 UTC)
### Faza 4 — Brain integration (vezi Doc B)
- [ ] Brain v2 deployment + `/v1/analysis_atom` endpoint
- [ ] Modificare `resolve` endpoint să cheme brain PATCH cu human_validated=true
- [ ] Test end-to-end: corecție mod → atom gold în brain → analiză repetată → return cached gold
### Faza 5 — User flag în extensie
- [ ] Endpoint `/api/v3/moderation/flag` activat
- [ ] Modificare extensie browser (separate repo) să adauge buton "Report"
- [ ] Rate limiting: max 5 flag-uri/zi/user pentru a preveni abuz
---
## Open Questions
1. **Cine devine primul moderator?** Email-ul lui tehnic@finesynergy.eu? Trebuie creat user separat cu rol moderator?
2. **Notificări**: când nou enqueue → email/Slack către moderatori? (opțional, putem face polling UI auto-refresh inițial)
3. **Escalation logic**: dacă mod simplu marchează "uncertain", sesiunea trece la senior? (faza 2 sau mai târziu)
4. **Soft delete vs hard delete pe `rejected`**: dacă moderator marchează "low quality input" (ex: text gibberish), sesiunea rămâne dar marcată rejected sau dispare din istoricul user-ului?
5. **Privacy**: moderatorul vede textul original integral. Dacă e date personale (nume, adrese) — trebuie redactare? Nu acum, dar în roadmap.
---
## Metrice de monitorizat
- `moderation_queue_pending` (gauge) — alertă peste 50
- `moderation_queue_avg_age_minutes` (gauge) — alertă peste 1440 (24h lag)
- `moderation_resolutions_per_day` (counter, by action)
- `moderation_corrections_per_session_avg` — semnal cât de bine se descurcă AI-ul
- `triage_enqueue_rate` — % din sesiuni care intră în coadă (target 1-5%)
Toate scrise în Prometheus prin endpoint `/metrics` din agent-v3 (sau via PG query în Grafana).
---
## Risk register
| Risc | Probabilitate | Impact | Mitigare |
|---|---|---|---|
| Coada explodează (1-2 mod insuficient) | Medie | Mare | Auto-tune triage descrescător + alert pending>50 |
| Mod corectează inconsistent → bad gold atoms | Medie | Mediu | Faza inițială: toate corecțiile cer al 2-lea acord (slowness ok pe bootstrap); după 200 atoms gold colectate, 1-mod e suficient |
| User abuz pe `/flag` | Mică | Mic | Rate limit 5/zi/user + auto-decline pe sesiuni cu >3 flag-uri rezolvate ca approved (mod a zis e ok) |
| Schema PG migration fail pe cluster | Mică | Mare | Migration testat pe staging, rollback script pregătit |
| Race condition pe queue claim | Mică | Mic | UPDATE ... WHERE status='pending' RETURNING ... e atomic în PG |
---
## Dependencies cu Doc B (Brain v2)
- `resolve` endpoint apelează brain PATCH `/v1/analysis_atom` — depinde de Brain v2 deploy
- Faza 1-3 din rollout NU depind de brain (queue funcționează standalone)
- Faza 4 e gating pentru beneficii cost-saving + cached results
Brain v2 poate fi deployed în paralel cu Faza 1-3.