Spaces:
Sleeping
Sleeping
Commit ·
e8072a2
1
Parent(s): 1b9c3f5
Added logic to not generate RAW file and limit Excel Master File to certain super large files
Browse files
app.py
CHANGED
|
@@ -69,6 +69,11 @@ MASTER_OUTPUT = "excel"
|
|
| 69 |
PROCESS_MODE = "stream"
|
| 70 |
STREAM_CHUNK_ROWS = 400_000 # rows per chunk while splitting the input by county
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
def _mem_mb():
|
| 74 |
"""Current process memory (RSS) in MB. psutil if available, else peak RSS."""
|
|
@@ -743,10 +748,11 @@ def run_pipeline(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 743 |
audit.to_csv(ap, index=False)
|
| 744 |
outputs.append(str(ap))
|
| 745 |
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
|
|
|
| 750 |
|
| 751 |
# ---- UNDETERMINED/NULL -> NaN ------------------------------------
|
| 752 |
for col in ["DDPPropertyCityFinal", "DDPPropertyZIPFinal",
|
|
@@ -1085,6 +1091,14 @@ def run_from_dataset(input_repo, output_repo):
|
|
| 1085 |
except Exception as e:
|
| 1086 |
log += (f"\nMaster build/upload FAILED -- the individual files are already "
|
| 1087 |
f"saved in the dataset, so nothing is lost: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1088 |
return log, outputs
|
| 1089 |
|
| 1090 |
|
|
@@ -1298,7 +1312,11 @@ def _split_by_county(uploaded_files, out_dir, log, chunksize=STREAM_CHUNK_ROWS):
|
|
| 1298 |
return county_paths[county]
|
| 1299 |
|
| 1300 |
def route(chunk):
|
| 1301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1302 |
for county, idx in chunk.groupby(keys).groups.items():
|
| 1303 |
p = path_for(county)
|
| 1304 |
chunk.loc[idx].to_csv(p, mode="a", header=not p.exists(), index=False)
|
|
@@ -1378,7 +1396,8 @@ def run_streaming(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 1378 |
blank_cities_not_in_list, cluster_threshold, replace_threshold)
|
| 1379 |
cluster_offset += res["n_clusters"]
|
| 1380 |
|
| 1381 |
-
|
|
|
|
| 1382 |
if len(res["audit"]) > 0:
|
| 1383 |
_append(res["audit"], audit_path); any_audit = True
|
| 1384 |
_append(res["final_cities"], fc_path)
|
|
@@ -1426,8 +1445,11 @@ def run_streaming(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 1426 |
|
| 1427 |
def _write_master_streaming(path, output_parts, audit_path, clu_path,
|
| 1428 |
fc_path, fz_path, fct_path, summary_df, row_limit=1_048_575):
|
| 1429 |
-
"""Build Master_Dataset.xlsx
|
| 1430 |
-
|
|
|
|
|
|
|
|
|
|
| 1431 |
try:
|
| 1432 |
import xlsxwriter
|
| 1433 |
except Exception:
|
|
@@ -1436,57 +1458,66 @@ def _write_master_streaming(path, output_parts, audit_path, clu_path,
|
|
| 1436 |
def _read(p):
|
| 1437 |
return pd.read_csv(p, low_memory=False) if Path(p).exists() else pd.DataFrame()
|
| 1438 |
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
output_df = pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
|
| 1445 |
-
write_master_workbook(path, [
|
| 1446 |
-
("Output_70_95_RapidFuzz", output_df),
|
| 1447 |
-
("ValidationAuditResults", _read(audit_path)),
|
| 1448 |
-
("cluster_df", _read(clu_path)),
|
| 1449 |
-
("final_cities", _read(fc_path)),
|
| 1450 |
-
("final_zips", _read(fz_path)),
|
| 1451 |
-
("final_census_tracts", _read(fct_path)),
|
| 1452 |
-
("Summary", summary_df),
|
| 1453 |
-
], row_limit=row_limit)
|
| 1454 |
-
return
|
| 1455 |
|
| 1456 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1457 |
|
| 1458 |
def _write_df(name, dfx):
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
-
|
| 1478 |
-
|
| 1479 |
-
|
| 1480 |
-
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
|
|
|
|
|
|
|
|
|
|
| 1488 |
_write_df("Summary", summary_df)
|
| 1489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1490 |
|
| 1491 |
|
| 1492 |
def build_master_bundle(out_dir, master_output):
|
|
|
|
| 69 |
PROCESS_MODE = "stream"
|
| 70 |
STREAM_CHUNK_ROWS = 400_000 # rows per chunk while splitting the input by county
|
| 71 |
|
| 72 |
+
# RawFile_FIPSValidated.csv is a full copy of the validated input (as big as the
|
| 73 |
+
# input itself). It's rarely used downstream and dominates the output/upload time
|
| 74 |
+
# on big states, so it's OFF by default. Set True if you actually need that file.
|
| 75 |
+
WRITE_RAWFILE = False
|
| 76 |
+
|
| 77 |
|
| 78 |
def _mem_mb():
|
| 79 |
"""Current process memory (RSS) in MB. psutil if available, else peak RSS."""
|
|
|
|
| 748 |
audit.to_csv(ap, index=False)
|
| 749 |
outputs.append(str(ap))
|
| 750 |
|
| 751 |
+
if WRITE_RAWFILE:
|
| 752 |
+
rp = out_dir / "RawFile_FIPSValidated.csv"
|
| 753 |
+
df.to_csv(rp, index=False)
|
| 754 |
+
outputs.append(str(rp))
|
| 755 |
+
_step(log, "Wrote RawFile_FIPSValidated.csv")
|
| 756 |
|
| 757 |
# ---- UNDETERMINED/NULL -> NaN ------------------------------------
|
| 758 |
for col in ["DDPPropertyCityFinal", "DDPPropertyZIPFinal",
|
|
|
|
| 1091 |
except Exception as e:
|
| 1092 |
log += (f"\nMaster build/upload FAILED -- the individual files are already "
|
| 1093 |
f"saved in the dataset, so nothing is lost: {e}")
|
| 1094 |
+
|
| 1095 |
+
# A clean, clickable list of every file in the dataset, so nobody has to hunt.
|
| 1096 |
+
if output_repo and outputs:
|
| 1097 |
+
base = f"https://huggingface.co/datasets/{output_repo}/blob/main/runs/{stamp}"
|
| 1098 |
+
links = "\n".join(f" - {Path(p).name}: {base}/{Path(p).name}" for p in outputs)
|
| 1099 |
+
log += (f"\n\n================ RESULTS (click to open) ================\n"
|
| 1100 |
+
f"Folder: {dest}\n{links}\n"
|
| 1101 |
+
f"=========================================================")
|
| 1102 |
return log, outputs
|
| 1103 |
|
| 1104 |
|
|
|
|
| 1312 |
return county_paths[county]
|
| 1313 |
|
| 1314 |
def route(chunk):
|
| 1315 |
+
# Key on the NORMALIZED 5-digit FIPS, not the raw text: chunked reading can
|
| 1316 |
+
# render the same code as '48001' in one chunk and '48001.0' in another
|
| 1317 |
+
# (float, from a chunk containing a blank), which would otherwise split a
|
| 1318 |
+
# county in two and cluster the halves separately.
|
| 1319 |
+
keys = chunk[fips_col].map(_norm_fips).fillna("UNKNOWN")
|
| 1320 |
for county, idx in chunk.groupby(keys).groups.items():
|
| 1321 |
p = path_for(county)
|
| 1322 |
chunk.loc[idx].to_csv(p, mode="a", header=not p.exists(), index=False)
|
|
|
|
| 1396 |
blank_cities_not_in_list, cluster_threshold, replace_threshold)
|
| 1397 |
cluster_offset += res["n_clusters"]
|
| 1398 |
|
| 1399 |
+
if WRITE_RAWFILE:
|
| 1400 |
+
_append(res["validated"], raw_path)
|
| 1401 |
if len(res["audit"]) > 0:
|
| 1402 |
_append(res["audit"], audit_path); any_audit = True
|
| 1403 |
_append(res["final_cities"], fc_path)
|
|
|
|
| 1445 |
|
| 1446 |
def _write_master_streaming(path, output_parts, audit_path, clu_path,
|
| 1447 |
fc_path, fz_path, fct_path, summary_df, row_limit=1_048_575):
|
| 1448 |
+
"""Build Master_Dataset.xlsx. Any result too big for a single Excel sheet
|
| 1449 |
+
(the full output on a big state, sometimes cluster_df) is NOT written into the
|
| 1450 |
+
workbook -- writing millions of Excel cells row-by-row is extremely slow and
|
| 1451 |
+
such a sheet isn't usable in Excel anyway. Those are left as their CSV files
|
| 1452 |
+
and listed on a READ_ME sheet. Small results are written normally."""
|
| 1453 |
try:
|
| 1454 |
import xlsxwriter
|
| 1455 |
except Exception:
|
|
|
|
| 1458 |
def _read(p):
|
| 1459 |
return pd.read_csv(p, low_memory=False) if Path(p).exists() else pd.DataFrame()
|
| 1460 |
|
| 1461 |
+
def _count_rows(p):
|
| 1462 |
+
if not Path(p).exists():
|
| 1463 |
+
return 0
|
| 1464 |
+
with open(p, "r", encoding="utf-8", errors="replace") as fh:
|
| 1465 |
+
return max(sum(1 for _ in fh) - 1, 0) # minus header
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1466 |
|
| 1467 |
+
notes = []
|
| 1468 |
+
if xlsxwriter is None:
|
| 1469 |
+
engine_kwargs = {}
|
| 1470 |
+
wb = None
|
| 1471 |
+
writer = pd.ExcelWriter(path, engine="openpyxl")
|
| 1472 |
+
else:
|
| 1473 |
+
wb = xlsxwriter.Workbook(str(path), {"constant_memory": True, "nan_inf_to_errors": True})
|
| 1474 |
+
writer = None
|
| 1475 |
|
| 1476 |
def _write_df(name, dfx):
|
| 1477 |
+
if wb is not None:
|
| 1478 |
+
ws = wb.add_worksheet(str(name)[:31])
|
| 1479 |
+
ws.write_row(0, 0, [str(c) for c in dfx.columns])
|
| 1480 |
+
clean = dfx.where(pd.notna(dfx), None)
|
| 1481 |
+
for r, rowvals in enumerate(clean.itertuples(index=False, name=None), start=1):
|
| 1482 |
+
ws.write_row(r, 0, rowvals)
|
| 1483 |
+
else:
|
| 1484 |
+
dfx.to_excel(writer, sheet_name=str(name)[:31], index=False)
|
| 1485 |
+
|
| 1486 |
+
def _sheet_or_note(name, path_, csv_label):
|
| 1487 |
+
n = _count_rows(path_)
|
| 1488 |
+
if n == 0:
|
| 1489 |
+
_write_df(name, _read(path_))
|
| 1490 |
+
elif n <= row_limit:
|
| 1491 |
+
_write_df(name, _read(path_))
|
| 1492 |
+
else:
|
| 1493 |
+
notes.append(f"{name}: {n:,} rows -- too many for one Excel sheet. See {csv_label}.")
|
| 1494 |
+
|
| 1495 |
+
# Full output: skip if it spans more than one 1M-row part (huge states).
|
| 1496 |
+
total_output = sum(_count_rows(p) for p in output_parts)
|
| 1497 |
+
if len(output_parts) == 1 and total_output <= row_limit:
|
| 1498 |
+
_write_df("Output_70_95_RapidFuzz", _read(output_parts[0]))
|
| 1499 |
+
elif output_parts:
|
| 1500 |
+
notes.append(f"Output_70_95_RapidFuzz: {total_output:,} rows across "
|
| 1501 |
+
f"{len(output_parts)} files -- too many for Excel. See the "
|
| 1502 |
+
f"OutputFile_...part_N.csv files.")
|
| 1503 |
+
|
| 1504 |
+
_sheet_or_note("cluster_df", clu_path, "cluster_df.csv")
|
| 1505 |
+
_sheet_or_note("ValidationAuditResults", audit_path, "FIPS_Validation_Audit_ChangedRecords.csv")
|
| 1506 |
+
_sheet_or_note("final_cities", fc_path, "final_cities.csv")
|
| 1507 |
+
_sheet_or_note("final_zips", fz_path, "final_zips.csv")
|
| 1508 |
+
_sheet_or_note("final_census_tracts", fct_path, "final_census_tracts.csv")
|
| 1509 |
_write_df("Summary", summary_df)
|
| 1510 |
+
|
| 1511 |
+
if notes:
|
| 1512 |
+
header = ("Some results are too large for an Excel sheet (Excel caps a sheet "
|
| 1513 |
+
"at 1,048,576 rows) and are provided as CSV files alongside this "
|
| 1514 |
+
"workbook:")
|
| 1515 |
+
_write_df("READ_ME", pd.DataFrame({"Note": [header] + notes}))
|
| 1516 |
+
|
| 1517 |
+
if wb is not None:
|
| 1518 |
+
wb.close()
|
| 1519 |
+
else:
|
| 1520 |
+
writer.close()
|
| 1521 |
|
| 1522 |
|
| 1523 |
def build_master_bundle(out_dir, master_output):
|