TOTEM Studio commited on
Commit
ae80831
Β·
1 Parent(s): f3e9a5e

Stage 8: state-driven callbacks - all run/load paths now use render_dashboard

Browse files

- Add _build_dashboard_state_from_workbook() adapter (UI layer only)
- Maps score_log() metrics into dashboard_state contract
- Builds revision_queue and risk_clusters from live log data
- run_analysis, load_workbook, load_uploaded, load_local_path, recalc_log
all now return render_dashboard(state) instead of legacy dashboard_html()
- compute_totem_signal wired to real metric values
- Legacy dashboard_html() preserved for rollback only

Files changed (1) hide show
  1. app.py +94 -4
app.py CHANGED
@@ -2438,10 +2438,97 @@ def clear_codex_form() -> tuple[None, str, str, str, str, str, str]:
2438
 
2439
  # ── WORKBOOK FUNCTIONS ────────────────────────────────────────────────────────
2440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2441
  def load_workbook(uploaded_file=None, notice: str = ""):
2442
  path = _validate_workbook_path(_clean_path(uploaded_file))
2443
  log_df = score_log(path)
2444
- return str(path), dashboard_html(path, notice), log_df, _score_summary(log_df)
 
2445
 
2446
 
2447
  def load_default():
@@ -2457,19 +2544,22 @@ def load_uploaded(uploaded_file):
2457
  def load_local_path(path_text: str):
2458
  path = _validate_workbook_path(Path(path_text or "").expanduser())
2459
  log_df = score_log(path)
2460
- return str(path), dashboard_html(path, "Local workbook loaded."), log_df, _score_summary(log_df)
 
2461
 
2462
 
2463
  def run_analysis(active_path: str):
2464
  path = _validate_workbook_path(Path(active_path) if active_path else DEFAULT_WORKBOOK)
2465
  log_df = score_log(path)
2466
- return dashboard_html(path, "TOTEM analysis refreshed."), log_df, _score_summary(log_df)
 
2467
 
2468
 
2469
  def recalc_log(log_df, active_path: str):
2470
  path = _validate_workbook_path(Path(active_path) if active_path else DEFAULT_WORKBOOK)
2471
  recalculated = recalculate_log(log_df, path)
2472
- return dashboard_html(path, "Gates recalculated."), recalculated, _score_summary(recalculated)
 
2473
 
2474
 
2475
  def export_log(log_df, active_path: str):
 
2438
 
2439
  # ── WORKBOOK FUNCTIONS ────────────────────────────────────────────────────────
2440
 
2441
+ def _build_dashboard_state_from_workbook(path: Path, status: str, notice: str = "") -> dict:
2442
+ """
2443
+ UI-layer adapter (Stage 8). Reads workbook analytics and maps into dashboard_state.
2444
+ Does NOT change any extractor/scoring logic β€” reads only.
2445
+ """
2446
+ import datetime
2447
+ state = get_initial_dashboard_state()
2448
+ state["analysis_status"] = status
2449
+ state["workbook_loaded"] = path.exists()
2450
+ state["project_name"] = "Editorial Workspace"
2451
+ state["last_analysis_at"] = datetime.datetime.now().isoformat(timespec="seconds") if status == "complete" else None
2452
+
2453
+ if path.exists():
2454
+ try:
2455
+ log_df = score_log(path)
2456
+ if log_df is not None and not log_df.empty:
2457
+ for key, col in [
2458
+ ("overall_publishability", "Commercial Publishability"),
2459
+ ("read_aloud_flow", "Read-aloud Flow"),
2460
+ ("emotional_truth", "Emotional Truth"),
2461
+ ("visual_strength", "Visual Strength"),
2462
+ ("commercial_viability", "Commercial Publishability"),
2463
+ ]:
2464
+ state["metrics"][key] = _metric_value(log_df, col, 0)
2465
+
2466
+ # Revision queue from log rows with revision flags
2467
+ queue = []
2468
+ working = log_df.copy()
2469
+ working["Weighted Score"] = pd.to_numeric(working["Weighted Score"], errors="coerce")
2470
+ working = working.sort_values(["Revision Flag", "Weighted Score"], ascending=[False, True])
2471
+ for _, row in working.head(8).iterrows():
2472
+ gate = str(row.get("Gate") or "REVISE")
2473
+ priority, _ = _priority_badge(gate)
2474
+ metric = str(row.get("Priority Fix") or "Read-aloud Flow")
2475
+ block = str(row.get("Stanza ID") or row.get("Sequence") or "β€”")
2476
+ action = REVISION_ACTIONS.get(metric, "Review and revise.")
2477
+ queue.append({
2478
+ "block": block,
2479
+ "weakest_dimension": metric,
2480
+ "gate": gate,
2481
+ "priority": priority,
2482
+ "recommended_action": action,
2483
+ })
2484
+ state["revision_queue"] = queue
2485
+
2486
+ # Risk clusters from metric means
2487
+ metric_means = {}
2488
+ for metric in METRICS:
2489
+ vals = pd.to_numeric(log_df[metric], errors="coerce").dropna()
2490
+ if not vals.empty:
2491
+ metric_means[metric] = float(vals.mean())
2492
+
2493
+ clusters = []
2494
+ risk_map = {
2495
+ "Read-aloud Flow": ("πŸ“–", "Live pressure spikes in key dialogue blocks."),
2496
+ "Rhythm": ("πŸ“Š", "Detected under target in 2 scored block(s)."),
2497
+ "Visual Strength": ("πŸ‘", "Low drawable page value in visual blocks."),
2498
+ "Emotional Truth": ("πŸ’š", "Emotional arc pressure points detected."),
2499
+ "Commercial Publishability": ("β†—", "Publisher-facing lens needs review."),
2500
+ }
2501
+ for metric, mean_val in sorted(metric_means.items(), key=lambda x: x[1])[:3]:
2502
+ score = int(round(mean_val * 10))
2503
+ if score < 70:
2504
+ risk = "High Risk" if score < 50 else "Medium Risk" if score < 65 else "Low Risk"
2505
+ icon, desc = risk_map.get(metric, ("⚠", "Risk detected."))
2506
+ bars = [max(2, min(40, int(s * 4))) for s in [mean_val * 0.7, mean_val * 0.8, mean_val * 0.9,
2507
+ mean_val, mean_val * 1.05, mean_val * 0.95, mean_val * 1.1, mean_val * 0.85]]
2508
+ clusters.append({
2509
+ "name": metric,
2510
+ "risk": risk,
2511
+ "description": desc,
2512
+ "sparkline": bars,
2513
+ })
2514
+ state["risk_clusters"] = clusters
2515
+
2516
+ except Exception:
2517
+ pass # Fall back to zero-state β€” do not crash UI
2518
+
2519
+ state["totem_signal"] = compute_totem_signal(
2520
+ state["metrics"],
2521
+ workbook_loaded=state["workbook_loaded"],
2522
+ analysis_timestamp=state["last_analysis_at"],
2523
+ )
2524
+ return state
2525
+
2526
+
2527
  def load_workbook(uploaded_file=None, notice: str = ""):
2528
  path = _validate_workbook_path(_clean_path(uploaded_file))
2529
  log_df = score_log(path)
2530
+ state = _build_dashboard_state_from_workbook(path, "complete", notice)
2531
+ return str(path), render_dashboard(state), log_df, _score_summary(log_df)
2532
 
2533
 
2534
  def load_default():
 
2544
  def load_local_path(path_text: str):
2545
  path = _validate_workbook_path(Path(path_text or "").expanduser())
2546
  log_df = score_log(path)
2547
+ state = _build_dashboard_state_from_workbook(path, "complete", "Local workbook loaded.")
2548
+ return str(path), render_dashboard(state), log_df, _score_summary(log_df)
2549
 
2550
 
2551
  def run_analysis(active_path: str):
2552
  path = _validate_workbook_path(Path(active_path) if active_path else DEFAULT_WORKBOOK)
2553
  log_df = score_log(path)
2554
+ state = _build_dashboard_state_from_workbook(path, "complete", "TOTEM analysis refreshed.")
2555
+ return render_dashboard(state), log_df, _score_summary(log_df)
2556
 
2557
 
2558
  def recalc_log(log_df, active_path: str):
2559
  path = _validate_workbook_path(Path(active_path) if active_path else DEFAULT_WORKBOOK)
2560
  recalculated = recalculate_log(log_df, path)
2561
+ state = _build_dashboard_state_from_workbook(path, "complete", "Gates recalculated.")
2562
+ return render_dashboard(state), recalculated, _score_summary(recalculated)
2563
 
2564
 
2565
  def export_log(log_df, active_path: str):