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+)?)",
|
|
@@ -251,8 +242,7 @@ def handle_docx(file):
|
|
| 251 |
|
| 252 |
def handle_image(img):
|
| 253 |
if img is None:
|
| 254 |
-
|
| 255 |
-
return "(no image)", {}, pd.DataFrame(), pd.DataFrame(), b"", b"", b"", 0.0, 0.0, 10, pd.Timestamp.now().year, {}, pd.DataFrame()
|
| 256 |
|
| 257 |
from PIL import Image as PILImage
|
| 258 |
pil = img if isinstance(img, PILImage.Image) else PILImage.fromarray(img)
|
|
@@ -262,17 +252,6 @@ def handle_image(img):
|
|
| 262 |
df_raw = _table_from_ocr_text(ocr_text or "")
|
| 263 |
df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else pd.DataFrame()
|
| 264 |
|
| 265 |
-
# numerics (helps when you edit later)
|
| 266 |
-
if not df_norm.empty:
|
| 267 |
-
for c in ["Year","Begin BV","Depreciation","Accum Dep","End BV"]:
|
| 268 |
-
if c in df_norm.columns:
|
| 269 |
-
df_norm[c] = pd.to_numeric(df_norm[c], errors="coerce")
|
| 270 |
-
|
| 271 |
-
# in‑memory artifacts
|
| 272 |
-
raw_csv_bytes = _csv_bytes(df_raw)
|
| 273 |
-
norm_csv_bytes = _csv_bytes(df_norm)
|
| 274 |
-
txt_bytes = _text_bytes(ocr_text or "")
|
| 275 |
-
|
| 276 |
cost, salv, life, year = _params_tuple(params)
|
| 277 |
|
| 278 |
return (
|
|
@@ -280,14 +259,13 @@ def handle_image(img):
|
|
| 280 |
params,
|
| 281 |
df_raw, # raw table shown in OCR tab
|
| 282 |
df_norm, # normalized table shown in OCR tab
|
| 283 |
-
raw_csv_bytes, # bytes for RAW CSV download
|
| 284 |
-
norm_csv_bytes, # bytes for NORMALIZED CSV download
|
| 285 |
-
txt_bytes, # bytes for OCR TEXT download
|
| 286 |
cost, salv, life, year, # auto-fill numbers
|
| 287 |
params, # save params state
|
| 288 |
-
df_norm # save normalized table to last_table
|
| 289 |
)
|
| 290 |
|
|
|
|
|
|
|
| 291 |
def fill_from_state(p):
|
| 292 |
p = p or {}
|
| 293 |
return (
|
|
@@ -313,7 +291,7 @@ def check_cb(cost, salv, life, year, table_state):
|
|
| 313 |
if not isinstance(table_state, pd.DataFrame) or table_state.empty:
|
| 314 |
return pd.DataFrame(), "No student table found to check."
|
| 315 |
|
| 316 |
-
# re-normalize and coerce here every time
|
| 317 |
actual = _normalize_depr_columns(table_state)
|
| 318 |
for c in ["Year", "Begin BV", "Depreciation", "Accum Dep", "End BV"]:
|
| 319 |
actual[c] = pd.to_numeric(actual[c], errors="coerce")
|
|
@@ -344,16 +322,7 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 344 |
ocr_txt = gr.Textbox(label="Raw OCR text", lines=12)
|
| 345 |
params_json2 = gr.JSON(label="Detected parameters")
|
| 346 |
raw_df = gr.Dataframe(label="Raw table guess", interactive=False)
|
| 347 |
-
|
| 348 |
-
norm_df = gr.Dataframe(label="Detected table (normalized)", interactive=True)
|
| 349 |
-
|
| 350 |
-
# ---- NEW: in‑memory downloads ----
|
| 351 |
-
raw_dl = gr.DownloadButton(label="Download RAW CSV")
|
| 352 |
-
norm_dl = gr.DownloadButton(label="Download NORMALIZED CSV")
|
| 353 |
-
txt_dl = gr.DownloadButton(label="Download OCR TEXT")
|
| 354 |
-
|
| 355 |
-
# re‑export after manual edits
|
| 356 |
-
export_btn = gr.DownloadButton(label="Download EDITED NORMALIZED CSV")
|
| 357 |
|
| 358 |
# --- Tab 3: Solve & Check ---
|
| 359 |
with gr.Tab("Straight-Line • Solve & Check"):
|
|
@@ -393,9 +362,6 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 393 |
params_json2, # json
|
| 394 |
raw_df, # raw table
|
| 395 |
norm_df, # normalized table (tab 2)
|
| 396 |
-
raw_dl, # NEW: bytes -> RAW CSV download
|
| 397 |
-
norm_dl, # NEW: bytes -> NORMALIZED CSV download
|
| 398 |
-
txt_dl, # NEW: bytes -> OCR TEXT download
|
| 399 |
in_cost, in_salv, in_life, in_year, # autofill inputs
|
| 400 |
last_params, # state
|
| 401 |
last_table, # state
|
|
@@ -412,12 +378,6 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
|
|
| 412 |
btn_build.click(build_cb, [in_cost, in_salv, in_life, in_year], [expected_df])
|
| 413 |
btn_check.click(check_cb, [in_cost, in_salv, in_life, in_year, last_table], [deltas_df, coach_txt])
|
| 414 |
|
| 415 |
-
# ---- NEW: export edited normalized CSV ----
|
| 416 |
-
def export_current(norm_df_current: pd.DataFrame):
|
| 417 |
-
return _csv_bytes(norm_df_current if isinstance(norm_df_current, pd.DataFrame) else pd.DataFrame())
|
| 418 |
-
|
| 419 |
-
export_btn.click(export_current, inputs=norm_df, outputs=export_btn)
|
| 420 |
-
|
| 421 |
gr.Markdown("— Echo mode finished. When this looks good, we’ll plug in the SL solver + coaching.")
|
| 422 |
|
| 423 |
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+)?)",
|
|
|
|
| 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)
|
|
|
|
| 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 (
|
|
|
|
| 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__":
|