saulsolis09 commited on
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
Files changed (1) hide show
  1. app.py +86 -55
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
- rp = out_dir / "RawFile_FIPSValidated.csv"
747
- df.to_csv(rp, index=False)
748
- outputs.append(str(rp))
749
- _step(log, "Wrote RawFile_FIPSValidated.csv")
 
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
- keys = chunk[fips_col].astype(str).str.strip()
 
 
 
 
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
- _append(res["validated"], raw_path)
 
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 in low memory: stream the Output sheet from the
1430
- combined output CSV(s) in chunks; load the (small) side sheets fully."""
 
 
 
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
- if xlsxwriter is None:
1440
- # openpyxl fallback (higher memory): load Output fully.
1441
- frames = []
1442
- for p in output_parts:
1443
- frames.append(pd.read_csv(p, low_memory=False))
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
- wb = xlsxwriter.Workbook(str(path), {"constant_memory": True, "nan_inf_to_errors": True})
 
 
 
 
 
 
 
1457
 
1458
  def _write_df(name, dfx):
1459
- ws = wb.add_worksheet(str(name)[:31])
1460
- ws.write_row(0, 0, [str(c) for c in dfx.columns])
1461
- clean = dfx.where(pd.notna(dfx), None)
1462
- for r, rowvals in enumerate(clean.itertuples(index=False, name=None), start=1):
1463
- ws.write_row(r, 0, rowvals)
1464
-
1465
- # Output sheet(s): stream from the combined CSV, split at row_limit
1466
- sheet_idx, row_in_sheet, ws, header = 0, 0, None, None
1467
- for p in output_parts:
1468
- for chunk in pd.read_csv(p, chunksize=200_000, low_memory=False):
1469
- if header is None:
1470
- header = [str(c) for c in chunk.columns]
1471
- clean = chunk.where(pd.notna(chunk), None)
1472
- for rowvals in clean.itertuples(index=False, name=None):
1473
- if ws is None or row_in_sheet >= row_limit:
1474
- sheet_idx += 1
1475
- ws = wb.add_worksheet(f"Output_70_95_RapidFuzz_{sheet_idx}"[:31])
1476
- ws.write_row(0, 0, header)
1477
- row_in_sheet = 0
1478
- row_in_sheet += 1
1479
- ws.write_row(row_in_sheet, 0, rowvals)
1480
- if ws is None: # no output rows at all
1481
- wb.add_worksheet("Output_70_95_RapidFuzz").write_row(0, 0, header or ["(empty)"])
1482
-
1483
- _write_df("ValidationAuditResults", _read(audit_path))
1484
- _write_df("cluster_df", _read(clu_path))
1485
- _write_df("final_cities", _read(fc_path))
1486
- _write_df("final_zips", _read(fz_path))
1487
- _write_df("final_census_tracts", _read(fct_path))
 
 
 
1488
  _write_df("Summary", summary_df)
1489
- wb.close()
 
 
 
 
 
 
 
 
 
 
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):