Spaces:
Sleeping
Sleeping
Commit ·
cc11853
1
Parent(s): aa04cd0
Enabled CSV type file as input
Browse files
app.py
CHANGED
|
@@ -50,6 +50,14 @@ CLUSTER_THRESHOLD = 95 # RapidFuzz similarity for LegalSubDivision cluste
|
|
| 50 |
REPLACE_THRESHOLD = 70 # majority-vote % required to fill a blank
|
| 51 |
FILTER_DOMINANT_COUNTY = False # process ALL counties (multi-county / full-state files)
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
def get_safety_list_path() -> str:
|
| 55 |
local_name = os.environ.get("SAFETY_LIST_FILE", DEFAULT_SAFETY_LIST_NAME)
|
|
@@ -472,6 +480,54 @@ def save_csv_in_parts(df, base_filename, out_dir, row_limit=1_000_000):
|
|
| 472 |
return created
|
| 473 |
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
# =====================================================================
|
| 476 |
# MAIN PIPELINE
|
| 477 |
# =====================================================================
|
|
@@ -500,14 +556,18 @@ def run_pipeline(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 500 |
except Exception as e:
|
| 501 |
return "\n".join(log) + f"\n\nCould not parse reference list: {e}", []
|
| 502 |
|
| 503 |
-
# ---- Load & merge uploads -------------------------
|
| 504 |
dfs = []
|
| 505 |
for f in uploaded_files:
|
|
|
|
| 506 |
try:
|
| 507 |
-
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
| 509 |
except Exception as e:
|
| 510 |
-
log.append(f"ERROR loading {Path(
|
| 511 |
if not dfs:
|
| 512 |
return "\n".join(log) + "\nNo files could be read.", []
|
| 513 |
df = pd.concat(dfs, ignore_index=True)
|
|
@@ -753,20 +813,31 @@ def run_pipeline(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 753 |
summary_df.to_csv(summary_path, index=False)
|
| 754 |
outputs.append(str(summary_path))
|
| 755 |
|
| 756 |
-
# ---- Master bundle:
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 770 |
outputs.append(str(master_path))
|
| 771 |
|
| 772 |
elapsed_min = (time.time() - t_start) / 60
|
|
@@ -813,9 +884,9 @@ with gr.Blocks() as demo:
|
|
| 813 |
with gr.Column(scale=1, elem_id="sidebar", min_width=270):
|
| 814 |
gr.Markdown("### Upload Files")
|
| 815 |
|
| 816 |
-
gr.HTML('<div class="step-label">1. Upload
|
| 817 |
-
files_in = gr.File(file_count="multiple", file_types=[".xlsx", ".xls"],
|
| 818 |
-
label="200MB per file • XLSX", show_label=True)
|
| 819 |
|
| 820 |
gr.HTML('<div class="step-label">2. Upload Reference File (Optional)</div>')
|
| 821 |
reference_in = gr.File(file_count="single", file_types=[".xlsx", ".csv"],
|
|
|
|
| 50 |
REPLACE_THRESHOLD = 70 # majority-vote % required to fill a blank
|
| 51 |
FILTER_DOMINANT_COUNTY = False # process ALL counties (multi-county / full-state files)
|
| 52 |
|
| 53 |
+
# Master bundle format:
|
| 54 |
+
# "excel" -> Master_Dataset.xlsx, every result on its own sheet (full output
|
| 55 |
+
# split across Output_..._1/_2/... if it exceeds Excel's row cap)
|
| 56 |
+
# "csv" -> Master_Dataset.zip, every result bundled as a CSV (faster: no Excel
|
| 57 |
+
# writing -- use this for very large / state-sized runs)
|
| 58 |
+
# The individual per-category files are CSV in both modes.
|
| 59 |
+
MASTER_OUTPUT = "excel"
|
| 60 |
+
|
| 61 |
|
| 62 |
def get_safety_list_path() -> str:
|
| 63 |
local_name = os.environ.get("SAFETY_LIST_FILE", DEFAULT_SAFETY_LIST_NAME)
|
|
|
|
| 480 |
return created
|
| 481 |
|
| 482 |
|
| 483 |
+
def write_master_workbook(path, sheets_in_order, row_limit=1_048_575):
|
| 484 |
+
"""Write a multi-sheet .xlsx from [(sheet_name, df), ...]. Any sheet larger than
|
| 485 |
+
Excel's 1,048,576-row cap is split into <name>_1, <name>_2, ... so nothing is
|
| 486 |
+
dropped. Uses xlsxwriter in constant_memory mode, driven row-by-row (low RAM --
|
| 487 |
+
important for the full-output sheet on state-sized files). We write the rows
|
| 488 |
+
ourselves because the pandas + constant_memory path corrupts rows that contain
|
| 489 |
+
NaN cells. Falls back to openpyxl if xlsxwriter isn't installed."""
|
| 490 |
+
try:
|
| 491 |
+
import xlsxwriter
|
| 492 |
+
except Exception:
|
| 493 |
+
xlsxwriter = None
|
| 494 |
+
|
| 495 |
+
if xlsxwriter is not None:
|
| 496 |
+
wb = xlsxwriter.Workbook(str(path), {"constant_memory": True, "nan_inf_to_errors": True})
|
| 497 |
+
|
| 498 |
+
def _write(name, dfx):
|
| 499 |
+
ws = wb.add_worksheet(str(name)[:31])
|
| 500 |
+
ws.write_row(0, 0, [str(c) for c in dfx.columns])
|
| 501 |
+
clean = dfx.where(pd.notna(dfx), None) # NaN -> blank cell
|
| 502 |
+
for r, rowvals in enumerate(clean.itertuples(index=False, name=None), start=1):
|
| 503 |
+
ws.write_row(r, 0, rowvals)
|
| 504 |
+
|
| 505 |
+
for name, dfx in sheets_in_order:
|
| 506 |
+
if dfx is None:
|
| 507 |
+
continue
|
| 508 |
+
n = len(dfx)
|
| 509 |
+
if n <= row_limit:
|
| 510 |
+
_write(name, dfx)
|
| 511 |
+
else:
|
| 512 |
+
for i in range(math.ceil(n / row_limit)):
|
| 513 |
+
_write(f"{name}_{i + 1}", dfx.iloc[i * row_limit:(i + 1) * row_limit])
|
| 514 |
+
gc.collect()
|
| 515 |
+
wb.close()
|
| 516 |
+
else:
|
| 517 |
+
with pd.ExcelWriter(path, engine="openpyxl") as writer:
|
| 518 |
+
for name, dfx in sheets_in_order:
|
| 519 |
+
if dfx is None:
|
| 520 |
+
continue
|
| 521 |
+
n = len(dfx)
|
| 522 |
+
if n <= row_limit:
|
| 523 |
+
dfx.to_excel(writer, sheet_name=str(name)[:31], index=False)
|
| 524 |
+
else:
|
| 525 |
+
for i in range(math.ceil(n / row_limit)):
|
| 526 |
+
dfx.iloc[i * row_limit:(i + 1) * row_limit].to_excel(
|
| 527 |
+
writer, sheet_name=f"{name}_{i + 1}"[:31], index=False)
|
| 528 |
+
return str(path)
|
| 529 |
+
|
| 530 |
+
|
| 531 |
# =====================================================================
|
| 532 |
# MAIN PIPELINE
|
| 533 |
# =====================================================================
|
|
|
|
| 556 |
except Exception as e:
|
| 557 |
return "\n".join(log) + f"\n\nCould not parse reference list: {e}", []
|
| 558 |
|
| 559 |
+
# ---- Load & merge uploads (Excel or CSV) -------------------------
|
| 560 |
dfs = []
|
| 561 |
for f in uploaded_files:
|
| 562 |
+
name = f.name
|
| 563 |
try:
|
| 564 |
+
if str(name).lower().endswith(".csv"):
|
| 565 |
+
dfs.append(pd.read_csv(name))
|
| 566 |
+
else:
|
| 567 |
+
dfs.append(pd.read_excel(name))
|
| 568 |
+
log.append(f"Loaded {Path(name).name}")
|
| 569 |
except Exception as e:
|
| 570 |
+
log.append(f"ERROR loading {Path(name).name}: {e}")
|
| 571 |
if not dfs:
|
| 572 |
return "\n".join(log) + "\nNo files could be read.", []
|
| 573 |
df = pd.concat(dfs, ignore_index=True)
|
|
|
|
| 813 |
summary_df.to_csv(summary_path, index=False)
|
| 814 |
outputs.append(str(summary_path))
|
| 815 |
|
| 816 |
+
# ---- Master bundle: Excel workbook OR zip of CSVs (see MASTER_OUTPUT) -
|
| 817 |
+
if MASTER_OUTPUT == "csv":
|
| 818 |
+
# Bundle the already-written CSVs into one zip. No Excel writing -> fast.
|
| 819 |
+
master_path = out_dir / "Master_Dataset.zip"
|
| 820 |
+
member_names = ["FIPS_Validation_Audit_ChangedRecords.csv", "cluster_df.csv",
|
| 821 |
+
"final_cities.csv", "final_zips.csv", "final_census_tracts.csv",
|
| 822 |
+
"Summary.csv"]
|
| 823 |
+
members = [out_dir / n for n in member_names if (out_dir / n).exists()]
|
| 824 |
+
members += sorted(out_dir.glob("OutputFile_*.csv")) # full output (chunked)
|
| 825 |
+
with zipfile.ZipFile(master_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 826 |
+
for p in members:
|
| 827 |
+
zf.write(p, arcname=p.name)
|
| 828 |
+
else:
|
| 829 |
+
# Every result on its own sheet; the full output splits across
|
| 830 |
+
# Output_70_95_RapidFuzz_1, _2, ... if it exceeds Excel's row cap.
|
| 831 |
+
master_path = out_dir / "Master_Dataset.xlsx"
|
| 832 |
+
write_master_workbook(master_path, [
|
| 833 |
+
("Output_70_95_RapidFuzz", output_df),
|
| 834 |
+
("ValidationAuditResults", audit),
|
| 835 |
+
("cluster_df", cluster_df),
|
| 836 |
+
("final_cities", final_cities),
|
| 837 |
+
("final_zips", final_zips),
|
| 838 |
+
("final_census_tracts", final_ct),
|
| 839 |
+
("Summary", summary_df),
|
| 840 |
+
])
|
| 841 |
outputs.append(str(master_path))
|
| 842 |
|
| 843 |
elapsed_min = (time.time() - t_start) / 60
|
|
|
|
| 884 |
with gr.Column(scale=1, elem_id="sidebar", min_width=270):
|
| 885 |
gr.Markdown("### Upload Files")
|
| 886 |
|
| 887 |
+
gr.HTML('<div class="step-label">1. Upload Files</div>')
|
| 888 |
+
files_in = gr.File(file_count="multiple", file_types=[".xlsx", ".xls", ".csv"],
|
| 889 |
+
label="200MB per file • XLSX, XLS, CSV", show_label=True)
|
| 890 |
|
| 891 |
gr.HTML('<div class="step-label">2. Upload Reference File (Optional)</div>')
|
| 892 |
reference_in = gr.File(file_count="single", file_types=[".xlsx", ".csv"],
|