Skip to content

Commit 72ab591

Browse files
author
superpios
committed
feat: adapter Explorer->generatore (scripts/adapt_explorer.py) + test; README avvio rapido con adattamento
1 parent ec13dca commit 72ab591

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Nessuna pista dimostra, suggerisce o implica illecito, spreco, frode o responsab
2222
## Avvio rapido
2323
```bash
2424
pip install -r requirements.txt
25-
# Copia le tabelle relations/ dall'Explorer in data/input/
25+
# 1) Adatta le tabelle di relazione dell'Explorer nel formato atteso dal generatore
26+
python scripts/adapt_explorer.py --relations <EXPLORE>/data/relations --output data/input
27+
# 2) Applica le regole (deterministico, fail-closed)
2628
python scripts/apply_rules.py --input data/input --output data/leads --rules rules/rules_v0.1.yaml
2729
```
2830

scripts/adapt_explorer.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
"""Adattatore: trasforma le tabelle di relazione di investigative-explorer-dvns
3+
nel formato di input atteso da scripts/apply_rules.py.
4+
5+
Mappatura documentata e revisionabile (vedi REGOLE_SEGNALAZIONE.md):
6+
persona_incarico_ente__incarichi_nominativi_shard.csv
7+
-> person_name = subject_key, entity_id = object_key, year = period[:4]
8+
awards__affidamenti_diretti.csv
9+
-> awardee = subject_key, entity_id = object_key, award_date = period,
10+
procedure_type = "affidamento diretto" (il dataset e' gia' filtrato)
11+
cig_ente__affidamenti_diretti.csv
12+
-> cig = subject_key, subject_id = object_key
13+
14+
La provenienza (source_dataset, source_record_id) e' preservata cosi' com'e'.
15+
"""
16+
from __future__ import annotations
17+
18+
import argparse
19+
from pathlib import Path
20+
21+
import pandas as pd
22+
23+
SOURCES = {
24+
"persona_incarico_ente__incarichi_nominativi_shard.csv": "incarichi.csv",
25+
"awards__affidamenti_diretti.csv": "affidamenti_diretti.csv",
26+
"cig_ente__affidamenti_diretti.csv": "cig_enti.csv",
27+
}
28+
29+
30+
def _src(df: pd.DataFrame) -> dict[str, object]:
31+
return {
32+
"source_dataset": df.get("source_dataset", ""),
33+
"source_record_id": df.get("source_record_id", ""),
34+
}
35+
36+
37+
def adapt_persona(df: pd.DataFrame) -> pd.DataFrame:
38+
out = pd.DataFrame()
39+
out["person_name"] = df["subject_key"]
40+
out["entity_id"] = df["object_key"]
41+
out["year"] = df["period"].astype(str).str[:4]
42+
out["source_dataset"] = _src(df)["source_dataset"]
43+
out["source_record_id"] = _src(df)["source_record_id"]
44+
return out
45+
46+
47+
def adapt_awards(df: pd.DataFrame) -> pd.DataFrame:
48+
out = pd.DataFrame()
49+
out["awardee"] = df["subject_key"]
50+
out["entity_id"] = df["object_key"]
51+
out["award_date"] = df["period"].astype(str)
52+
out["procedure_type"] = "affidamento diretto"
53+
out["source_dataset"] = _src(df)["source_dataset"]
54+
out["source_record_id"] = _src(df)["source_record_id"]
55+
return out
56+
57+
58+
def adapt_cig(df: pd.DataFrame) -> pd.DataFrame:
59+
out = pd.DataFrame()
60+
out["cig"] = df["subject_key"]
61+
out["subject_id"] = df["object_key"]
62+
out["source_dataset"] = _src(df)["source_dataset"]
63+
out["source_record_id"] = _src(df)["source_record_id"]
64+
return out
65+
66+
67+
ADAPTERS = {
68+
"persona_incarico_ente__incarichi_nominativi_shard.csv": adapt_persona,
69+
"awards__affidamenti_diretti.csv": adapt_awards,
70+
"cig_ente__affidamenti_diretti.csv": adapt_cig,
71+
}
72+
73+
74+
def main() -> None:
75+
ap = argparse.ArgumentParser(description="Adatta le relazioni dell'Explorer al formato del generatore.")
76+
ap.add_argument("--relations", required=True, type=Path, help="Cartella data/relations dell'Explorer")
77+
ap.add_argument("--output", required=True, type=Path, help="Cartella data/input del generatore")
78+
args = ap.parse_args()
79+
args.output.mkdir(parents=True, exist_ok=True)
80+
for src, dst in SOURCES.items():
81+
p = args.relations / src
82+
if not p.exists():
83+
print(f"salto (assente): {src}")
84+
continue
85+
df = pd.read_csv(p, dtype=str, keep_default_na=False)
86+
out = ADAPTERS[src](df)
87+
out.to_csv(args.output / dst, index=False)
88+
print(f"{src} -> {dst}: {len(out)} righe")
89+
90+
91+
if __name__ == "__main__":
92+
main()

tests/test_adapter.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import sys
2+
from pathlib import Path
3+
4+
import pandas as pd
5+
6+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
7+
8+
import adapt_explorer # noqa: E402
9+
10+
11+
def test_adapt_persona():
12+
df = pd.DataFrame(
13+
[
14+
{
15+
"subject_key": "MARIO ROSSI",
16+
"object_key": "COMUNE X",
17+
"period": "2025-03-01",
18+
"source_dataset": "ds",
19+
"source_record_id": "r1",
20+
}
21+
]
22+
)
23+
out = adapt_explorer.adapt_persona(df)
24+
assert list(out.columns) == [
25+
"person_name",
26+
"entity_id",
27+
"year",
28+
"source_dataset",
29+
"source_record_id",
30+
]
31+
assert out.iloc[0]["person_name"] == "MARIO ROSSI"
32+
assert out.iloc[0]["entity_id"] == "COMUNE X"
33+
assert out.iloc[0]["year"] == "2025"
34+
35+
36+
def test_adapt_awards():
37+
df = pd.DataFrame(
38+
[
39+
{
40+
"subject_key": "AZ",
41+
"object_key": "ENT",
42+
"period": "2024-05-01",
43+
"source_dataset": "ds",
44+
"source_record_id": "r2",
45+
}
46+
]
47+
)
48+
out = adapt_explorer.adapt_awards(df)
49+
assert out.iloc[0]["awardee"] == "AZ"
50+
assert out.iloc[0]["entity_id"] == "ENT"
51+
assert out.iloc[0]["award_date"] == "2024-05-01"
52+
assert out.iloc[0]["procedure_type"] == "affidamento diretto"
53+
54+
55+
def test_adapt_cig():
56+
df = pd.DataFrame(
57+
[
58+
{
59+
"subject_key": "CIG1",
60+
"object_key": "ENT",
61+
"source_dataset": "ds",
62+
"source_record_id": "r3",
63+
}
64+
]
65+
)
66+
out = adapt_explorer.adapt_cig(df)
67+
assert out.iloc[0]["cig"] == "CIG1"
68+
assert out.iloc[0]["subject_id"] == "ENT"

0 commit comments

Comments
 (0)