"""MP1 · paso 42: compara cada caso de la muestra del cotejo (paso 41) con su entrada ATOM original (paso 40). Imagen etl:v2 (solo biblioteca estándar y csv). Escribe /w/interno/cotejo/comparacion.csv (campo a campo: nuestro valor, el del ATOM, coincide) para la revisión caso a caso; el veredicto final lo pone la revisión (cotejo_resultado.csv). """ import csv, json, re import xml.etree.ElementTree as ET from pathlib import Path D = Path("/w/interno/cotejo") NS = {"cbc": "urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2", "cac": "urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2", "cacx": "urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2", "cbcx": "urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonBasicComponents-2", "a": "http://www.w3.org/2005/Atom"} CAB = ('' % (NS["cbc"], NS["cac"], NS["cacx"], NS["cbcx"])) TIPO = {"1": "Suministros", "2": "Servicios", "3": "Obras", "21": "Gestion_servicios_publicos", "22": "Concesion_servicios", "31": "Concesion_obras", "32": "Concesion_obras", "7": "Administrativo_especial", "8": "Privado", "50": "Patrimonial"} PROC = {"1": "Abierto", "2": "Restringido", "3": "Negociado_sin_publicidad", "4": "Negociado_publicidad", "5": "Dialogo_competitivo", "6": "Contrato_menor", "7": "Derivado_acuerdo_marco", "8": "Concurso_proyectos", "9": "Abierto_simplificado", "10": "Asociacion_innovacion", "11": "Derivado_asociacion_innovacion", "12": "Basado_sistema_dinamico", "13": "Licitacion_con_negociacion", "100": "Normas_internas", "999": "Otros"} def norm(s): s = (s or "").lower() for a, b in zip("áéíóúüñàèìòùç", "aeiouunaeiouc"): s = s.replace(a, b) return re.sub(r"[^a-z0-9]+", " ", s).strip() def parse(xml): e = ET.fromstring(xml.replace("", CAB, 1)) cfs = e.find("cacx:ContractFolderStatus", NS) g = lambda path: (cfs.find(path, NS).text if cfs is not None and cfs.find(path, NS) is not None else None) party = cfs.find("cacx:LocatedContractingParty/cac:Party", NS) ids = {i.get("schemeName"): i.text for i in party.findall("cac:PartyIdentification/cbc:ID", NS)} if party is not None else {} out = {"organo": party.find("cac:PartyName/cbc:Name", NS).text if party is not None else None, "nif": ids.get("NIF"), "dir3": ids.get("DIR3"), "expediente": g("cbc:ContractFolderID"), "estado": g("cbcx:ContractFolderStatusCode"), "tipo": TIPO.get(g("cac:ProcurementProject/cbc:TypeCode") or "", g("cac:ProcurementProject/cbc:TypeCode")), "cpv": g("cac:ProcurementProject/cac:RequiredCommodityClassification/cbc:ItemClassificationCode"), "pbl_exp": g("cac:ProcurementProject/cac:BudgetAmount/cbc:TaxExclusiveAmount"), "ve": g("cac:ProcurementProject/cac:BudgetAmount/cbc:EstimatedOverallContractAmount"), "procedimiento": PROC.get(g("cac:TenderingProcess/cbc:ProcedureCode") or "", g("cac:TenderingProcess/cbc:ProcedureCode")), "fin_ofertas": g("cac:TenderingProcess/cac:TenderSubmissionDeadlinePeriod/cbc:EndDate"), "updated": e.find("a:updated", NS).text} lotes = {} for lt in cfs.findall("cac:ProcurementProjectLot", NS): lid = lt.find("cbc:ID", NS).text lotes[lid] = {"cpv": (lt.find("cac:ProcurementProject/cac:RequiredCommodityClassification/cbc:ItemClassificationCode", NS).text if lt.find("cac:ProcurementProject/cac:RequiredCommodityClassification/cbc:ItemClassificationCode", NS) is not None else None), "pbl": (lt.find("cac:ProcurementProject/cac:BudgetAmount/cbc:TaxExclusiveAmount", NS).text if lt.find("cac:ProcurementProject/cac:BudgetAmount/cbc:TaxExclusiveAmount", NS) is not None else None)} out["n_lotes"] = len(lotes) res = [] for tr in cfs.findall("cac:TenderResult", NS): lot = tr.find("cac:AwardedTenderedProject/cbc:ProcurementProjectLotID", NS) res.append({"lote": lot.text if lot is not None else None, "codigo": (tr.find("cbc:ResultCode", NS).text if tr.find("cbc:ResultCode", NS) is not None else None), "ofertas": (tr.find("cbc:ReceivedTenderQuantity", NS).text if tr.find("cbc:ReceivedTenderQuantity", NS) is not None else None), "fecha": (tr.find("cbc:AwardDate", NS).text if tr.find("cbc:AwardDate", NS) is not None else None)}) out["resultados"] = res pubs = [] for v in cfs.findall("cacx:ValidNoticeInfo", NS): tipo = v.find("cbcx:NoticeTypeCode", NS) for d in v.findall(".//cbc:IssueDate", NS): pubs.append(((tipo.text if tipo is not None else None), d.text)) out["publicaciones"] = pubs mods = [] for m in cfs.iter(): if m.tag.endswith("ContractModification"): txt = {c.tag.split("}")[-1]: (c.text or "").strip() for c in m.iter() if c.text and c.text.strip()} mods.append(txt) out["modificaciones"] = mods out["lotes"] = lotes return out muestra = list(csv.DictReader(open(D / "muestra.csv"))) filas = [] for r in muestra: x = D / "xml" / f"{r['clave']}.xml" a = parse(x.read_text(encoding="utf-8")) if x.exists() else None lote = None if r.get("lote_u") and r.get("licitacion_id") and r["lote_u"] != r["licitacion_id"]: lote = r["lote_u"].rsplit("|", 1)[-1] if "|" in r["lote_u"] else r["lote_u"].split("/")[-1] f = {"clave": r["clave"], "grupo": r["grupo"], "licitacion_id": r.get("licitacion_id"), "lote_u": r.get("lote_u"), "en_atom": a is not None} if a: f.update({"organo_nuestro": r.get("organo_nom") or "", "organo_atom": norm(a["organo"]), "nif_atom": a["nif"], "cpv_nuestro": r.get("cpv_div"), "cpv_atom": (a["lotes"].get(lote, {}).get("cpv") if lote else a["cpv"]), "tipo_nuestro": r.get("tipo_contrato"), "tipo_atom": a["tipo"], "proc_nuestro": r.get("proc_nat"), "proc_atom": a["procedimiento"], "pbl_nuestro": r.get("pbl") or r.get("pbl_lote") or r.get("pbl_exp"), "pbl_atom": (a["lotes"].get(lote, {}).get("pbl") if lote else a["pbl_exp"]), "fecha_pub_nuestra": r.get("fecha_pub"), "publicaciones_atom": json.dumps(a["publicaciones"]), "ffo_nuestro": r.get("ffo"), "ffo_atom": a["fin_ofertas"], "nlotes_nuestro": r.get("n_lotes_an"), "nlotes_atom": a["n_lotes"], "ofertas_nuestro": r.get("num_ofertas"), "desierto_nuestro": r.get("des") or r.get("y_des"), "resultados_atom": json.dumps(a["resultados"], ensure_ascii=False), "y_mod": r.get("y_mod"), "fecha_adj_nuestra": r.get("fecha_adj"), "modificaciones_atom": json.dumps(a["modificaciones"], ensure_ascii=False)[:1500], "estado_atom": a["estado"], "updated_atom": a["updated"], "expediente_atom": a["expediente"], "y_rec": r.get("y_rec")}) filas.append(f) cols = sorted({k for f in filas for k in f}, key=lambda k: list(filas[0].keys()).index(k) if k in filas[0] else 99) with open(D / "comparacion.csv", "w", newline="") as fh: w = csv.DictWriter(fh, fieldnames=cols); w.writeheader(); [w.writerow(f) for f in filas] print(len(filas), sum(f["en_atom"] for f in filas))