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