stasaking commited on
Commit
25c4977
·
verified ·
1 Parent(s): 2d5afa4

Sync main.py from blood-brain-omics repo

Browse files
Files changed (1) hide show
  1. main.py +86 -0
main.py CHANGED
@@ -541,6 +541,92 @@ def h2h_blood_summary(
541
  }
542
 
543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  @app.get("/api/v1/h2h/brain")
545
  def h2h_brain(
546
  blood: str = Query(..., description="Blood platform name"),
 
541
  }
542
 
543
 
544
+ @app.get("/api/v1/h2h/blood/per_outcome")
545
+ def h2h_blood_per_outcome(
546
+ pair: str = Query(..., description="H2H pair, e.g. ClinLabs_vs_Metabolon"),
547
+ metric: str = Query("pearson"),
548
+ covariates: str = Query("included", enum=["none", "included"]),
549
+ model: str = Query("TabPFN"),
550
+ ):
551
+ """Per-outcome head-to-head comparison for one blood pair.
552
+
553
+ Returns one row per brain_target with the mean and best metric value
554
+ for each side of the pair, plus per-target win counts. Lets the H2H
555
+ page show "for ClinLabs vs Metabolon, here's how each performs on
556
+ every outcome group" instead of just the global win count.
557
+ """
558
+ validate_metric(metric)
559
+ phase = "h2h_blood_withcov" if covariates == "included" else "h2h_blood"
560
+
561
+ rows = q(f"""
562
+ SELECT brain_target, target, blood_platform, {metric}
563
+ FROM results
564
+ WHERE phase = ? AND model = ? AND h2h_pair = ?
565
+ ORDER BY brain_target, target, blood_platform
566
+ """, [phase, model, pair]).fetchall()
567
+
568
+ # Group by (brain_target, target) → {platform: value}
569
+ per_target: dict[tuple[str, str], dict[str, Optional[float]]] = {}
570
+ platforms_seen: set[str] = set()
571
+ for brain_target, target, platform, val in rows:
572
+ per_target.setdefault((brain_target, target), {})[platform] = val
573
+ platforms_seen.add(platform)
574
+
575
+ if len(platforms_seen) != 2:
576
+ return {"pair": pair, "metric": metric, "covariates": covariates,
577
+ "platforms": sorted(platforms_seen), "outcomes": []}
578
+
579
+ p_a, p_b = sorted(platforms_seen)
580
+ asc = metric in ("mse", "mae")
581
+
582
+ # Aggregate per brain_target
583
+ by_outcome: dict[str, dict] = {}
584
+ for (brain_target, target), vals in per_target.items():
585
+ v_a = vals.get(p_a)
586
+ v_b = vals.get(p_b)
587
+ if v_a is None or v_b is None:
588
+ continue
589
+ bucket = by_outcome.setdefault(brain_target, {
590
+ "n_targets": 0, "sum_a": 0.0, "sum_b": 0.0,
591
+ "wins_a": 0, "wins_b": 0,
592
+ "best_a": None, "best_b": None,
593
+ })
594
+ bucket["n_targets"] += 1
595
+ bucket["sum_a"] += v_a
596
+ bucket["sum_b"] += v_b
597
+ if v_a < v_b if asc else v_a > v_b:
598
+ bucket["wins_a"] += 1
599
+ else:
600
+ bucket["wins_b"] += 1
601
+ if bucket["best_a"] is None or (v_a < bucket["best_a"] if asc else v_a > bucket["best_a"]):
602
+ bucket["best_a"] = v_a
603
+ if bucket["best_b"] is None or (v_b < bucket["best_b"] if asc else v_b > bucket["best_b"]):
604
+ bucket["best_b"] = v_b
605
+
606
+ outcomes = []
607
+ for brain_target, b in sorted(by_outcome.items()):
608
+ n = b["n_targets"]
609
+ outcomes.append({
610
+ "brain_target": brain_target,
611
+ "n_targets": n,
612
+ "mean_a": round(b["sum_a"] / n, 4),
613
+ "mean_b": round(b["sum_b"] / n, 4),
614
+ "best_a": round(b["best_a"], 4),
615
+ "best_b": round(b["best_b"], 4),
616
+ "wins_a": b["wins_a"],
617
+ "wins_b": b["wins_b"],
618
+ })
619
+
620
+ return {
621
+ "pair": pair,
622
+ "metric": metric,
623
+ "covariates": covariates,
624
+ "platform_a": p_a,
625
+ "platform_b": p_b,
626
+ "outcomes": outcomes,
627
+ }
628
+
629
+
630
  @app.get("/api/v1/h2h/brain")
631
  def h2h_brain(
632
  blood: str = Query(..., description="Blood platform name"),