Files
solduri2roa/tests/anonimizeaza_etalon.py
Marius Mutu 15bb26ac15 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
2026-09-21 15:44:57 +03:00

121 lines
4.4 KiB
Python

"""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()