saulsolis09 commited on
Commit
cc11853
·
1 Parent(s): aa04cd0

Enabled CSV type file as input

Browse files
Files changed (1) hide show
  1. app.py +92 -21
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
- dfs.append(pd.read_excel(f.name))
508
- log.append(f"Loaded {Path(f.name).name}")
 
 
 
509
  except Exception as e:
510
- log.append(f"ERROR loading {Path(f.name).name}: {e}")
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: one ZIP of CSVs (no Excel row limit) ---------
757
- # A single CSV can hold only one table, so the "master workbook" becomes a
758
- # ZIP that bundles every result as its own CSV -- including the full output
759
- # (chunked into parts), which no longer hits Excel's 1,048,576-row ceiling.
760
- # CSV writing also avoids openpyxl's heavy memory use on huge files.
761
- master_path = out_dir / "Master_Dataset.zip"
762
- member_names = ["FIPS_Validation_Audit_ChangedRecords.csv", "cluster_df.csv",
763
- "final_cities.csv", "final_zips.csv", "final_census_tracts.csv",
764
- "Summary.csv"]
765
- members = [out_dir / n for n in member_names if (out_dir / n).exists()]
766
- members += sorted(out_dir.glob("OutputFile_*.csv")) # full output (chunked)
767
- with zipfile.ZipFile(master_path, "w", zipfile.ZIP_DEFLATED) as zf:
768
- for p in members:
769
- zf.write(p, arcname=p.name)
 
 
 
 
 
 
 
 
 
 
 
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 Excel Files</div>')
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"],