feat(import): T16 job purjare + purge_after SET la sent (OV-5)

- mark(sent): seteaza purge_after = now + 90 zile (GDPR/L.142)
- purge_expired(conn): sterge submissions sent expirate + import_batches expirate
  (import_rows via ON DELETE CASCADE). NULL purge_after = nu expira.
- run(): tick de purjare odata pe ora (guard _last_purge_time + _PURGE_INTERVAL_S)
  NU mai agresiv, nu blocheaza trimiterea
- 8 teste: purge_after la sent, alte stari fara purge, expirati vs neexpirat,
  queued neatins, cascade import_rows, null purge_after pastrat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-16 20:24:59 +00:00
parent 8cdfc976e4
commit ef52dc2823
2 changed files with 246 additions and 4 deletions

View File

@@ -75,11 +75,43 @@ def _is_transient(exc: Exception) -> bool:
# --- Operatii pe submissions ---
def mark(conn, submission_id: int, status: str, *, rar_status_code=None, rar_error=None, id_prezentare=None) -> None:
conn.execute(
"UPDATE submissions SET status=?, rar_status_code=?, rar_error=?, id_prezentare=?, "
"sending_since=NULL, updated_at=datetime('now') WHERE id=?",
(status, rar_status_code, rar_error, id_prezentare, submission_id),
if status == "sent":
# T16: purge_after = sent + 90 zile (GDPR/L.142 retentie maxima).
conn.execute(
"UPDATE submissions SET status=?, rar_status_code=?, rar_error=?, id_prezentare=?, "
"sending_since=NULL, updated_at=datetime('now'), "
"purge_after=datetime('now', '+90 days') WHERE id=?",
(status, rar_status_code, rar_error, id_prezentare, submission_id),
)
else:
conn.execute(
"UPDATE submissions SET status=?, rar_status_code=?, rar_error=?, id_prezentare=?, "
"sending_since=NULL, updated_at=datetime('now') WHERE id=?",
(status, rar_status_code, rar_error, id_prezentare, submission_id),
)
# T16: purge interval in secunde (odata pe ora, nu prea agresiv)
_PURGE_INTERVAL_S = 3600
def purge_expired(conn) -> dict[str, int]:
"""Sterge randurile expirate (purge_after < now).
T16/OV-5: purge_after era exportat dar setat de nimeni si niciun job nu exista.
Acum: submissions sent + expirate, import_batches expirate (import_rows via CASCADE).
Intoarce {submissions_purged, batches_purged}.
"""
cur_sub = conn.execute(
"DELETE FROM submissions WHERE purge_after IS NOT NULL AND purge_after < datetime('now') AND status='sent'"
)
cur_batch = conn.execute(
"DELETE FROM import_batches WHERE purge_after IS NOT NULL AND purge_after < datetime('now')"
)
return {
"submissions_purged": cur_sub.rowcount,
"batches_purged": cur_batch.rowcount,
}
def requeue_with_backoff(conn, settings: Settings, submission_id: int, *, reason: str) -> None:
@@ -330,11 +362,24 @@ def run() -> int:
print(f"[worker] pornit (send_enabled={settings.worker_send_enabled}, env={settings.rar_env})", flush=True)
sessions = AccountSessions(settings)
_last_purge_time: float = 0.0
while _running:
try:
write_heartbeat(conn, detail=f"poll (queue={_queue_depth(conn)})")
# T16: purjare periodica (odata pe ora) — NU mai frecvent.
now_ts = time.time()
if now_ts - _last_purge_time >= _PURGE_INTERVAL_S:
stats = purge_expired(conn)
if stats["submissions_purged"] or stats["batches_purged"]:
print(
f"[worker] purjare: {stats['submissions_purged']} submissions, "
f"{stats['batches_purged']} batches sterse",
flush=True,
)
_last_purge_time = now_ts
if not settings.worker_send_enabled:
time.sleep(settings.worker_poll_interval_s)
continue