Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
4fbbaed
1
Parent(s): 1548940
Added Delimiter-Sniffer Logic
Browse files
app.py
CHANGED
|
@@ -56,7 +56,7 @@ FILTER_DOMINANT_COUNTY = False # process ALL counties (multi-county / full-stat
|
|
| 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 = "
|
| 60 |
|
| 61 |
# How records are processed:
|
| 62 |
# "stream" -> ONE COUNTY AT A TIME. The input is split into per-county temp
|
|
@@ -117,10 +117,49 @@ def get_safety_list_path() -> str:
|
|
| 117 |
_SAFETY_CACHE: dict | None = None
|
| 118 |
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
def build_whitelist_from_path(path):
|
| 121 |
"""Parse a safety-list file (xlsx or csv) into {pairs, cities, rows, fips_name}."""
|
| 122 |
if str(path).lower().endswith(".csv"):
|
| 123 |
-
safety_df =
|
| 124 |
else:
|
| 125 |
safety_df = pd.read_excel(path)
|
| 126 |
acceptable_cols = [c for c in safety_df.columns if str(c).startswith("Acceptable FIPS")]
|
|
@@ -661,10 +700,13 @@ def run_pipeline(uploaded_files, reference_file, blank_cities_not_in_list,
|
|
| 661 |
name = f.name
|
| 662 |
try:
|
| 663 |
if str(name).lower().endswith(".csv"):
|
| 664 |
-
|
|
|
|
|
|
|
|
|
|
| 665 |
else:
|
| 666 |
dfs.append(pd.read_excel(name))
|
| 667 |
-
|
| 668 |
except Exception as e:
|
| 669 |
_step(log, f"ERROR loading {Path(name).name}: {e}")
|
| 670 |
if not dfs:
|
|
@@ -1235,7 +1277,7 @@ def _split_by_county(uploaded_files, out_dir, log, chunksize=STREAM_CHUNK_ROWS):
|
|
| 1235 |
# detect the FIPS-county column from a small sample of the first file
|
| 1236 |
first = uploaded_files[0].name
|
| 1237 |
if str(first).lower().endswith(".csv"):
|
| 1238 |
-
sample = pd.read_csv(first, nrows=50_000, low_memory=False)
|
| 1239 |
else:
|
| 1240 |
sample = pd.read_excel(first)
|
| 1241 |
fips_col, reason = detect_fips_county_column(sample)
|
|
@@ -1263,7 +1305,9 @@ def _split_by_county(uploaded_files, out_dir, log, chunksize=STREAM_CHUNK_ROWS):
|
|
| 1263 |
for f in uploaded_files:
|
| 1264 |
nm = f.name
|
| 1265 |
if str(nm).lower().endswith(".csv"):
|
| 1266 |
-
|
|
|
|
|
|
|
| 1267 |
route(chunk)
|
| 1268 |
total += len(chunk)
|
| 1269 |
_step(log, f"Split: routed {total:,} rows into {len(county_paths)} counties")
|
|
|
|
| 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 |
# How records are processed:
|
| 62 |
# "stream" -> ONE COUNTY AT A TIME. The input is split into per-county temp
|
|
|
|
| 117 |
_SAFETY_CACHE: dict | None = None
|
| 118 |
|
| 119 |
|
| 120 |
+
def detect_delimiter(path, candidates=(",", "|", "^", "\t", ";")):
|
| 121 |
+
"""Sniff a text file's field delimiter from its first lines. SQL exports here
|
| 122 |
+
are sometimes '^' or '|' rather than ','. Returns the delimiter char (default
|
| 123 |
+
','). Detects once from a small header sample so the actual (possibly chunked)
|
| 124 |
+
read can still use pandas' fast C parser with an explicit sep."""
|
| 125 |
+
try:
|
| 126 |
+
with open(path, "r", encoding="utf-8", errors="replace", newline="") as fh:
|
| 127 |
+
lines = []
|
| 128 |
+
for _ in range(8):
|
| 129 |
+
ln = fh.readline()
|
| 130 |
+
if not ln:
|
| 131 |
+
break
|
| 132 |
+
ln = ln.rstrip("\r\n")
|
| 133 |
+
if ln:
|
| 134 |
+
lines.append(ln)
|
| 135 |
+
except Exception:
|
| 136 |
+
return ","
|
| 137 |
+
if not lines:
|
| 138 |
+
return ","
|
| 139 |
+
best, best_score = ",", -1
|
| 140 |
+
for d in candidates:
|
| 141 |
+
header_count = lines[0].count(d)
|
| 142 |
+
if header_count == 0:
|
| 143 |
+
continue
|
| 144 |
+
# reward a high, consistent field count across the sampled rows
|
| 145 |
+
consistent = sum(1 for ln in lines if ln.count(d) == header_count)
|
| 146 |
+
score = header_count * 100 + consistent
|
| 147 |
+
if score > best_score:
|
| 148 |
+
best, best_score = d, score
|
| 149 |
+
return best
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _read_csv_auto(path, **kwargs):
|
| 153 |
+
"""pd.read_csv with the delimiter auto-detected (unless caller passed sep)."""
|
| 154 |
+
if "sep" not in kwargs:
|
| 155 |
+
kwargs["sep"] = detect_delimiter(path)
|
| 156 |
+
return pd.read_csv(path, **kwargs)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
def build_whitelist_from_path(path):
|
| 160 |
"""Parse a safety-list file (xlsx or csv) into {pairs, cities, rows, fips_name}."""
|
| 161 |
if str(path).lower().endswith(".csv"):
|
| 162 |
+
safety_df = _read_csv_auto(path)
|
| 163 |
else:
|
| 164 |
safety_df = pd.read_excel(path)
|
| 165 |
acceptable_cols = [c for c in safety_df.columns if str(c).startswith("Acceptable FIPS")]
|
|
|
|
| 700 |
name = f.name
|
| 701 |
try:
|
| 702 |
if str(name).lower().endswith(".csv"):
|
| 703 |
+
sep = detect_delimiter(name)
|
| 704 |
+
dfs.append(pd.read_csv(name, low_memory=False, sep=sep))
|
| 705 |
+
_step(log, f"Loaded {Path(name).name} ({len(dfs[-1]):,} rows, "
|
| 706 |
+
f"delimiter {sep!r})")
|
| 707 |
else:
|
| 708 |
dfs.append(pd.read_excel(name))
|
| 709 |
+
_step(log, f"Loaded {Path(name).name} ({len(dfs[-1]):,} rows)")
|
| 710 |
except Exception as e:
|
| 711 |
_step(log, f"ERROR loading {Path(name).name}: {e}")
|
| 712 |
if not dfs:
|
|
|
|
| 1277 |
# detect the FIPS-county column from a small sample of the first file
|
| 1278 |
first = uploaded_files[0].name
|
| 1279 |
if str(first).lower().endswith(".csv"):
|
| 1280 |
+
sample = pd.read_csv(first, nrows=50_000, low_memory=False, sep=detect_delimiter(first))
|
| 1281 |
else:
|
| 1282 |
sample = pd.read_excel(first)
|
| 1283 |
fips_col, reason = detect_fips_county_column(sample)
|
|
|
|
| 1305 |
for f in uploaded_files:
|
| 1306 |
nm = f.name
|
| 1307 |
if str(nm).lower().endswith(".csv"):
|
| 1308 |
+
sep = detect_delimiter(nm)
|
| 1309 |
+
_step(log, f"Reading {Path(nm).name} (delimiter {sep!r})")
|
| 1310 |
+
for chunk in pd.read_csv(nm, chunksize=chunksize, low_memory=False, sep=sep):
|
| 1311 |
route(chunk)
|
| 1312 |
total += len(chunk)
|
| 1313 |
_step(log, f"Split: routed {total:,} rows into {len(county_paths)} counties")
|