Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -39,15 +39,6 @@ def _coerce_numeric(x):
|
|
| 39 |
try: return float(s)
|
| 40 |
except: return pd.NA
|
| 41 |
|
| 42 |
-
# ---- NEW: in‑memory file helpers (no disk writes) ----
|
| 43 |
-
def _csv_bytes(df: pd.DataFrame | None) -> bytes:
|
| 44 |
-
buf = io.StringIO()
|
| 45 |
-
(df if df is not None else pd.DataFrame()).to_csv(buf, index=False)
|
| 46 |
-
return buf.getvalue().encode("utf-8")
|
| 47 |
-
|
| 48 |
-
def _text_bytes(txt: str | None) -> bytes:
|
| 49 |
-
return (txt or "").encode("utf-8")
|
| 50 |
-
|
| 51 |
PARAM_PATTERNS = {
|
| 52 |
"cost": r"cost\s*[:=]\s*\$?\s*([\d,]+(?:\.\d+)?)",
|
| 53 |
"salvage": r"salvage\s*[:=]\s*\$?\s*([\d,]+(?:\.\d+)?)",
|
|
@@ -250,42 +241,31 @@ def handle_docx(file):
|
|
| 250 |
# )
|
| 251 |
|
| 252 |
def handle_image(img):
|
| 253 |
-
"""OCR → table (raw + normalized) → keep types safe for Gradio."""
|
| 254 |
-
from PIL import Image as PILImage
|
| 255 |
-
|
| 256 |
-
# If nothing uploaded, return safe empties for every output
|
| 257 |
if img is None:
|
| 258 |
-
|
| 259 |
-
yr = pd.Timestamp.now().year
|
| 260 |
-
return ("(no image)", {}, empty, empty, 0.0, 0.0, 10, yr, {}, empty)
|
| 261 |
|
| 262 |
-
|
| 263 |
pil = img if isinstance(img, PILImage.Image) else PILImage.fromarray(img)
|
| 264 |
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
| 267 |
|
| 268 |
-
# 2) Try to pick params out of the OCR text
|
| 269 |
-
params = _extract_params(ocr_text)
|
| 270 |
cost, salv, life, year = _params_tuple(params)
|
| 271 |
|
| 272 |
-
# 3) Parse a table from OCR text
|
| 273 |
-
raw = _table_from_ocr_text(ocr_text or "")
|
| 274 |
-
|
| 275 |
-
# 🔒 IMPORTANT: never return None to gr.Dataframe
|
| 276 |
-
raw_df = raw if isinstance(raw, pd.DataFrame) else pd.DataFrame()
|
| 277 |
-
norm_df = _normalize_depr_columns(raw_df) if not raw_df.empty else pd.DataFrame()
|
| 278 |
-
|
| 279 |
return (
|
| 280 |
-
|
| 281 |
-
params,
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
cost, salv, life, year, #
|
| 285 |
-
params,
|
| 286 |
-
|
| 287 |
)
|
| 288 |
|
|
|
|
|
|
|
| 289 |
def fill_from_state(p):
|
| 290 |
p = p or {}
|
| 291 |
return (
|
|
@@ -311,7 +291,7 @@ def check_cb(cost, salv, life, year, table_state):
|
|
| 311 |
if not isinstance(table_state, pd.DataFrame) or table_state.empty:
|
| 312 |
return pd.DataFrame(), "No student table found to check."
|
| 313 |
|
| 314 |
-
# re-normalize and coerce here every time
|
| 315 |
actual = _normalize_depr_columns(table_state)
|
| 316 |
for c in ["Year", "Begin BV", "Depreciation", "Accum Dep", "End BV"]:
|
| 317 |
actual[c] = pd.to_numeric(actual[c], errors="coerce")
|
|
@@ -342,16 +322,7 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 342 |
ocr_txt = gr.Textbox(label="Raw OCR text", lines=12)
|
| 343 |
params_json2 = gr.JSON(label="Detected parameters")
|
| 344 |
raw_df = gr.Dataframe(label="Raw table guess", interactive=False)
|
| 345 |
-
|
| 346 |
-
norm_df = gr.Dataframe(label="Detected table (normalized)", interactive=True)
|
| 347 |
-
|
| 348 |
-
# ---- NEW: in‑memory downloads ----
|
| 349 |
-
raw_dl = gr.DownloadButton(label="Download RAW CSV")
|
| 350 |
-
norm_dl = gr.DownloadButton(label="Download NORMALIZED CSV")
|
| 351 |
-
txt_dl = gr.DownloadButton(label="Download OCR TEXT")
|
| 352 |
-
|
| 353 |
-
# re‑export after manual edits
|
| 354 |
-
export_btn = gr.DownloadButton(label="Download EDITED NORMALIZED CSV")
|
| 355 |
|
| 356 |
# --- Tab 3: Solve & Check ---
|
| 357 |
with gr.Tab("Straight-Line • Solve & Check"):
|
|
@@ -391,9 +362,6 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 391 |
params_json2, # json
|
| 392 |
raw_df, # raw table
|
| 393 |
norm_df, # normalized table (tab 2)
|
| 394 |
-
raw_dl, # NEW: bytes -> RAW CSV download
|
| 395 |
-
norm_dl, # NEW: bytes -> NORMALIZED CSV download
|
| 396 |
-
txt_dl, # NEW: bytes -> OCR TEXT download
|
| 397 |
in_cost, in_salv, in_life, in_year, # autofill inputs
|
| 398 |
last_params, # state
|
| 399 |
last_table, # state
|
|
@@ -410,12 +378,6 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 410 |
btn_build.click(build_cb, [in_cost, in_salv, in_life, in_year], [expected_df])
|
| 411 |
btn_check.click(check_cb, [in_cost, in_salv, in_life, in_year, last_table], [deltas_df, coach_txt])
|
| 412 |
|
| 413 |
-
# ---- NEW: export edited normalized CSV ----
|
| 414 |
-
def export_current(norm_df_current: pd.DataFrame):
|
| 415 |
-
return _csv_bytes(norm_df_current if isinstance(norm_df_current, pd.DataFrame) else pd.DataFrame())
|
| 416 |
-
|
| 417 |
-
export_btn.click(export_current, inputs=norm_df, outputs=export_btn)
|
| 418 |
-
|
| 419 |
gr.Markdown("— Echo mode finished. When this looks good, we’ll plug in the SL solver + coaching.")
|
| 420 |
|
| 421 |
if __name__ == "__main__":
|
|
|
|
| 39 |
try: return float(s)
|
| 40 |
except: return pd.NA
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
PARAM_PATTERNS = {
|
| 43 |
"cost": r"cost\s*[:=]\s*\$?\s*([\d,]+(?:\.\d+)?)",
|
| 44 |
"salvage": r"salvage\s*[:=]\s*\$?\s*([\d,]+(?:\.\d+)?)",
|
|
|
|
| 241 |
# )
|
| 242 |
|
| 243 |
def handle_image(img):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
if img is None:
|
| 245 |
+
return "(no image)", {}, pd.DataFrame(), pd.DataFrame(), 0.0, 0.0, 10, pd.Timestamp.now().year, {}, pd.DataFrame()
|
|
|
|
|
|
|
| 246 |
|
| 247 |
+
from PIL import Image as PILImage
|
| 248 |
pil = img if isinstance(img, PILImage.Image) else PILImage.fromarray(img)
|
| 249 |
|
| 250 |
+
ocr_text = _image_to_text(pil)
|
| 251 |
+
params = _extract_params(ocr_text or "")
|
| 252 |
+
df_raw = _table_from_ocr_text(ocr_text or "")
|
| 253 |
+
df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else pd.DataFrame()
|
| 254 |
|
|
|
|
|
|
|
| 255 |
cost, salv, life, year = _params_tuple(params)
|
| 256 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
return (
|
| 258 |
+
ocr_text or "(empty OCR)",
|
| 259 |
+
params,
|
| 260 |
+
df_raw, # raw table shown in OCR tab
|
| 261 |
+
df_norm, # normalized table shown in OCR tab
|
| 262 |
+
cost, salv, life, year, # auto-fill numbers
|
| 263 |
+
params, # save params state
|
| 264 |
+
df_norm # 🔹 save normalized table to last_table (same as docx)
|
| 265 |
)
|
| 266 |
|
| 267 |
+
|
| 268 |
+
|
| 269 |
def fill_from_state(p):
|
| 270 |
p = p or {}
|
| 271 |
return (
|
|
|
|
| 291 |
if not isinstance(table_state, pd.DataFrame) or table_state.empty:
|
| 292 |
return pd.DataFrame(), "No student table found to check."
|
| 293 |
|
| 294 |
+
# 👇 Gradio returns strings → re-normalize and coerce here every time
|
| 295 |
actual = _normalize_depr_columns(table_state)
|
| 296 |
for c in ["Year", "Begin BV", "Depreciation", "Accum Dep", "End BV"]:
|
| 297 |
actual[c] = pd.to_numeric(actual[c], errors="coerce")
|
|
|
|
| 322 |
ocr_txt = gr.Textbox(label="Raw OCR text", lines=12)
|
| 323 |
params_json2 = gr.JSON(label="Detected parameters")
|
| 324 |
raw_df = gr.Dataframe(label="Raw table guess", interactive=False)
|
| 325 |
+
norm_df = gr.Dataframe(label="Detected table (normalized)", interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
# --- Tab 3: Solve & Check ---
|
| 328 |
with gr.Tab("Straight-Line • Solve & Check"):
|
|
|
|
| 362 |
params_json2, # json
|
| 363 |
raw_df, # raw table
|
| 364 |
norm_df, # normalized table (tab 2)
|
|
|
|
|
|
|
|
|
|
| 365 |
in_cost, in_salv, in_life, in_year, # autofill inputs
|
| 366 |
last_params, # state
|
| 367 |
last_table, # state
|
|
|
|
| 378 |
btn_build.click(build_cb, [in_cost, in_salv, in_life, in_year], [expected_df])
|
| 379 |
btn_check.click(check_cb, [in_cost, in_salv, in_life, in_year, last_table], [deltas_df, coach_txt])
|
| 380 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
gr.Markdown("— Echo mode finished. When this looks good, we’ll plug in the SL solver + coaching.")
|
| 382 |
|
| 383 |
if __name__ == "__main__":
|