MukulRay commited on
Commit
93faab0
·
1 Parent(s): dffe992

Phase 2: UI reliability badges, changelog, eval runner — Phase 2 complete

Browse files
Files changed (2) hide show
  1. CHANGELOG.md +43 -0
  2. app.py +27 -6
CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
 
3
  ## [Unreleased]
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  ### Phase 1 — Integrity + Critic Fix
6
  - 1.1: Archived patch_contradiction.py to eval/archived/ with README
7
  - 1.2: Fixed critic_node — STALE and CONTRADICTED checks now run in parallel
 
2
 
3
  ## [Unreleased]
4
 
5
+ ### Phase 2 — Edge Reliability Scoring + OpenAlex Augmentation
6
+ - 2.1: Created src/openalex_utils.py
7
+ - `search_openalex(query, max_results=5)` — keyword search, returns Paper-compatible dicts
8
+ - `get_openalex_by_doi(doi)` — singleton DOI lookup for citation enrichment
9
+ - `get_citation_centrality(doi, citation_count)` — `min(1.0, log1p(count) / log1p(10000))`
10
+ - Abstract reconstruction from OpenAlex inverted index format
11
+ - DOI URL stripping (`https://doi.org/` prefix removed)
12
+ - API key auth via `OPENALEX_API_KEY` env var; graceful degradation without key
13
+ - 2.2: Added DOI extraction to retriever_utils.py
14
+ - Added `"externalIds"` to Semantic Scholar API field list
15
+ - Extracts `doi = (r.get("externalIds") or {}).get("DOI", "") or ""`
16
+ - Passes `doi=doi` to Paper constructor
17
+ - Added `doi: str = ""` field to Paper dataclass in state.py (after references, before hybrid_score)
18
+ - 2.3: OpenAlex augmentation in retriever.py
19
+ - Triggered when S2 retrieval returns < 12 papers
20
+ - Searches top 2 sub-questions, max 3 results each
21
+ - Deduplicates by DOI and paper_id before merging
22
+ - OpenAlex papers get hybrid_score from semantic sim (0.3) + authority + recency
23
+ - 2.4: Created src/reliability.py — three-signal paper reliability scorer
24
+ - Signals: centrality (40%), recency (30%), coherence (30%)
25
+ - Centrality: `min(1.0, log1p(citation_count) / log1p(10000))`
26
+ - Recency: `max(0, 1 - (age_years / 10))`
27
+ - Coherence: batched LLM call (one call for all papers) — scores 0–1 relevance to query
28
+ - Dominant signal labels: FOUNDATIONAL / CURRENT / DECLINING / SUPERSEDED
29
+ - FOUNDATIONAL: `centrality >= 0.60 AND (coherence >= 0.65 OR coherence == 0.0)` — coherence passthrough for LLM-off case
30
+ - Returns `{paper_id: ReliabilityScore}` dict
31
+ - 2.5: Wired reliability scorer into critic.py
32
+ - `score_papers()` called after high-score paper check
33
+ - STALE threshold changed from `mean_age > 24` to `mean_reliability < 0.40`
34
+ - Falls back to age-based threshold if scorer returns empty
35
+ - `paper_reliability_scores` added to ResearchState TypedDict
36
+ - Scores serialised to `{pid: rs.__dict__}` in all non-INSUFFICIENT critic return paths
37
+ - 2.6: Added per-paper trust summary to synthesizer output
38
+ - Reads `paper_reliability_scores` from state after synthesis
39
+ - Appends `## Evidence Trust Summary` block to `synthesized_position`
40
+ - Color-coded signal labels: HIGH (FOUNDATIONAL/CURRENT), MEDIUM (DECLINING), LOW (SUPERSEDED)
41
+ - Trust summary included in `export_md` via `_build_export_md`
42
+ - Warning fix: `_headers()` in openalex_utils.py downgraded from WARNING to DEBUG when API key absent (was spamming 14+ warnings per query during centrality scoring)
43
+ - Eval results (recon_linear_v2_full, 130Q):
44
+ - Staleness (STALE verdict): 32.3% (42/130) — down from 52% v1; reliability-based threshold is more precise, fewer false-positive STALE calls
45
+ - Contradiction catch rate: 0% (retriever still returns adjacent-topic papers, not opposing-camp pairs — known gap, deferred)
46
+ - Position accuracy (MATCH): 44.6% (58/130) — unchanged vs v1 43.9%
47
+
48
  ### Phase 1 — Integrity + Critic Fix
49
  - 1.1: Archived patch_contradiction.py to eval/archived/ with README
50
  - 1.2: Fixed critic_node — STALE and CONTRADICTED checks now run in parallel
app.py CHANGED
@@ -43,7 +43,14 @@ def _highlight_citations(text: str) -> str:
43
  text
44
  )
45
 
46
- def _paper_cards_html(papers) -> str:
 
 
 
 
 
 
 
47
  """Render retrieved papers as styled cards."""
48
  if not papers:
49
  return "<p style='color:#6b7280;font-style:italic'>No papers retrieved.</p>"
@@ -54,15 +61,29 @@ def _paper_cards_html(papers) -> str:
54
  authors = ", ".join(p.authors[:2]) + (" et al." if len(p.authors) > 2 else "") if p.authors else "Unknown"
55
  abstract_preview = (p.abstract[:180] + "...") if p.abstract and len(p.abstract) > 180 else (p.abstract or "")
56
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  cards.append(f"""
58
  <div style="border:1px solid #2d3748;border-radius:8px;padding:12px 14px;
59
  margin-bottom:8px;background:#1a1f2e;">
60
  <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px">
61
  <span style="font-weight:600;font-size:0.92em;color:#e2e8f0;flex:1;margin-right:8px">{p.title}</span>
62
- <span style="background:{score_color}22;color:{score_color};border:1px solid {score_color}44;
63
- padding:2px 8px;border-radius:12px;font-size:0.78em;white-space:nowrap;font-weight:600">
64
- {p.hybrid_score:.3f}
65
- </span>
 
 
66
  </div>
67
  <div style="color:#94a3b8;font-size:0.8em;margin-bottom:6px">
68
  {authors} · {p.year} · {p.citation_count:,} citations · <span style="color:#64748b">{p.source}</span>
@@ -205,7 +226,7 @@ def run_query(query, session_id, decay_config, history):
205
  verdict_html = _verdict_badge_html(verdict, critic_notes, retry_count,
206
  papers_used, latency, decay_config, rewritten)
207
  claims_html = _claims_html(result.get("claim_confidences") or [])
208
- papers_html = _paper_cards_html(result.get("retrieved_papers") or [])
209
  session_ctx = load_session(session_id)
210
  session_html = _session_html(session_ctx, session_id)
211
  export_md = result.get("export_md", "")
 
43
  text
44
  )
45
 
46
+ SIGNAL_COLORS = {
47
+ "FOUNDATIONAL": "#22c55e",
48
+ "CURRENT": "#3b82f6",
49
+ "DECLINING": "#f59e0b",
50
+ "SUPERSEDED": "#ef4444",
51
+ }
52
+
53
+ def _paper_cards_html(papers, reliability_scores: dict = {}) -> str:
54
  """Render retrieved papers as styled cards."""
55
  if not papers:
56
  return "<p style='color:#6b7280;font-style:italic'>No papers retrieved.</p>"
 
61
  authors = ", ".join(p.authors[:2]) + (" et al." if len(p.authors) > 2 else "") if p.authors else "Unknown"
62
  abstract_preview = (p.abstract[:180] + "...") if p.abstract and len(p.abstract) > 180 else (p.abstract or "")
63
 
64
+ rs = reliability_scores.get(p.paper_id)
65
+ if rs:
66
+ dominant = rs.get("dominant_signal", "DECLINING") if isinstance(rs, dict) else rs.dominant_signal
67
+ sig_color = SIGNAL_COLORS.get(dominant, "#6b7280")
68
+ signal_badge = (
69
+ f'<span style="background:{sig_color}22;color:{sig_color};border:1px solid {sig_color}44;'
70
+ f'padding:2px 7px;border-radius:12px;font-size:0.75em;white-space:nowrap;font-weight:600;margin-left:6px">'
71
+ f'{dominant}</span>'
72
+ )
73
+ else:
74
+ signal_badge = ""
75
+
76
  cards.append(f"""
77
  <div style="border:1px solid #2d3748;border-radius:8px;padding:12px 14px;
78
  margin-bottom:8px;background:#1a1f2e;">
79
  <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px">
80
  <span style="font-weight:600;font-size:0.92em;color:#e2e8f0;flex:1;margin-right:8px">{p.title}</span>
81
+ <div style="display:flex;align-items:center;flex-shrink:0">
82
+ <span style="background:{score_color}22;color:{score_color};border:1px solid {score_color}44;
83
+ padding:2px 8px;border-radius:12px;font-size:0.78em;white-space:nowrap;font-weight:600">
84
+ {p.hybrid_score:.3f}
85
+ </span>{signal_badge}
86
+ </div>
87
  </div>
88
  <div style="color:#94a3b8;font-size:0.8em;margin-bottom:6px">
89
  {authors} · {p.year} · {p.citation_count:,} citations · <span style="color:#64748b">{p.source}</span>
 
226
  verdict_html = _verdict_badge_html(verdict, critic_notes, retry_count,
227
  papers_used, latency, decay_config, rewritten)
228
  claims_html = _claims_html(result.get("claim_confidences") or [])
229
+ papers_html = _paper_cards_html(result.get("retrieved_papers") or [], result.get("paper_reliability_scores") or {})
230
  session_ctx = load_session(session_id)
231
  session_html = _session_html(session_ctx, session_id)
232
  export_md = result.get("export_md", "")