jeffrey1963 commited on
Commit
a8e9b17
·
verified ·
1 Parent(s): 37859cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -10
app.py CHANGED
@@ -152,32 +152,86 @@ def _normalize_depr_columns(df_in: pd.DataFrame) -> pd.DataFrame:
152
  out = out[~out[["Begin BV","Depreciation","Accum Dep","End BV"]].isna().all(axis=1)].reset_index(drop=True)
153
  return out
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  # ---------- Gradio callbacks ----------
156
  def handle_docx(file):
157
  if file is None:
158
- return "(no file)", None, None
159
  df_raw, header = _docx_to_table_and_text(file.name if hasattr(file, "name") else file)
160
  params = _extract_params(header or "")
161
  df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else None
162
- return header or "(no text found)", params, df_norm
163
 
164
  def handle_image(img):
165
  if img is None:
166
- return "(no image)", None, None, None
167
- # gradio gives a numpy array; convert to PIL
168
- if not isinstance(img, Image.Image):
169
- from PIL import Image as PILImage
170
- pil = PILImage.fromarray(img)
171
- else:
172
- pil = img
173
  ocr_text = _image_to_text(pil)
174
  params = _extract_params(ocr_text or "")
175
  df_raw = _table_from_ocr_text(ocr_text or "")
176
  df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else None
177
- return ocr_text or "(empty OCR)", params, df_raw, df_norm
 
178
 
179
  # ---------- UI ----------
180
  with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
 
 
181
  gr.Markdown("## Jerry (TA) – Homework Intake\nThis Space **only reads and echoes** your files.\nNext step will add solving & coaching.")
182
  with gr.Tab("Upload .docx"):
183
  docx_in = gr.File(file_types=[".docx"], label="Homework .docx")
@@ -196,6 +250,60 @@ with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
196
  norm_df = gr.Dataframe(label="Detected table (normalized)", interactive=False)
197
  btn2.click(handle_image, inputs=img_in, outputs=[ocr_txt, params_json2, raw_df, norm_df])
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  gr.Markdown("— Echo mode finished. When this looks good, we’ll plug in the SL solver + coaching.")
200
 
201
  if __name__ == "__main__":
 
152
  out = out[~out[["Begin BV","Depreciation","Accum Dep","End BV"]].isna().all(axis=1)].reset_index(drop=True)
153
  return out
154
 
155
+ # Monday Aug 11 New helpers
156
+ def build_sl_schedule(cost: float, salvage: float, life: int, start_year: int):
157
+ """Return the straight-line depreciation schedule as a DataFrame."""
158
+ dep = (cost - salvage) / life
159
+ years = [start_year + i for i in range(life)]
160
+ begin_bv, dep_col, accum, end_bv = [], [], [], []
161
+ b = cost
162
+ acc = 0.0
163
+ for _ in years:
164
+ begin_bv.append(b)
165
+ dep_col.append(dep)
166
+ acc += dep
167
+ accum.append(acc)
168
+ b = b - dep
169
+ end_bv.append(b)
170
+ out = pd.DataFrame(
171
+ {
172
+ "Year": years,
173
+ "Begin BV": begin_bv,
174
+ "Depreciation": dep_col,
175
+ "Accum Dep": accum,
176
+ "End BV": end_bv,
177
+ }
178
+ )
179
+ return out
180
+
181
+ def audit_against_expected(expected: pd.DataFrame, actual: pd.DataFrame):
182
+ """Row-by-row deltas (actual - expected). Assumes normalized col names."""
183
+ if actual is None or actual.empty:
184
+ return pd.DataFrame(), "No student table found to check."
185
+ # align only overlapping years
186
+ merged = expected.merge(
187
+ actual[["Year","Begin BV","Depreciation","Accum Dep","End BV"]],
188
+ on="Year", how="inner", suffixes=("_exp","_act")
189
+ )
190
+ if merged.empty:
191
+ return pd.DataFrame(), "No matching years between expected and uploaded table."
192
+ deltas = pd.DataFrame({"Year": merged["Year"]})
193
+ for c in ["Begin BV","Depreciation","Accum Dep","End BV"]:
194
+ deltas[c + " Δ"] = merged[f"{c}_act"] - merged[f"{c}_exp"]
195
+ # first mismatch helper
196
+ first_bad = None
197
+ for _, r in deltas.iterrows():
198
+ if any(abs(r[col]) > 1e-6 for col in deltas.columns if col.endswith("Δ")):
199
+ first_bad = int(r["Year"])
200
+ break
201
+ msg = (
202
+ "All good 🎉 Straight‑line matches your table."
203
+ if first_bad is None
204
+ else f"First mismatch at year {first_bad}. Remember: Dep=(Cost−Salvage)/Life and Accum_t=Accum_(t−1)+Dep."
205
+ )
206
+ return deltas, msg
207
+
208
+
209
+
210
  # ---------- Gradio callbacks ----------
211
  def handle_docx(file):
212
  if file is None:
213
+ return "(no file)", None, None, {}, pd.DataFrame()
214
  df_raw, header = _docx_to_table_and_text(file.name if hasattr(file, "name") else file)
215
  params = _extract_params(header or "")
216
  df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else None
217
+ return header or "(no text found)", params, df_norm, params, (df_norm if df_norm is not None else pd.DataFrame())
218
 
219
  def handle_image(img):
220
  if img is None:
221
+ return "(no image)", None, None, None, {}, pd.DataFrame()
222
+ from PIL import Image as PILImage
223
+ pil = img if isinstance(img, PILImage.Image) else PILImage.fromarray(img)
 
 
 
 
224
  ocr_text = _image_to_text(pil)
225
  params = _extract_params(ocr_text or "")
226
  df_raw = _table_from_ocr_text(ocr_text or "")
227
  df_norm = _normalize_depr_columns(df_raw) if df_raw is not None else None
228
+ return ocr_text or "(empty OCR)", params, df_raw, df_norm, params, (df_norm if df_norm is not None else pd.DataFrame())
229
+
230
 
231
  # ---------- UI ----------
232
  with gr.Blocks(title="Jerry • HW Intake (Echo)") as demo:
233
+ last_params = gr.State({})
234
+ last_table = gr.State(pd.DataFrame())
235
  gr.Markdown("## Jerry (TA) – Homework Intake\nThis Space **only reads and echoes** your files.\nNext step will add solving & coaching.")
236
  with gr.Tab("Upload .docx"):
237
  docx_in = gr.File(file_types=[".docx"], label="Homework .docx")
 
250
  norm_df = gr.Dataframe(label="Detected table (normalized)", interactive=False)
251
  btn2.click(handle_image, inputs=img_in, outputs=[ocr_txt, params_json2, raw_df, norm_df])
252
 
253
+ with gr.Tab("Straight‑Line • Solve & Check"):
254
+ gr.Markdown("Enter params (auto-filled if detected) → build the correct SL schedule → compare to your uploaded table.")
255
+ with gr.Row():
256
+ in_cost = gr.Number(label="Cost", value=0.0)
257
+ in_salv = gr.Number(label="Salvage", value=0.0)
258
+ in_life = gr.Number(label="Life (years)", value=10, precision=0)
259
+ in_year = gr.Number(label="Start year", value=2025, precision=0)
260
+ btn_build = gr.Button("Build expected schedule")
261
+ expected_df = gr.Dataframe(label="Expected (SL) schedule", interactive=False)
262
+ btn_check = gr.Button("Check against uploaded table")
263
+ deltas_df = gr.Dataframe(label="Differences (student − expected)", interactive=False)
264
+ coach_txt = gr.Markdown()
265
+
266
+ def fill_from_state(p):
267
+ # prefill controls when we have parsed params
268
+ p = p or {}
269
+ return (
270
+ p.get("cost", 0.0),
271
+ p.get("salvage", 0.0),
272
+ p.get("life", 10),
273
+ p.get("start_year", pd.Timestamp.now().year),
274
+ )
275
+
276
+ def build_cb(cost, salv, life, year):
277
+ try:
278
+ df = build_sl_schedule(float(cost), float(salv), int(life), int(year))
279
+ except Exception as e:
280
+ return pd.DataFrame([{"error": str(e)}])
281
+ return df
282
+
283
+ def check_cb(cost, salv, life, year, table):
284
+ exp = build_sl_schedule(float(cost), float(salv), int(life), int(year))
285
+ deltas, msg = audit_against_expected(exp, table if isinstance(table, pd.DataFrame) else pd.DataFrame())
286
+ return deltas, msg
287
+
288
+ # Wire up
289
+ btn_build.click(build_cb, [in_cost, in_salv, in_life, in_year], [expected_df])
290
+ btn_check.click(check_cb, [in_cost, in_salv, in_life, in_year, last_table], [deltas_df, coach_txt])
291
+
292
+ btn1.click(handle_docx, inputs=docx_in,
293
+ outputs=[header_txt, params_json, table_df, last_params, last_table])
294
+ btn2.click(handle_image, inputs=img_in,
295
+ outputs=[ocr_txt, params_json2, raw_df, norm_df, last_params, last_table])
296
+
297
+ # When params state changes, prefill inputs
298
+ last_params.change(lambda p: fill_from_state(p), inputs=last_params,
299
+ outputs=[in_cost, in_salv, in_life, in_year])
300
+
301
+
302
+ # Auto-fill inputs whenever we parse new params
303
+ def prefill_inputs(p): return fill_from_state(p)
304
+ params_from_docx = gr.EventListener(last_params, triggers=["change"])
305
+ params_from_img = gr.EventListener(last_params, triggers=["change"])
306
+
307
  gr.Markdown("— Echo mode finished. When this looks good, we’ll plug in the SL solver + coaching.")
308
 
309
  if __name__ == "__main__":