Etapa 2: plan v3, rapoarte de cercetare si etalonul de regresie
Cercetare: ambele formate SAGA (VFP xls/xlsx si Firebird .FDB), cu dovezi fisier:linie in docs/raport_sursa_saga_xlsx.md si raport_exporturi_saga_noua.md. Plan v3 dupa review de strategie si arhitectura: contract intern + trei cititoare, mapare in doua fisiere cu proprietari diferiti, lane-uri. Etalon de regresie anonimizat in tests/golden/ (sume si structura neatinse, zero IBAN si zero cod fiscal real). Tabela de corespondenta ramane ignorata. Iesirile de productie ies din git (raman pe disc); .gitignore acopera si copiile de baze de client si iesirile intermediare ale convertorului. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYeAtVxeS8m4oXekjX8Am2
This commit is contained in:
120
tests/anonimizeaza_etalon.py
Normal file
120
tests/anonimizeaza_etalon.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Produce etalonul de regresie: copii anonimizate ale xlsx-urilor importate in productie.
|
||||
|
||||
Ce se pastreaza NEATINS: conturile, sumele, structura si numarul randurilor, datele, tipul
|
||||
randului. Asta este ce verifica poarta de regresie.
|
||||
Ce se inlocuieste:
|
||||
- pe randurile FACTURA: numele partenerului, codul fiscal, registrul comertului, adresa,
|
||||
explicatia;
|
||||
- ORIUNDE in fisier: IBAN-urile. Randurile BALANTA de pe 5121/5124 au in `nume` denumirea
|
||||
analiticului, care la conturile de banca este chiar IBAN-ul clientului
|
||||
(`RO73 BACX 0000 0008 7243 0001`). Restul denumirilor de cont (CAPITAL SOCIAL, REZULTAT
|
||||
REPORTAT, DEBITORI DIVERSI) sunt din planul de conturi si raman.
|
||||
|
||||
Inlocuirea e stabila: acelasi nume real primeste mereu acelasi nume fals, ca relatiile dintre
|
||||
randuri (mai multe facturi ale aceluiasi partener) sa se pastreze.
|
||||
|
||||
Tabela de corespondenta se scrie in tests/_corespondenta_anonimizare.json, care este IGNORAT de
|
||||
git. Nu o comite: cu ea, anonimizarea se poate desface.
|
||||
|
||||
Rulare: py tests\anonimizeaza_etalon.py
|
||||
"""
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import openpyxl
|
||||
|
||||
RADACINA = pathlib.Path(__file__).resolve().parent.parent
|
||||
GOLDEN = RADACINA / "tests" / "golden"
|
||||
CORESP = RADACINA / "tests" / "_corespondenta_anonimizare.json"
|
||||
|
||||
SURSE = [
|
||||
("init_FUNDATIA_2025_12.xlsx", "golden_FUNDATIA_2025_12.xlsx"),
|
||||
("init_MASTER_2025_12.xlsx", "golden_MASTER_2025_12.xlsx"),
|
||||
]
|
||||
|
||||
# coloana (1-based) -> cum se falsifica
|
||||
COL_NUME = 10
|
||||
COL_COD_FISCAL = 11
|
||||
COL_REG_COM = 12
|
||||
COL_ADRESA = 13
|
||||
COL_EXPLICATIA = 14
|
||||
COL_TIP = 5
|
||||
|
||||
# IBAN romanesc, cu sau fara spatii: RO + 2 cifre + 4 litere + 16 caractere alfanumerice
|
||||
IBAN = re.compile(r"RO\s?\d{2}(?:\s?[A-Z0-9]{4}){5}", re.IGNORECASE)
|
||||
|
||||
|
||||
def anonimizeaza():
|
||||
GOLDEN.mkdir(parents=True, exist_ok=True)
|
||||
coresp = {"nume": {}, "cod_fiscal": {}, "iban": {}}
|
||||
raport = []
|
||||
|
||||
for sursa, tinta in SURSE:
|
||||
cale = RADACINA / sursa
|
||||
if not cale.exists():
|
||||
raise SystemExit(
|
||||
"Lipseste %s. Etalonul nu se poate construi - vezi docs/handoff_etapa2_convertor.md"
|
||||
% sursa
|
||||
)
|
||||
shutil.copy(cale, GOLDEN / tinta)
|
||||
wb = openpyxl.load_workbook(GOLDEN / tinta)
|
||||
ws = wb.active
|
||||
schimbate = 0
|
||||
|
||||
for rand in range(2, ws.max_row + 1):
|
||||
if (ws.cell(rand, COL_TIP).value or "").strip().upper() != "FACTURA":
|
||||
continue
|
||||
schimbate += 1
|
||||
|
||||
nume = ws.cell(rand, COL_NUME).value
|
||||
if nume:
|
||||
if nume not in coresp["nume"]:
|
||||
coresp["nume"][nume] = "PARTENER %03d" % (len(coresp["nume"]) + 1)
|
||||
ws.cell(rand, COL_NUME).value = coresp["nume"][nume]
|
||||
|
||||
cf = ws.cell(rand, COL_COD_FISCAL).value
|
||||
if cf:
|
||||
cf = str(cf)
|
||||
if cf not in coresp["cod_fiscal"]:
|
||||
coresp["cod_fiscal"][cf] = "CF%06d" % (len(coresp["cod_fiscal"]) + 1)
|
||||
ws.cell(rand, COL_COD_FISCAL).value = coresp["cod_fiscal"][cf]
|
||||
|
||||
for col in (COL_REG_COM, COL_ADRESA, COL_EXPLICATIA):
|
||||
if ws.cell(rand, col).value:
|
||||
ws.cell(rand, col).value = None
|
||||
|
||||
# IBAN-urile apar si in afara randurilor FACTURA (denumirea analiticului de banca)
|
||||
ibanuri = 0
|
||||
for rand in ws.iter_rows():
|
||||
for celula in rand:
|
||||
if not isinstance(celula.value, str) or not IBAN.search(celula.value):
|
||||
continue
|
||||
def fals(m, _c=coresp):
|
||||
real = m.group(0)
|
||||
if real not in _c["iban"]:
|
||||
_c["iban"][real] = "RO00 IBAN %04d" % (len(_c["iban"]) + 1)
|
||||
return _c["iban"][real]
|
||||
|
||||
celula.value = IBAN.sub(fals, celula.value)
|
||||
ibanuri += 1
|
||||
|
||||
wb.save(GOLDEN / tinta)
|
||||
raport.append((tinta, ws.max_row - 1, schimbate, ibanuri))
|
||||
|
||||
CORESP.write_text(json.dumps(coresp, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
|
||||
for tinta, randuri, schimbate, ibanuri in raport:
|
||||
print(
|
||||
"%-34s %4d randuri, %3d FACTURA anonimizate, %2d celule cu IBAN"
|
||||
% (tinta, randuri, schimbate, ibanuri)
|
||||
)
|
||||
print(
|
||||
"%d nume, %d coduri fiscale si %d IBAN-uri inlocuite. Corespondenta: %s (ignorata de git)"
|
||||
% (len(coresp["nume"]), len(coresp["cod_fiscal"]), len(coresp["iban"]), CORESP.name)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
anonimizeaza()
|
||||
Reference in New Issue
Block a user