feat(import): T7 batch_id scope reresolve_account — R1 INCHIS

- reresolve_account(conn, account_id, batch_id=None):
  - batch_id specificat -> scope la batch-ul exact (import commit explicit)
  - fara batch_id (POST /v1/mapari) -> EXCLUSIV canal API (batch_id IS NULL)
  - salvarea unei mapari NU mai re-queues randuri cross-batch (R1 inchis)
- 6 teste: izolare batch A/B, regresie API canal, batch explicit nu atinge API,
  schema batch_id/row_index, 3 batches izolate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-16 20:23:11 +00:00
parent 61a7b4ea1c
commit 8cdfc976e4
2 changed files with 204 additions and 3 deletions

View File

@@ -298,16 +298,20 @@ def reresolve_account(conn, account_id: int | None, batch_id: int | None = None)
mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()} mapping = {op: meta["cod_prestatie"] for op, meta in mapping_meta.items()}
if batch_id is not None: if batch_id is not None:
# T7: scope la batch-ul specificat # T7: scope la batch-ul specificat (import commit explicit).
# NU atinge randuri din alte batches sau din feed API.
rows = conn.execute( rows = conn.execute(
"SELECT id, payload_json FROM submissions " "SELECT id, payload_json FROM submissions "
"WHERE status='needs_mapping' AND account_id=? AND batch_id=?", "WHERE status='needs_mapping' AND account_id=? AND batch_id=?",
(acct, batch_id), (acct, batch_id),
).fetchall() ).fetchall()
else: else:
# Canal API (batch_id IS NULL) + legacy (batch_id nesetat) # POST /v1/mapari (save manual): re-rezolva EXCLUSIV canalul API (batch_id IS NULL).
# T7/R1 INCHIS: salvarea unei mapari NU re-queues randuri din batches de import
# (cross-batch / cross-feed). Batches de import sunt re-rezolvate doar la commit explicit.
rows = conn.execute( rows = conn.execute(
"SELECT id, payload_json FROM submissions WHERE status='needs_mapping' AND account_id=?", "SELECT id, payload_json FROM submissions "
"WHERE status='needs_mapping' AND account_id=? AND batch_id IS NULL",
(acct,), (acct,),
).fetchall() ).fetchall()

View File

@@ -0,0 +1,197 @@
"""Teste T7: batch_id/row_index scope reresolve_account (R1 INCHIS).
Verify:
(a) salvare mapare in batch A NU trimite randuri din batch B / feed API.
(b) canal API (batch_id NULL) tot se re-rezolva ca azi (regresie).
"""
from __future__ import annotations
import json
import os
import tempfile
import pytest
@pytest.fixture()
def env(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t7.db"))
from app.config import get_settings
get_settings.cache_clear()
from app.db import init_db
init_db()
yield monkeypatch
get_settings.cache_clear()
@pytest.fixture()
def conn(env):
from app.db import get_connection
c = get_connection()
yield c
c.close()
def _insert_batch(conn, account_id=1):
"""Creeaza un import_batch si returneaza id-ul."""
cur = conn.execute(
"INSERT INTO import_batches (account_id, filename, status) VALUES (?, ?, 'staging')",
(account_id, "test.xlsx"),
)
return int(cur.lastrowid)
def _insert_submission(conn, account_id=1, batch_id=None, cod_op="ITP-1", key_sfx=None):
"""Insereaza un submission needs_mapping (cu sau fara batch)."""
content = {
"vin": "WVWZZZ1KZAW000123", "nr_inmatriculare": "B1",
"data_prestatie": "2026-06-15", "odometru_final": "123456",
"prestatii": [{"cod_op_service": cod_op, "denumire": "Test"}],
}
sfx = key_sfx or os.urandom(4).hex()
if batch_id is not None:
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, batch_id) "
"VALUES (?, ?, 'needs_mapping', ?, ?)",
(f"k-{sfx}", account_id, json.dumps(content), batch_id),
)
else:
cur = conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json) "
"VALUES (?, ?, 'needs_mapping', ?)",
(f"k-{sfx}", account_id, json.dumps(content)),
)
return int(cur.lastrowid)
def _add_mapping(conn, account_id=1, cod_op="ITP-1", cod_prestatie="OE-1", auto_send=True):
conn.execute(
"INSERT OR IGNORE INTO nomenclator_rar (cod_prestatie, nume_prestatie) VALUES (?, ?)",
(cod_prestatie, "Test operatie"),
)
conn.execute(
"INSERT OR REPLACE INTO operations_mapping (account_id, cod_op_service, cod_prestatie, auto_send) "
"VALUES (?, ?, ?, ?)",
(account_id, cod_op, cod_prestatie, 1 if auto_send else 0),
)
# --- Scoping ---
def test_reresolve_batch_specific_nu_atinge_alt_batch(conn):
"""(a) reresolve_account cu batch_id=A nu atinge randuri din batch_id=B."""
from app.mapping import reresolve_account
batch_a = _insert_batch(conn)
batch_b = _insert_batch(conn)
sid_a = _insert_submission(conn, batch_id=batch_a, cod_op="ITP-1", key_sfx="a1")
sid_b = _insert_submission(conn, batch_id=batch_b, cod_op="ITP-1", key_sfx="b1")
_add_mapping(conn, cod_op="ITP-1", cod_prestatie="OE-1", auto_send=True)
# Re-rezolva NUMAI batch_a
stats = reresolve_account(conn, 1, batch_id=batch_a)
assert stats["requeued"] == 1
# Batch B nemodificat
row_a = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_a,)).fetchone()
row_b = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_b,)).fetchone()
assert row_a["status"] == "queued", "batch A trebuie requeued"
assert row_b["status"] == "needs_mapping", "batch B trebuie sa ramana needs_mapping"
def test_reresolve_fara_batch_nu_atinge_batches(conn):
"""(a) reresolve fara batch (POST /v1/mapari) NU atinge batch submissions."""
from app.mapping import reresolve_account
batch_a = _insert_batch(conn)
sid_batch = _insert_submission(conn, batch_id=batch_a, cod_op="ITP-1", key_sfx="ba")
sid_api = _insert_submission(conn, batch_id=None, cod_op="ITP-1", key_sfx="ap")
_add_mapping(conn, cod_op="ITP-1", cod_prestatie="OE-1", auto_send=True)
# Fara batch (cum apeleaza POST /v1/mapari)
stats = reresolve_account(conn, 1)
assert stats["requeued"] == 1 # numai API canal
row_batch = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_batch,)).fetchone()
row_api = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_api,)).fetchone()
assert row_batch["status"] == "needs_mapping", "batch submission NU trebuie atins de reresolve global"
assert row_api["status"] == "queued", "API canal trebuie requeued"
def test_reresolve_canal_api_regresie(conn):
"""(b) Canal API (batch_id NULL) tot se re-rezolva ca azi."""
from app.mapping import reresolve_account
# Doua submission-uri API fara batch
sid1 = _insert_submission(conn, batch_id=None, cod_op="ITP-2", key_sfx="r1")
sid2 = _insert_submission(conn, batch_id=None, cod_op="ITP-2", key_sfx="r2")
_add_mapping(conn, cod_op="ITP-2", cod_prestatie="OE-1", auto_send=True)
stats = reresolve_account(conn, 1) # fara batch — re-rezolva tot API
assert stats["requeued"] == 2
for sid in (sid1, sid2):
row = conn.execute("SELECT status FROM submissions WHERE id=?", (sid,)).fetchone()
assert row["status"] == "queued"
def test_reresolve_batch_explicit_nu_atinge_api(conn):
"""Batch explicit: nu atinge feed API (batch_id IS NULL)."""
from app.mapping import reresolve_account
batch_a = _insert_batch(conn)
sid_batch = _insert_submission(conn, batch_id=batch_a, cod_op="ITP-3", key_sfx="ba3")
sid_api = _insert_submission(conn, batch_id=None, cod_op="ITP-3", key_sfx="ap3")
_add_mapping(conn, cod_op="ITP-3", cod_prestatie="OE-1", auto_send=True)
stats = reresolve_account(conn, 1, batch_id=batch_a)
assert stats["requeued"] == 1
row_batch = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_batch,)).fetchone()
row_api = conn.execute("SELECT status FROM submissions WHERE id=?", (sid_api,)).fetchone()
assert row_batch["status"] == "queued"
assert row_api["status"] == "needs_mapping", "API canal nu trebuie atins de reresolve batch-specific"
def test_submissions_au_batch_id_si_row_index(conn):
"""Schema: submissions.batch_id si .row_index exista si se pot seta."""
batch_id = _insert_batch(conn)
conn.execute(
"INSERT INTO submissions (idempotency_key, account_id, status, payload_json, batch_id, row_index) "
"VALUES (?, ?, 'queued', '{}', ?, ?)",
("k-test-bi", 1, batch_id, 5),
)
row = conn.execute("SELECT batch_id, row_index FROM submissions WHERE idempotency_key='k-test-bi'").fetchone()
assert row["batch_id"] == batch_id
assert row["row_index"] == 5
def test_reresolve_multiple_batches_izolate(conn):
"""R1 INCHIS: 3 batches, fiecare re-rezolvat independent."""
from app.mapping import reresolve_account
batches = [_insert_batch(conn) for _ in range(3)]
sids = {
b: _insert_submission(conn, batch_id=b, cod_op="ITP-4", key_sfx=f"mb{i}")
for i, b in enumerate(batches)
}
_add_mapping(conn, cod_op="ITP-4", cod_prestatie="OE-1", auto_send=True)
# Re-rezolva batch 0, verifica ca 1 si 2 nu sunt atinse
reresolve_account(conn, 1, batch_id=batches[0])
statuses = {
b: conn.execute("SELECT status FROM submissions WHERE id=?", (sid,)).fetchone()["status"]
for b, sid in sids.items()
}
assert statuses[batches[0]] == "queued"
assert statuses[batches[1]] == "needs_mapping"
assert statuses[batches[2]] == "needs_mapping"