Files
rar-autopass/docs/prd/prd-5.17-tipuri-cont-planuri-trial.md
Claude Agent 8dd0e1678c docs(prd): 5.16 tipografie+bugfix editare + 5.17 tipuri cont + mockup-uri
PRD 5.16 (draft) — propagare design uniform peste aplicatie:
- fonturi standard web (system font stack), scala uniforma --fs-* (carduri aerisite)
- RAR online = dot in antet (datetime pe hover) + meniu burger; banda doar cand e blocat
- antet branded "ROMFAST AUTOPASS" + nume service + badge plan (gate is_authenticated)
- /login profesional (antet minimal pre-login), selector tema stil landing
- bug-uri editare: denumiri in picker, adaugare operatie extra, fix save no-op, fix Renunta
- dashboard compact: strip-less, contoare separate (mobil = bara numere), import colapsat,
  ordine carduri->import->tab-uri->lista, meniu cu separatoare
- wizard import (4 pasi) + editare/corectie aliniate la design

PRD 5.17 (draft) — tipuri de cont (Gratuit/Standard/Pro/Premium) + trial Pro 30 zile:
- model accounts.tier + trial_until, app/plans.py sursa unica
- enforcement DUR: limita Gratuit 60/luna (era 100) + API doar Pro+
- downgrade automat la expirare trial; aliniere landing (60, "Pro gratuit 30 zile")

Mockup-uri vizuale (docs/mockups/prd-5.16-*.html): fonturi, header+login+tema,
dashboard desktop+mobil, wizard import. Doar documentatie + mockup-uri; fara cod aplicatie.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:20:20 +00:00

21 KiB
Raw Blame History

PRD 5.17 — Tipuri de cont (planuri) + trial Pro 30 zile + enforcement

Stare: draft

Proces complet: docs/ROADMAP.md §5. Contract RAR (sursa de adevar): docs/api-rar-contract.md. Landing comercial cu planurile: app/web/templates/landing.html (sectiunea PRICING). Lifecycle cont existent: app/accounts.py, app/schema.sql (tabela accounts, coloana status). Signup: app/web/auth_routes.py (signup_post, butoanele landing trimit data-plan). Starea trece: draft -> aprobat -> in-executie -> verify-pass -> inchis.

1. Introducere

Landing-ul comercial promite patru planuri — Gratuit, Standard (39 lei), Pro (59 lei, cu API), Premium (la cerere) — si afirma ca fiecare cont incepe cu acces gratuit 30 de zile la un plan superior. In aplicatie insa nu exista deloc conceptul de tip de cont: tabela accounts are doar status (pending/active/blocked/archived/deleted) si on_unmapped_error_default. Nimic nu diferentiaza un cont gratuit de unul platit, nimic nu aplica limita de volum sau gate-ul de API, si nu exista niciun trial.

In plus, userul a decis doua corectii fata de landing-ul actual:

  1. Trial-ul de 30 de zile e pe Pro, NU pe Premium (landing-ul scrie azi "Premium gratuit 30 de zile" — gresit; trebuie "Pro 30 de zile").
  2. Limita planului Gratuit scade de la 100 la 60 de prestatii/luna — actualizata si in landing si in aplicatie.

5.17 introduce modelul de tipuri de cont, trial-ul Pro de 30 de zile, enforcement DUR al diferentelor (volum lunar + acces API), si downgrade automat la expirarea trial-ului. NU include integrare de plata (nu exista inca sistem de facturare) — alocarea planului platit ramane manuala (admin), iar trial-ul porneste automat la creare cont.

2. Obiective

Obiectiv principal

Aplicatia sa sustina real diferentele dintre planuri pe care landing-ul le promite: cont nou → trial Pro 30 zile → la expirare downgrade pe Gratuit (60/luna, fara API), cu enforcement efectiv.

Obiective secundare

  • Sursa unica de adevar pentru definitia planurilor (limite + capabilitati), consumata de backend si UI.
  • Mesaje oneste cand un cont atinge limita sau cere o capabilitate neinclusa (3 niveluri, ca 5.4).
  • Vizibilitate in dashboard: planul curent + zile ramase din trial + consum lunar.

Metrici de succes

  • Un cont Gratuit care depaseste 60 prestatii/luna primeste un raspuns clar de respingere (API + web), iar contoarele lunare se reseteaza corect la inceput de luna (timp local RO).
  • Un cont fara plan Pro+ primeste 403 onest pe /v1/* de import API.
  • Un cont nou are trial Pro activ; dupa 30 zile (sau setand trial_until in trecut in test) trece automat pe Gratuit, cu enforcement-ul aferent.
  • Landing + app afiseaza coerent "60 prestatii/luna" si "Pro gratuit 30 de zile".

3. User Stories

Database → backend → API → UI (ordinea dependentelor). Un singur autor pe accounts.py/schema.sql in valul de model.

US-001: Schema — accounts.tier + trial_until + definitia planurilor

Ca sistem vreau sa stiu planul fiecarui cont si pana cand e in trial pentru ca restul logicii depinde de asta.

  • Depinde de: —
  • Fisiere: app/schema.sql (coloane noi + migrare defensiva), app/accounts.py (helperi), app/plans.py (NOU — definitia planurilor, sursa de adevar), tests/test_accounts.py / tests/test_plans.py (~4 fisiere)
  • Test intai (RED): test_migrare_tier_trial_defensiva, test_plan_definitii, test_cont_nou_trial_pro_30z
  • Acceptance criteria:
    • accounts capata (migrare aditiva defensiva, ca email/status in 5.5/5.12): tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free','standard','pro','premium')) si trial_until TEXT (nullable; ISO datetime UTC sau NULL daca nu e in trial).
    • app/plans.py = SINGURA sursa de adevar: dict PLANS cu, per plan, {label, monthly_limit, api_access, ...}. Valori: freemonthly_limit=60, api_access=False; standardmonthly_limit=None (nelimitat), api_access=False; promonthly_limit=None, api_access=True; premiummonthly_limit=None, api_access=True. (Aliniat landing-ului, cu limita Gratuit 60.)
    • Helper effective_tier(account): daca trial_until e in viitor → randeaza ca pro (trial); altfel tier. (Trial-ul = acces Pro temporar peste tier-ul de baza free.)
    • create_account seteaza tier='free' si trial_until = now + 30 zile (trial Pro automat la creare). Contul implicit id=1 (dev) e exceptat / setat coerent (nu blocheaza dev-ul).
    • Migrare idempotenta (re-rulabila); conturile legacy fara tier primesc free + fara trial (sau trial calculat din created_at — decizie la executie; implicit: legacy → free fara trial).
  • Verificare E2E: creez cont nou → tier=free, trial_until ≈ now+30z, effective_tier=pro.

US-002: Numarator de consum lunar (prestatii/luna pe cont)

Ca sistem vreau sa stiu cate prestatii a trimis un cont in luna curenta pentru ca limita Gratuit (60/luna) se aplica pe acest numar.

  • Depinde de: US-001
  • Fisiere: app/accounts.py SAU app/plans.py (monthly_usage(conn, account_id)), tests/test_plans.py (~2 fisiere)
  • Test intai (RED): test_consum_lunar_numara_sent_si_queued, test_consum_lunar_timp_local_ro, test_consum_lunar_resetare_luna_noua
  • Acceptance criteria:
    • monthly_usage(conn, account_id) numara prestatiile contului in luna calendaristica curenta. Definitia "prestatie consumata" (de fixat la executie, propus): randuri submissions ale contului cu status in (queued,sending,sent) cu created_at in luna curenta — adica prestatiile ACCEPTATE in coada, nu cele respinse/blocate. (Justificare: limita e pe ce trimitem la RAR, nu pe incercari esuate.) Alternativ doar sent — de decis; implicit: acceptate-in-coada.
    • Timp local RO (ca E7 din 5.15): bucketarea lunii foloseste offset RO (created_at,'+3 hours' sau echivalent), nu UTC pur, ca prestatiile de la granita de luna sa cada corect. Test la granita.
    • Scoped strict pe cont (nu numara cross-account).
    • Fara coloana noua daca submissions.created_at ajunge (respecta non-goal migrare minima).
  • Verificare E2E: cont cu N trimiteri in luna → monthly_usage == N; luna urmatoare → reset la 0.

US-003: Enforcement DUR — limita lunara Gratuit (60) pe ambele canale

Ca owner vreau ca un cont Gratuit care depaseste 60 prestatii/luna sa fie oprit pentru ca asa sustinem diferenta de plan promisa.

  • Depinde de: US-001, US-002
  • Fisiere: app/api/v1/router.py (create_prezentari), app/api/v1/import_router.py (commit import), app/errors.py (cod nou PLAN_LIMITA_LUNARA), app/web/routes.py (commit web), tests/test_api_scope.py / tests/test_web_* / tests/test_plans.py (~6 fisiere)
  • Test intai (RED): test_free_peste_60_respins_api, test_free_peste_60_respins_import_web, test_pro_si_trial_nelimitat, test_eroare_3_niveluri_plan_limita
  • Acceptance criteria:
    • La enqueue (API POST /v1/prezentari + commit import web + commit import API), daca effective_tier are monthly_limit si monthly_usage + nr_cerut > monthly_limit → cererea e respinsa (sau respinsa partial, la limita) cu eroare 3 niveluri (app/errors.py, cod PLAN_LIMITA_LUNARA: problema "Ai atins limita planului Gratuit (60/luna)", cauza, fix "Treci pe Standard/Pro sau astepti luna viitoare"). NU se face enqueue peste limita.
    • standard/pro/premium si conturile in trial Pro → fara limita de volum.
    • Comportament la cerere de lot care depaseste partial limita (ex. 50 folosite, vin 20): decizie la executie — implicit RESPINGERE clara a intregului lot cu mesaj cat mai e disponibil ("mai poti trimite 10 luna asta"), NU enqueue partial tacut (evita surprize). De confirmat.
    • Enforcement aliniat cu AUTOPASS_REQUIRE_API_KEY (dev vs prod): in dev, contul id=1 nu e blocat artificial (trial/standard coerent), ca dogfooding-ul sa nu se loveasca de limita.
    • Idempotenta neatinsa: respingerea pe limita se face INAINTE de build_key/enqueue; un retry idempotent al unei prestatii deja acceptate nu consuma din nou cota.
  • Verificare E2E: cont free cu 60 trimise → a 61-a respinsa cu mesaj 3 niveluri (API si import web); cont pro → trece.

US-004: Enforcement DUR — gate API doar pe Pro/Premium

Ca owner vreau ca importul prin API sa fie disponibil doar pe Pro+ pentru ca landing-ul spune ca API-ul e o capabilitate Pro.

  • Depinde de: US-001
  • Fisiere: app/auth.py (sau dependinta de ruta), app/api/v1/router.py, app/api/v1/import_router.py, app/errors.py (cod PLAN_FARA_API), tests/test_api_scope.py (~5 fisiere)
  • Test intai (RED): test_free_fara_api_403, test_standard_fara_api_403, test_pro_api_ok, test_trial_pro_api_ok, test_dry_run_valideaza_ramane_permis
  • Acceptance criteria:
    • Rutele de import/ingestie prin API (POST /v1/prezentari, POST /v1/import, etc.) cer effective_tier.api_access == True (pro/premium sau trial Pro). Altfel 403 cu eroare 3 niveluri (PLAN_FARA_API: "Importul prin API e disponibil pe planul Pro", fix).
    • Canalul web ramane neafectat — operatorii pe plan gratuit pot folosi import xlsx/csv prin dashboard (asa promite landing-ul: Gratuit are import manual, NU API). Doar suprafata API e gated.
    • GET /v1/nomenclator ramane public (coduri RAR, fara PII) — invariant CLAUDE.md.
    • POST /v1/prezentari/valideaza (dry-run) — decizie: ramane permis pe orice plan (read-only, ajuta integrarea inainte de upgrade) SAU gated ca restul API. Implicit: PERMIS (read-only, fara enqueue). De confirmat.
    • In dev (AUTOPASS_REQUIRE_API_KEY=false), contul id=1 are acces API (tier coerent), ca testele API existente sa nu pice.
  • Verificare E2E: cheie API pe cont free → 403 onest pe import; cheie pe cont pro/trial → 200.

US-005: Downgrade automat la expirarea trial-ului

Ca owner vreau ca la expirarea celor 30 de zile contul sa treaca automat pe Gratuit pentru ca landing-ul spune "apoi trece automat pe Gratuit, fara plata".

  • Depinde de: US-001, US-003, US-004
  • Fisiere: app/plans.py (effective_tier deja trateaza expirarea — lazy), optional app/worker/__main__.py SAU un job de intretinere (eager), tests/test_plans.py (~3 fisiere)
  • Test intai (RED): test_trial_expirat_efective_free, test_trial_expirat_aplica_limita_60, test_trial_expirat_pierde_api
  • Acceptance criteria:
    • Lazy-first: effective_tier returneaza tier de baza (free) imediat ce trial_until <= now — fara job necesar pentru corectitudine (enforcement-ul US-003/004 se bazeaza pe effective_tier, deci downgrade-ul e automat la prima cerere dupa expirare).
    • Optional (eager, non-blocant): un pas in purjarea orara a worker-ului (T16 existent) poate normaliza trial_until expirat → NULL pentru igiena (NU obligatoriu pentru corectitudine).
    • Un cont cu tier='standard'/'pro'/'premium' setat de admin NU e downgradat de expirarea trial-ului (trial-ul e un BONUS peste free; un plan platit alocat persista).
    • Mesajele de limita/API dupa expirare sunt cele 3-niveluri din US-003/004.
  • Verificare E2E: setez trial_until in trecut → contul aplica limita 60 + pierde API, fara restart.

US-006: UI dashboard — plan curent + zile ramase din trial + consum lunar

Ca operator vreau sa vad pe ce plan sunt, cat mi-a mai ramas din trial si cat am consumat luna asta pentru ca vreau sa stiu cand ma apropii de limita.

  • Depinde de: US-001, US-002
  • Fisiere: app/web/routes.py (context), app/web/templates/_status.html SAU _cont.html (afisaj plan), tests/test_web_status.py / tests/test_dashboard.py (~4 fisiere)
  • Test intai (RED): test_afisaj_plan_si_zile_trial, test_afisaj_consum_lunar, test_avertizare_aproape_de_limita
  • Acceptance criteria:
    • Dashboard-ul afiseaza discret planul curent (ex. "Plan: Pro · trial 18 zile ramase" sau "Plan: Gratuit · 47/60 luna asta"). In trial → eticheta "trial" + zile ramase; pe Gratuit → consum N/60.
    • Plasare (aliniat cu PRD 5.16): planul apare ca badge in titlul din antet (Gratuit/Standard/Pro/Premium) SI ca linie in meniul burger ("Plan: [· trial N zile]"), nu doar intr-un card pe Acasa. Vezi mockup-urile 5.16 (docs/mockups/prd-5.16-dashboard.html / ...-mobil.html). 5.16 furnizeaza locul de afisare (antet + meniu); 5.17 furnizeaza datele (tier, trial, consum).
    • Avertizare vizuala cand consumul Gratuit se apropie de limita (ex. ≥80% → ton warn), fara a ingropa stripul de sanatate (zero-silent-failures pastrat).
    • Scoped pe cont; design conform 5.15/5.16 (tokeni, fonturi system, fara hex hardcodat).
    • Pagina "Cont" arata planul + (daca exista) o explicatie "cum trec pe alt plan" (contact, ca nu exista plata self-service inca).
  • Verificare E2E: cont trial → "trial N zile"; cont free aproape de 60 → avertizare; cont pro → fara contor de limita.

US-007: Aliniere landing — limita 60 + trial pe Pro (nu Premium)

Ca vizitator vreau ca landing-ul sa spuna adevarul pentru ca azi promite "100/luna" si "Premium gratuit 30 zile", dar realitatea va fi 60/luna si trial pe Pro.

  • Depinde de: — (copy-only; aliniaza cu modelul din US-001)
  • Fisiere: app/web/templates/landing.html, tests/test_web_* (~2 fisiere)
  • Test intai (RED): test_landing_limita_60, test_landing_trial_pro_nu_premium
  • Acceptance criteria:
    • Toate aparitiile "100 de prestatii/luna" / "100/luna" / meta description (landing.html:7,65,266 + oriunde apar) → 60. Inclusiv cardul Gratuit din sectiunea PRICING.
    • Textul "Fiecare cont incepe cu Premium gratuit 30 de zile" (landing.html:256) → "Pro gratuit 30 de zile" (planul corect). Restul frazei ("Apoi trece automat pe Gratuit…") ramane.
    • Coerenta: orice alt loc care implica trial/limita reflecta 60 + Pro.
    • Fara alte schimbari de pret/continut (39/59 lei raman).
  • Verificare E2E: landing in browser — "60 prestatii/luna" peste tot, "Pro gratuit 30 de zile".

US-008: Admin — alocare manuala de plan (fara plata self-service)

Ca admin vreau sa pot seta planul unui cont pentru ca nu exista inca facturare automata, dar trebuie sa pot acorda Standard/Pro/Premium.

  • Depinde de: US-001
  • Fisiere: tools/account.py (CLI set-tier), optional app/web/routes.py (/admin actiune), tests/test_accounts.py / tests/test_web_admin*.py (~3 fisiere)
  • Test intai (RED): test_cli_set_tier, test_admin_set_tier_scoped, test_tier_invalid_respins
  • Acceptance criteria:
    • CLI python3 -m tools.account set-tier --account N --tier pro [--trial-days 30|--no-trial] seteaza tier/trial_until. Tier invalid → eroare clara.
    • Optional (la executie): actiune in panoul /admin pentru a seta planul unui cont (scoped, CSRF, ca bulk-ul de status din 5.5). Daca nu intra in 5.17, CLI e suficient (admin-only).
    • Alocarea unui plan platit de catre admin NU e suprascrisa de expirarea trial-ului (US-005).
    • Audit: schimbarea de plan se logheaza in app_events (reuse jurnalul din 5.6), fara PII nou.
  • Verificare E2E: set-tier --account 2 --tier pro → contul 2 are API + volum nelimitat.

US-009: Teste de regresie + E2E plan/trial/enforcement

Ca dezvoltator vreau acoperire completa pentru ca enforcement-ul atinge ambele canale de ingestie si nu vreau sa blochez gresit conturi legitime.

  • Depinde de: US-003, US-004, US-005, US-006, US-007
  • Fisiere: tests/test_plans.py, tests/test_api_scope.py, tests/test_web_* (~3 fisiere)
  • Test intai (RED): matricea plan × capabilitate (volum, API) × canal (API, web) × trial activ/expirat.
  • Acceptance criteria:
    • python3 -m pytest -q -m "not live" verde; regresia de aur (POST /v1/prezentari → queued pe un cont cu drept) ramane verde.
    • Matrice testata: free(volum-blocat/API-blocat), standard(volum-ok/API-blocat), pro(ok/ok), trial-pro(ok/ok), trial-expirat(=free).
    • Contoarele lunare resetate la luna noua (test la granita timp local RO).
    • Dev (id=1) nu e blocat de enforcement (dogfooding).
  • Verificare E2E: rulare completa documentata in Raportul VERIFY.

4. Cerinte functionale (rezumat)

  1. [REQ-001] accounts.tier ∈ {free,standard,pro,premium} + trial_until; migrare aditiva defensiva.
  2. [REQ-002] app/plans.py = sursa unica: limite (free=60/luna) + capabilitati (API doar Pro+).
  3. [REQ-003] Cont nou → trial Pro 30 zile automat; effective_tier randeaza Pro in trial, free dupa.
  4. [REQ-004] Enforcement DUR: free peste 60/luna respins (API + import web) cu eroare 3 niveluri.
  5. [REQ-005] Enforcement DUR: import API gated pe Pro+ (403 onest); canalul web ramane liber.
  6. [REQ-006] Downgrade automat la expirare trial (lazy via effective_tier).
  7. [REQ-007] Dashboard arata plan + zile trial + consum lunar; landing aliniat (60, Pro).
  8. [REQ-008] Admin aloca planuri manual (CLI set-tier), audit in app_events.

5. Non-Goals (anti scope-creep)

  • Fara integrare de plata / facturare / abonamente (Stripe etc.) — alocarea platita = manuala (admin).
  • Fara self-service upgrade din UI (doar afisare plan + "contacteaza-ne"); plata vine intr-un PRD viitor.
  • Fara modificari pe backend-ul de trimitere (worker, masina de stari, idempotenta build_key, reconciliere, contract RAR). Enforcement-ul se face la ingestie/enqueue, INAINTE de coada.
  • Fara schimbarea capabilitatilor de produs in sine (sugestii/mapare exista deja pe toate planurile in cod; diferentierea 5.17 e pe VOLUM + ACCES API, exact ce promite landing-ul ca diferentiator hard).
  • Fara modificari de design (tipografia/temele sunt 5.16/5.15); doar reuse-ul stilurilor existente.

6. Consideratii tehnice

  • Stack: SQLite (migrare aditiva defensiva ca 5.5/5.12), FastAPI, Jinja2/HTMX.
  • Patterns de urmat: sursa unica (app/plans.py ca app/errors.py); eroare 3 niveluri (5.4); scope pe cont (5.15/US-011); timp local RO la bucketare (5.15/E7); audit app_events (5.6).
  • Riscuri:
    • Blocare gresita a unui cont legitim (enforcement prea agresiv) — risc de business. Mitigare: dev id=1 exceptat; teste matrice; mesaje 3 niveluri cu cale de iesire; respingere INAINTE de enqueue (nu pierde date).
    • Definitia "prestatie consumata" (acceptate-in-coada vs sent) schimba cand musca limita. Mitigare: o decidem explicit (US-002 AC) + test; documentam.
    • Granita de luna / fus orar — off-by-a-day la reset. Mitigare: timp local RO + test la granita (lectia E7 din 5.15).
    • Idempotenta vs cota — un retry idempotent nu trebuie sa consume cota de doua ori. Mitigare: enforce inainte de build_key; testul de retry.
    • Conturi legacy fara tier — migrare le pune free; un cont real activ ar putea fi limitat brusc la 60. Mitigare: decizie de migrare (legacy activ → ce plan?) confirmata cu user inainte de deploy.

7. Consideratii UI/UX

  • Afisaj plan discret, conform 5.16 (fonturi system, tokeni --fs-*, fara hex).
  • Stari: trial activ (zile ramase) / free (consum N/60, warn la ≥80%) / platit (fara contor limita).
  • Mesaje de respingere oneste, actionabile (cum trec pe alt plan), nu doar "403".

8. Open Questions

  • "Prestatie consumata" = acceptate-in-coada (queued+sending+sent) sau doar sent? (implicit: acceptate)
  • Lot care depaseste partial limita → respingere totala sau enqueue partial? (implicit: respingere totala clara)
  • POST /v1/prezentari/valideaza (dry-run) — gated pe Pro sau permis tuturor? (implicit: permis)
  • Migrare conturi legacy active: raman free (risc limitare brusca) sau primesc un trial/plan? (de confirmat cu user)
  • Standard (39 lei) si Premium difera de Pro doar prin API + suport in landing — pastram exact maparea de capabilitati din landing in plans.py? (implicit: da)

9. Valuri de executie

Val 1: [US-001]                  schema tier+trial + app/plans.py (autor unic schema/accounts)
Val 2: [US-002]                  numarator consum lunar (dupa model) ||
       [US-007]                  landing copy 60 + Pro (independent, copy-only)
Val 3: [US-003] [US-004] [US-005]  enforcement volum + API + downgrade (consuma plans.py)
Val 4: [US-006] [US-008]          UI dashboard plan/consum || admin set-tier
Val 5: [US-009]                   regresie + E2E matrice (dupa toate)

Secventiere fata de 5.16: independent (5.16 = design/tipografie; 5.17 = model de cont). Pot rula in paralel; doar US-006 (afisaj plan in _status.html) atinge un fisier pe care 5.16/US-003 il modifica (dot RAR) — serializeaza acel template daca ambele PRD-uri sunt in executie simultan.


Acest PRD nu a fost inca trecut prin /plan-ceo-review / /plan-eng-review. Recomandat inainte de executie (enforcement de business cu risc de blocare gresita + decizia de migrare a conturilor legacy).