Document tehnic
RoDebate — Specificație funcțională și tehnică
MVP, versiunea 1.2 • 12 noiembrie 2026
Sursă: RoDebate_Specificatie_Functionala_Tehnica_MVP_v1.2.docx
Pe scurt
- Mod de joc inițial: 1 vs. 1, față în față, pe un singur telefon cu browser, microfon și difuzor.
- Catalog: cinci moțiuni publicate de administrator; ulterior, propuneri și vot comunitar pentru moțiuni noi.
- Joc: research inițial, discursuri PRO/CONTRA, replici, întrebări și concluzii; panouri cu cronometru mare, tip ceas de șah, și buton „Predă tura”.
- Moderator: mesaje vocale neutre, predefinite; opțional, sfaturi la începutul fiecărei runde pentru începători.
- Înregistrare: fiecare tură este înregistrată și trimisă imediat spre procesare, în timp ce continuă tura următoare.
- Analiză: transcriere incrementală, apoi arbitraj final unitar cu OpenAI API în MVP; ulterior model local prin JudgeProvider.
- Rezultate: transcript, punctaje și explicații în conturile ambilor jucători; votul spectatorilor este independent și poate fi inactiv în MVP.
Ordinea priorităților: prevederile din secțiunile 16–25 sunt amendamentele cele mai recente și prevalează asupra descrierilor mai vechi (de exemplu, WebRTC între doi utilizatori sau trimiterea audio abia la final).
Scenariul prioritar: două persoane în aceeași încăpere folosesc UN SINGUR telefon cu browser, microfon și difuzor. Telefonul conduce jocul vocal și înregistrează. Pentru MVP sunt disponibile cinci moțiuni aprobate de admin. Votul spectatorilor poate rămâne inactiv până la lansarea modului public.
1. Goals / criterii de succes
- G1. Două persoane joacă față în față cu un singur telefon, roluri PRO/CONTRA și cronometru autoritativ, cu suport de pauză/reconectare.
- G2. Un singur microfon înregistrează o pistă ambientală; turn-taking-ul/etapele ajută la atribuirea vocilor, iar ambiguitățile sunt verificate înainte de scor. Audio ajunge securizat la procesorul laptopului.
- G3. Se creează transcript sincronizat, corectabil și auditabil.
- G4. Arbitrajul AI produce scoruri pe 5 criterii, explicații și citări la segmente, fără acces la votul publicului.
- G5. Votul publicului rămâne separat de scorul AI, dar este opțional în MVP-ul single-phone, fără spectatori.
- G6. Schimbarea OpenAI API → model local necesită modificarea configurației/adaptorului, nu rescrierea aplicației.
- G7. Erorile de audio, transcriere și arbitraj nu produc victorii inventate sau rating greșit.
- G8. Datele audio și personale au consimțământ/informare, acces controlat, retenție și ștergere definite.
2. Scope MVP și non-goals
| Inclus în MVP | Amânat pentru ulterior |
|---|---|
| 1 vs. 1, live, microfon, timer | Debate 2v2 / 3v3, turnee extinse |
| Lobby, confirmare, PRO/CONTRA | Streaming video, chat public avansat |
| Recorder și upload către laptop | Arbitraj în timp real în timpul discursurilor |
| STT, transcript, arbitraj OpenAI API | Fine-tuning / distilare model local |
| Scor AI, vot public, raport | Fact-checking automat complet al internetului |
| Contestații, audit, erori | Economie complexă de XP și recompense |
3. Arhitectura recomandată
Frontend mobil Next.js/React/TypeScript pe UN telefon ↔ backend Node/NestJS pentru conturi, catalog de moțiuni, faze și upload ↔ storage temporar protejat + coadă de joburi ↔ worker de pe laptop → transcriere ASR + diarizare asistată de ture → JudgeProvider → raport JSON → API → profilurile ambilor jucători. Pentru joc în aceeași încăpere NU este necesar WebRTC/SFU între doi utilizatori.
Nu se recomandă expunerea laptopului ca server public cu port deschis. Workerul local inițiază o conexiune de ieșire autentificată (poll/long-poll/WebSocket către coadă) sau se folosește un tunel securizat. Backendul public nu stochează chei OpenAI în browser.
| Componentă | Responsabilitate | Alegere propusă |
|---|---|---|
| Web app | Lobby, audio, timeline, rezultate | Next.js + TypeScript |
| Game API | Auth, meciuri, voturi, scoring status | NestJS/Node |
| Real-time | Timer și event-uri cu timp autoritativ | WebSocket / Socket.IO |
| Audio | Transport live și captură pe piste | WebRTC + MediaRecorder/recorder server |
| Queue | Joburi idempotente și retry | Redis/BullMQ sau echivalent |
| Laptop worker | Descărcare, STT, arbitraj, upload raport | Python sau Node, Docker opțional |
| Judge adapter | OpenAI sau local cu același contract | JudgeProvider interface |
| Persistence | Meciuri, audit, scoruri, metadate | PostgreSQL + object storage |
4. Flux cap-coadă
- S0 CREATED: creatorul alege moțiunea, acceptă regulile, invită adversar.
- S1 LOBBY: test microfoane, confirmare consimțământ/informare, verificare moderator și poziții.
- S2 LIVE: un singur telefon prezintă instrucțiuni audio și vizuale, numără timpul și înregistrează pista comună cu marcaje de fază. Nu există microfoane separate de dezactivat.
- S3 PER_TURN: la finalul fiecărei ture se sigilează segmentul cu checksum, se înregistrează metadatele și se inițiază upload securizat, în paralel cu tura următoare; la sfârșitul meciului se închide manifestul complet.
- S4 TRANSCRIBING (incremental per segment): audio-ul turelor încheiate este transcris în paralel cu jocul; speaker mapping este asistat de ture/timestamps și, opțional, diarizare. Fragmentele suprapuse sau incerte sunt marcate pentru verificare.
- S5 REVIEWABLE: transcriptul este segmentat pe runde și se verifică automat segmentele lipsă / speaker confidence.
- S6 EVALUATING: același pachet de date intră în OpenAIJudgeProvider sau LocalJudgeProvider.
- S7 VALIDATING: rezultatul JSON este verificat, citările sunt rezolvate la timestamp, se marchează erorile.
- S8 PROVISIONAL: votul public și arbitrajul AI se afișează separat; se permite contestație.
- S9 FINAL: după 24 h fără contestații sau după rezolvare, rezultatul devine definitiv și actualizează ratingul.
5. State machine și evenimente
| Stare | Evenimente acceptate | Observație |
|---|---|---|
| CREATED / LOBBY | join, mic_ready, consent, assign_side | Start doar cu 2 jucători |
| LIVE_OPENING | phase_started, timer_tick, segment_saved | 3 min per participant |
| LIVE_REBUTTAL | phase_started, timer_tick | 3 min per participant |
| LIVE_CROSS | turn_started, mic_granted | 3 min întrebări fiecare |
| LIVE_CLOSING | phase_started, timer_tick | 2 min fiecare |
| VOTING | vote_cast, vote_closed | Vot eligibil din backend |
| PROCESSING | upload_complete, transcript_ready, judging_ready | Fără rating până la validare |
| PROVISIONAL / FINAL | appeal_opened, appeal_resolved | Audit versionat |
6. Schema minimă de date
- User(id, handle, role, eligibility, createdAt)
- Debate(id, motion, proUserId, contraUserId, state, ruleVersion, rubricVersion, startedAt, endedAt)
- Round(id, debateId, phase, speaker, startedAt, endedAt)
- Recording(id, debateId, singleAudioTrack, location, sha256, durationMs, uploadStatus, encryptionStatus, retentionUntil)
- TranscriptSegment(id, debateId, speakerId, startMs, endMs, text, confidence, revision)
- Evidence(id, debateId, submittedBy, url, sourceTitle, verificationStatus)
- JudgeJob(id, debateId, provider, model, promptVersion, status, retryCount, errorCode)
- JudgeScore(id, debateId, side, criteriaJSON, total, rationale, citationsJSON, status)
- AudienceVote(id, debateId, voterId, side, eligible, createdAt) cu unicitate (debateId, voterId)
- Appeal(id, debateId, applicantId, reason, evidence, status, resolution)
- AuditEvent(id, debateId, eventType, actor, timestamp, payload)
7. Contract arbitru AI
Interfață logică: JudgeProvider.evaluate(DebateJudgeInput) -> DebateJudgeResult. Adapterul primește doar informațiile indispensabile și returnează obiecte validate strict la runtime.
Input: motion, sides, rules_version, rubric_version, phases, transcript_segments[{speaker,start_ms,end_ms,text}], evidence[], procedural_events[]. Excluse: voturi public, nume personale, rating, avatar, popularitate.
Output: provider, model, prompt_version, rubric_version, scores[{side,arguments:0..30,evidence:0..25,rebuttal:0..25,clarity:0..10,conduct:0..10,total:0..100,reasoning_summary,segment_refs[]}], winner:PRO|CONTRA|DRAW|INSUFFICIENT_DATA, factual_claims[{claim,verification_status,segment_ref}], warnings[], quality_flags[].
Reguli: total=sum(criteria); winner calculat și verificat în backend, nu acceptat orbește de la model; citările trebuie să indice segmente existente; o ieșire invalidă este respinsă; modelul nu decide singur sancțiuni grave.
8. Strategia de evaluare și reducerea costurilor
- Separă speech-to-text de evaluare. Transcrierea poate rula local (de ex. model ASR optimizat), în timp ce arbitrajul folosește OpenAI API în MVP.
- Nu trimite 30 de minute audio direct către modelul de arbitraj dacă transcriptul segmentat este suficient.
- Folosește câte un job idempotent per segment și un singur job de finalizare per meci; retry limitat și cache după hash transcript + model + prompt + rubrică.
- Un pas deterministic extrage fazele, speakerii, timpul și numărul de intervenții; AI primește doar conținutul de evaluat.
- Ține config provider=OPENAI|LOCAL, model, version, timeout, max_tokens, retry_count; fără schimbări în UI.
- Măsoară cost pe meci, latența, stabilitatea scorurilor între rulări și concordanța cu evaluări de referință.
9. Migrarea la LLM local „specializat pentru debate”
- Faza A: rubrică/prompt în română, output structurat, teste manuale pe 20-50 de transcrieri variate; nu antrena un model de la zero.
- Faza B: benchmark mai mare, cu arbitri umani care etichetează setul de referință și motivează scorurile; anonimizare și acord explicit pentru utilizarea datelor la antrenare.
- Faza C: evaluare zero-shot/few-shot a unui model instruct mic, cuantizat, pe hardware-ul disponibil; verifică suportul pentru limba română.
- Faza D: numai dacă e nevoie, LoRA/QLoRA cu exemple de arbitraj sau distilare pe setul licențiat. Rulează evaluări de bias, consistență și drift.
- Faza E: shadow mode: OpenAI și LOCAL evaluează aceleași meciuri, dar numai un provider atribuie scorul oficial; schimbarea oficială se face după criterii de acceptare publicate.
- Important: performanța unui LLM local depinde de CPU/GPU, RAM/VRAM și de numărul de tokenuri; alegerea modelului nu poate fi finalizată fără specificațiile laptopului.
10. API și fluxuri tehnice (propunere)
| Endpoint / event | Metodă | Funcție |
|---|---|---|
| POST /debates | HTTP | Creează meci |
| POST /debates/:id/join | HTTP | Join + rol |
| POST /debates/:id/start | HTTP | Start autorizat |
| debate:phase_changed | WebSocket | Cronometru și permisiuni microfon |
| POST /debates/:id/recordings | HTTP signed upload | Încarcă înregistrare / metadate |
| GET /worker/jobs/next | Worker authenticated | Laptopul preia job |
| POST /worker/jobs/:id/result | Worker authenticated | Raport transcriere/arbitraj |
| POST /debates/:id/votes | HTTP | Vot eligibil |
| GET /debates/:id/results | HTTP | Rezultate separate |
| POST /debates/:id/appeals | HTTP | Contestație |
11. Securitate, confidențialitate și eșecuri
- Cheile API rămân exclusiv în worker/backend; nu în aplicația client. TLS pentru transfer și tokenuri scurte de upload/worker.
- Înregistrările și transcrierile sunt date personale; acces limitat, loguri de acces, termene de păstrare explicite și ștergere programată.
- OpenAI API poate prelucra transcriptul în cloud în MVP; utilizatorii trebuie informați. Setările de retenție ale furnizorului trebuie verificate la configurare.
- Când laptopul este oprit: job queued, retry după reconectare; niciun rezultat AI inventat.
- Când transcrierea greșește speakerii: job manual_review, fără scor oficial până la corectare.
- Când răspunsul LLM e invalid sau inconsecvent: schema-validation fail, retry limitat, apoi status analysis_failed.
- Dacă apar solicitări concurente: limită de joburi active, backpressure și monitorizarea spațiului de stocare.
12. Plan de implementare pe sprinturi
| Sprint | Livrabil verificabil |
|---|---|
| 1 | Auth, schema DB, configurare moțiune, lobby, state machine, cronometru |
| 2 | WebRTC/audio, permisiuni microfon, înregistrare pe piste, upload securizat |
| 3 | Worker laptop, queue, transcriere, editor transcript cu timestamp |
| 4 | JudgeProvider OpenAI, schema JSON, raport explicabil, testare rubrică |
| 5 | Vot public eligibil, pagină rezultate duale, audit, contestații |
| 6 | Observabilitate, rate limits, retenție, accesibilitate, eșecuri, load test |
13. Acceptance criteria MVP
- Un meci poate fi jucat de două persoane față în față pe un singur telefon, cu ghidare vocală, research, ture, înregistrare și finalizare automată.
- Cronometrul nu poate fi accelerat/schimbat de client; reconnect păstrează starea.
- Pentru fiecare vorbitor există transcript și timestamp; lipsurile sunt marcate explicit.
- Raportul AI are exact cele cinci criterii, total verificabil și minimum câte o referință relevantă per justificare semnificativă.
- Votul publicului nu intră în inputul arbitrului și nu afectează scorul acestuia.
- Dacă laptopul este oprit, meciul rămâne în coadă și reia procesarea fără duplicat.
- Schimbarea providerului OpenAI -> LOCAL se face prin aceeași interfață fără schimbări de DB/UI.
- Operatorul poate investiga incidentul și participantul poate depune contestație.
14. Decizii deschise
- Specificații laptop: OS, CPU, GPU, RAM/VRAM și spațiu; determină capacitatea STT/LLM local.
- Mecanism de audio live: peer-to-peer pentru MVP sau SFU pentru scalare; pentru captură robustă poate fi necesară înregistrarea server-side.
- Retenția audio, transcrierilor, intervalul de păstrare al backupurilor și condițiile publicării.
- Dacă rezultatul AI este oficial automat în toate meciurile sau numai după anumite verificări de calitate.
- Pragurile de calitate pentru trecerea modelului LOCAL în producție.
15. Referințe și observații
OpenAI Help: ChatGPT și API au sisteme de facturare separate: https://help.openai.com/en/articles/9039756-managing-billing-for-chatgpt-and-the-api-platform
OpenAI API data controls și retenție: https://developers.openai.com/api/docs/guides/your-data
Versiunea de proiect este propunere arhitecturală, nu dovadă de existență a infrastructurii. Revizuiți GDPR/termeni de utilizare înainte de lansare.
16. Amendament MVP: experiența single-phone
Scenariu principal: două persoane stau lângă un telefon; deschid site-ul mobil RoDebate prin HTTPS; selectează două identități de jucător, moțiunea și rolurile; acceptă informarea privind înregistrarea; jocul este coordonat de ghidul vocal, cronometru și butoane mari. Un singur microfon captează întreaga încăpere. Nu este nevoie de apel audio între dispozitive.
16.1 Catalog inițial și administrare
Cele cinci moțiuni concrete sunt conținut editorial de completat de admin, nu trebuie hardcodate în client. Rejucarea aceleiași moțiuni este nelimitată, inclusiv cu același adversar; fiecare match este independent și reține motionVersion.
| Fază | Comportament | MVP? |
|---|---|---|
| Admin creează | Exact 5 moțiuni inițiale, fiecare cu ID, text, descriere neutră, categorie, status, versiune | Da |
| Listă jucabilă | Doar moțiunile publicate și marcate playable | Da |
| Propunere utilizator | Draft de moțiune cu audit și verificare admin | Ulterior |
| Vot comunitar | O persoană / un vot; cvorum și prag configurate și publicate | Ulterior |
| Aprobare finală | Admin verifică eligibilitatea și publică moțiunea votată | Ulterior |
16.2 Identități pe un singur dispozitiv
- Utilizatorul A se autentifică pe telefon și invită jucătorul B prin selectare/invitație sau cod QR.
- Alternativ, B joacă sub un token temporar verificabil; ulterior revendică participarea în cont. Un token nu permite revendicări multiple.
- Ambele conturi sunt legate la matchId; raportul apare în istoricul ambilor. Niciun cont nu are dreptul să modifice scorul sau transcriptul final.
- UX: ecranul afișează clar „Rândul lui PRO” / „Rândul lui CONTRA”, plus iconurile și timpul, fără a presupune că telefonul poate diferenția sigur vocile.
16.3 Timp și interacțiuni
Total competitiv: 22 min + research implicit 3 min + briefing/tranziții. Timpul nefolosit nu se transferă. Butonul de terminare cere o confirmare scurtă; comenzile vocale sunt suplimentare și nu trebuie să încheie automat tura fără confirmare. Pause/resume implică evenimente server-side cu timestamp și motiv.
| Fază | Durată implicită | Acțiune |
|---|---|---|
| Pregătire / research | 3 min (admin: 1–5) | Căutare, citit, notițe, pregătire simultană |
| Discurs PRO / CONTRA | 3 min / persoană | Start vocal; „Am terminat” închide devreme |
| Replică PRO / CONTRA | 3 min / persoană | Cronometru; tranziție vocală |
| Întrebări încrucișate | 6 min total | 3 min întrebări PRO, 3 min întrebări CONTRA |
| Concluzii CONTRA / PRO | 2 min / persoană | Închiderea voluntară sau la expirare |
| Vot public | opțional 3 min | Se omite în MVP fără spectatori |
16.4 Moderator vocal: fără LLM continuu
Pentru cost minim și predictibilitate, folosește o mașină de stări deterministă și mesaje românești predefinite redate cu speechSynthesis din browser sau fișiere audio locale. LLM este apelat DUPĂ meci pentru evaluare. Instrucțiunile vocale se pot declanșa doar după gest explicit al utilizatorului, pentru a respecta restricțiile autoplay din browser; oferă întotdeauna o copie text vizibilă.
- Exemple: „Moțiunea este…”, „Aveți 3 minute pentru documentare”, „PRO, este rândul tău”, „Mai ai 30 de secunde”, „Vă mulțumesc, încep analiza”.
- Moderatorul rămâne neutru: poate reda sfaturi pedagogice generale, identice ca reguli pentru ambele poziții, doar înaintea rundelor și fără cronometru activ; nu generează contraargumente pentru un jucător, nu oferă aprecierea pozițiilor și nu divulgă verdicte în timpul jocului.
- Buton clar pentru pauză, volum, repetarea instrucțiunilor și oprirea înregistrării; redarea TTS ar trebui evitată pe durata discursului sau identificată ca audio al sistemului.
16.5 Înregistrare pe un singur microfon și robustețe
Captură MediaRecorder/getUserMedia sub HTTPS; track unic cu timestamp-uri pentru phases și user-action events. Test inițial al microfonului, permisiunilor și bateriei; recomandare să se țină telefonul în centrul mesei. Salvare pe bucăți într-un buffer IndexedDB când este disponibil, upload resumable cu checksum și stare de recuperare.
Limitare de produs: un microfon NU oferă identificare perfectă a vorbitorului, mai ales când participanții se suprapun sau când vocea sintetizată a moderatorului intră în înregistrare. În fazele cu un singur vorbitor, etichetele sunt bazate pe tură; în cross-examination folosește „cine vorbește acum”/buton de predare, apoi diarizare locală și semnalizare a incertitudinii. Înregistrarea audio incompletă nu primește arbitraj definitiv automat.
Dacă telefonul intră în lock/sleep, browserul poate suspenda înregistrarea sau TTS. Menține ecranul activ când este disponibil, afișează avertisment și oferă reluare sau anulare; testează pe iOS Safari și Android Chrome. Nu promite captură continuă în background.
17. Model de date și endpointuri suplimentare
- Motion(id, slug, title, neutralDescription, category, version, status: DRAFT|PUBLISHED|ARCHIVED, playable, createdByAdminId, publishedAt)
- MotionProposal(id, authorId, title, description, moderationStatus, votingStatus, createdAt) [FUTURE]
- MotionProposalVote(id, proposalId, userId, value, createdAt) cu unique(proposalId,userId) [FUTURE]
- MatchPlayer(matchId, userId|claimToken, side, joinedAt, claimStatus)
- MatchConfig(matchId, researchSeconds, ruleVersion, motionVersion, publicVotingEnabled, judgeProvider)
- MatchEvent(matchId, sequence, phase, actor, eventType, timestamp, audioOffsetMs, metadata)
- TranscriptRevision(matchId, segmentId, oldText, newText, requestedBy, approvedBy, reason)
- GET /motions?playable=true; POST /admin/motions; PATCH /admin/motions/:id; POST /matches; POST /matches/:id/join
- POST /matches/:id/events (start_phase, finish_turn, pause, resume); POST /matches/:id/recordings; GET /me/matches; GET /matches/:id/report
- POST /matches/:id/claim; POST /matches/:id/appeals; POST /admin/motion-proposals/:id/approve [FUTURE]
18. State machine ajustată
CREATED → PLAYERS_LINKED → CONSENTED → DEVICE_CHECK → RESEARCH → BRIEFING → LIVE_PHASES → RECORDING_FINALIZING → UPLOAD_PENDING → TRANSCRIBING → SPEAKER_REVIEW (dacă e necesar) → EVALUATING → PROVISIONAL → FINAL. Stări alternative: PAUSED, CANCELLED, UPLOAD_FAILED, ANALYSIS_FAILED, APPEALED.
Fiecare tranziție este server-authoritative și idempotentă; evenimentele audioOffsetMs aliniază transcriptul cu cine avea tura. Moderatorul vocal este prezentare, nu sursa de adevăr pentru starea jocului.
19. Arbitraj, cost și migrare locală
MVP: laptop worker consumă incremental segmentele audio încheiate, transcrie local dacă hardware-ul permite și poate preanaliza turele în paralel. După încheiere și completarea tuturor segmentelor, trimite transcriptul structurat, moțiunea, fazele și sursele către OpenAI API pentru verdictul final. Cheia API există doar în backend/worker; abonamentul ChatGPT nu include automat credit API. Utilizează hash+versiune rubrică+model pentru cache, rate-limit, buget maxim per match și retry controlat.
Ulterior: LocalJudgeProvider execută un model instruct cuantizat, cu prompt/rubrică, evaluare benchmark față de arbitri umani, apoi opțional fine-tuning LoRA pe date autorizate. Nu se antrenează un LLM de la zero. Testele trebuie să compare consistență, bias PRO/CONTRA, robustețe în limba română și cost total pe meci. Nu se recomandă descrierea drept «antrenat exclusiv pe dezbateri» dacă modelul de bază a fost preantrenat general.
Regula de aur: verdictul nu este obținut din chat-ul ChatGPT personal; aplicația folosește OpenAI API cu cheie și facturare distinctă. În migrarea complet locală, nu mai trimite transcrieri sau audio către cloud pentru arbitraj; alte servicii cloud ale site-ului pot rămâne active.
20. Criterii de acceptare adăugate
- Admin poate crea și publica exact 5 moțiuni, afișate în lista PLAYABLE fără deploy de frontend.
- Aceeași moțiune poate avea două meciuri diferite, cu același adversar, fără suprascriere de scor/transcript.
- Două profiluri distincte sunt legate de jocul pornit de pe un singur telefon și văd raportul în istoricul lor.
- Telefonul redă corect instrucțiuni de moderator și înregistrează pe același microfon, cu mesaje vizuale de fallback.
- Research implicit 3 minute, opțiune administrativă 1–5 minute; „Am terminat” încheie o tură fără transferul timpului.
- Segmentarea speakerilor încrucișați marchează zonele ambigue, nu produce automat o atribuire certă.
- Jocul poate rămâne pending dacă laptopul este offline; procesarea la revenire nu dublează rezultatul.
- În MVP fără spectatori, pagina arată „vot public indisponibil” și nu pretinde un câștigător popular.
- Transcriptul din profil include runde, speaker, marcaje de timp, scoruri, justificări și proces de contestație.
- Providerul OPENAI poate fi înlocuit cu LOCAL fără modificarea contractului API sau a componentelor de rezultat.
21. Decizii pentru implementarea imediată
Setări propuse implicit: research 180 s; un telefon / un microfon; două conturi legate; trei intervenții vocale per fază (start, 30 s rămase, final); moțiuni 5 publicate de admin; fără votul publicului în meciurile offline; backend stochează în siguranță audio doar tranzitoriu; un singur job OpenAI per meci după transcriere locală.
De confirmat înainte de publicare: textele exacte ale celor cinci moțiuni, mecanismul de revendicare a unui jucător invitat, perioada de retenție și politica GDPR, acceptarea transcrierilor de către jucători, limita pentru costul AI per meci, platformele mobile suportate și specificațiile laptopului.
22. Procesare incrementală pe ture și pipeline asincron
Definiții: „fază” = tip de activitate (argument inițial, contraargument, întrebări, concluzie); „tură” = intervalul în care un jucător are dreptul la cuvânt; „segment” = fișier audio sigilat pentru acea tură. În faza de întrebări încrucișate, tura poate fi fragmentată în subsegmente atribuite vorbitorului activ. Nu trimitem audio înregistrat din research, în mod implicit.
Pipeline non-blocant: CAPTURE_ACTIVE → SEAL_SEGMENT → UPLOAD_QUEUED → UPLOADING → TRANSCRIBING → TRANSCRIBED → PARTIAL_ANALYSIS_READY. În paralel, motorul poate porni CAPTURE_ACTIVE pentru următoarea tură. La final: MANIFEST_CLOSED → ALL_SEGMENTS_READY → FINAL_EVALUATING → RESULT_PROVISIONAL. Orice eroare produce RETRY_PENDING sau REVIEW_REQUIRED, nu un scor fabricat.
Tehnic: browserul pornește MediaRecorder pe tura activă, închide pista/segmentul la transfer, memorează Blob-ul + metadatele în IndexedDB, apoi îl urcă prin URL semnat sau endpoint autorizat. Workerul de pe laptop preia joburile prin conexiune outbound securizată. Uploadul trebuie să aibă debounce/retry cu exponential backoff și chei de idempotency (matchId, turnId, attempt), checksum SHA-256 și confirmare din backend. Browserul nu poate garanta upload în background după închiderea paginii; afișăm explicit segmentele nesincronizate.
Evaluarea incrementală poate crea fișe interne pentru fiecare tură: claims, evidence_mentions, rebuttals, transcript_offsets, confidence. Acestea sunt date pregătitoare, nu scoruri oficiale. Verdictul este emis numai după ce toate segmentele au fost primite, atribuirea vorbitorilor este suficient de sigură și arbitrul vede ansamblul meciului. Pentru reducerea costurilor MVP, implicit transcriem fiecare tură, dar trimitem către OpenAI pentru arbitraj o singură cerere finală; preanaliza AI pe tură este feature flag opțional.
23. Ecranul de joc: ceas de șah cu două zone
Layout mobil: header cu moțiunea + faza; două panouri PRO/CONTRA cu nume, rol, timer mare monospaced (min:sec) și stare ACTIVE/WAITING; zonă centrală pentru instrucțiunea moderatorului; footer cu indicator REC, progres upload/transcriere, butoane Pauză, Repetă instrucțiune, Predă tura. Panoul activ are contrast ridicat; cel inactiv este estompat. Nu se folosesc doar culori pentru distingerea activității.
Interacțiune: apăsarea panoului activ sau „Predă tura” încheie segmentul și cere confirmare/gest anti-atingere accidentală; schimbul de ceasuri se face atomic pe server cu expectedVersion și turnId. Clientul afișează imediat tranziția optimistă doar dacă este sigură, reconciliază cu serverul și nu permite multiple comutări din dublu tap. Expirarea timerului activează aceeași tranziție. Pentru o fază cu durată fixă, ceasul nu poate fi mutat în mod arbitrar înainte și înapoi.
Timerul corect se derivă din serverStartedAt, remainingMs și starea RUNNING/PAUSED, sincronizate prin WebSocket/SSE; requestAnimationFrame sau interval client este strict afișaj. La reconectare se reface starea fără a reseta timpul. Moderația vocală anunță „Este rândul lui ...”, „Mai ai 30 de secunde”, „Timpul a expirat” și folosește texte predefinite TTS/Web Speech, cu fallback text; audio TTS este exclus sau marcat distinct în înregistrare.
24. Mod începător: sfaturi la începutul fiecărei runde
În ecranul pre-game: toggle „Ascult sfaturi la începutul fiecărei runde” (implicit OFF; se poate seta pentru fiecare jucător sau la nivel de meci). Sfaturile sunt redate înainte de pornirea ceasului, ideal cu text și buton „Sari peste”. Se folosesc șabloane aprobate de admin, nu apeluri OpenAI în timp real.
Exemple de șabloane: argument inițial: „Spune teza, două motive și un exemplu”; contraargument: „Identifică afirmația adversarului și explică unde nu funcționează”; întrebări: „Adresează întrebări scurte, legate de afirmațiile auzite”; concluzii: „Rezumați cele mai puternice două idei, fără dovezi complet noi”. Aceleași sfaturi structurale sunt disponibile atât pentru PRO, cât și pentru CONTRA. Opțiunea nu influențează arbitrajul.
25. Contracte și criterii de acceptare pentru noul flux
Date: Turn(id, matchId, phaseId, playerId, ordinal, startedAt, endedAt, remainingMs, state); AudioSegment(id, turnId, sequence, storageKey, checksum, durationMs, status, uploadedAt); ProcessingJob(id, segmentId, type, attempts, status, providerVersion); PartialFinding(segmentId, claimsJson, evidenceJson, rebuttalsJson, sourceOffsets); MatchProcessingStatus(matchId, totalSegments, uploaded, transcribed, pending, failed, finalEligible).
API/evente sugerate: POST /matches/:id/turns/:turnId/complete; POST /matches/:id/segments/init-upload; POST /segments/:id/confirm; GET /matches/:id/processing-status; WS match.turn.changed, audio.segment.accepted, transcript.segment.ready, match.processing.blocked, match.result.ready. Includem actor, server timestamp, expectedVersion și idempotencyKey.
Acceptance: (1) segmentul turei PRO începe upload în timp ce CONTRA vorbește; (2) dacă upload eșuează, CONTRA continuă și segmentul PRO se reîncearcă; (3) dublu tap nu sare două ture; (4) timerul mare evidențiază vorbitorul activ și se reface după reload; (5) opțiunea de sfaturi redă un mesaj scurt înaintea fiecărei faze, fără să consume timpul și fără sugestii părtinitoare; (6) lipsa unui segment blochează verdictul final, nu jocul în curs; (7) ambii jucători văd transcriptul și scorul final în cont, cu starea de procesare transparentă.
Decizie practică MVP: upload/transcriere imediat pe tură; arbitraj final unic OpenAI după terminarea jocului; evaluarea AI per tură se poate activa ulterior dacă latența sau cerințele de produs justifică extra costul. Analiza pe tură este asincronă și nu modifică ordine/timer în timpul rundei.
