[Atlas] Per-field time-series dashboard for Elo, citations, markets done

← Live Dashboard Artifact Framework
Per-field 12-month dashboard plotting Elo distribution, citation accumulation, market liquidity, hypothesis-count growth.

Completion Notes

Auto-completed by supervisor after successful deploy to main

Git Commits (1)

[Atlas] Per-field time-series dashboard — field_time_series.html template, seed script, api.py route, tests [task:5a12406b-7925-492e-9314-5d508248cc6a] (#794)2026-04-27
Spec File

Effort: thorough

Goal

The whats-changed weekly dashboard (q-synth-whats-changed)
shows week-over-week deltas, but a researcher asking "how has
the AD therapeutic landscape shifted in the last 12 months?"
gets nothing. Build a per-field longitudinal dashboard
plotting Elo distribution, citation accumulation, market
liquidity, and hypothesis-count trends over rolling 90/180/365-day
windows for any registered field.

The data exists — price_history, judge_elo historical rows, papers.published_date, markets.created_at — but no surface
joins them on a per-field basis over time.

Acceptance Criteria

☐ New dashboard slug pattern field-time-series-<field-slug>,
registered via register_dashboard. Eager seeding
scripts/seed_field_time_series_dashboards.py for the same
25+ fields as q-edu-intro-to-field.
☐ view_spec_json with 5 data_sources, all parameterized by a
since: timestamp query param defaulting to NOW() -
INTERVAL '365 days'
:
1. elo_distribution_over_time — for hypotheses tagged
with the field, monthly buckets of (p10, p50, p90) of
judge_elo ratings.
2. citation_accumulation — monthly count of papers
linked to the field (via paper_entities /
papers.tags).
3. market_liquidity_over_time — monthly sum of
liquidity_usd across markets tagged to the field.
4. hypothesis_count_growth — cumulative count of
hypotheses tagged to the field, monthly buckets, with
lost_traction line (count of hyps whose
composite_score dropped >0.2 in the month).
5. top_movers_per_quarter — top-5 hypotheses by Elo
delta within each quarter (rolling, last 4 quarters).
render.template = new field_time_series.html with:
- 4 stacked time-series charts (sparkline for the small
view, full chart on hover/click).
- "Movers" sidebar showing each quarter's top-5 with a
Elo-delta arrow.
- Time range selector (90d / 180d / 365d / all-time).
☐ Reuses chart rendering from q-live-market-liquidity-heatmap
and q-synth-whats-changed sparkline JS.
cache_ttl_seconds=3600. Snapshot ledger via
scidex-field-time-series-monthly.timer on the 1st of each
month — these snapshots become the canonical historical
record.
GET /field-trends/{field-slug} route renders the dashboard
directly; navigable from /intro/{field-slug} and from the
whats-changed top-movers section.
☐ Pytest seeds 12 months of data for 1 synthetic field;
asserts each data source returns 12 monthly buckets with
monotone cumulative counts; asserts top-movers ordering.

Approach

  • Read dashboard_engine.py ALLOWED_TABLES and confirm
  • price_history, papers, markets, hypotheses,
    knowledge_edges are accessible.
  • Use date_trunc('month', ts) for monthly bucketing in
  • PostgreSQL; UNION zero-fill missing months.
  • Field-tag join: each table has a tags array (or
  • entity_links join) — confirm via the existing
    q-live-ad-therapeutic-landscape query patterns.
  • Snapshot ledger uses the existing dashboard snapshot
  • primitive POST /api/dashboard/{id}/snapshot.
  • Time range selector reissues the dashboard query with a new
  • since param via the existing dashboard parameterization.

    Dependencies

    • e352460b-2d76 — view_spec_json DSL.
    • q-edu-intro-to-field — supplies the field registry.
    • q-synth-whats-changed — sparkline component reuse.

    Work Log

    2026-04-27 — Implementation (task:5a12406b-7925-492e-9314-5d508248cc6a)

    Delivered:

  • scidex/senate/dashboard_engine.py — Added field_time_series.html template
  • to _TEMPLATES. Template includes 4 SVG charts (line for score p10/p50/p90,
    bar for citations, bar for liquidity, line for cumulative hypothesis growth)
    plus a top-movers-per-quarter sidebar, and a 90d/180d/365d/all-time range
    selector that updates DASHBOARD_PARAMS and calls the existing refreshDashboard().

  • scripts/seed_field_time_series_dashboards.py — Seeds 27 dashboards across
  • neurodegeneration fields (neurodegeneration, alzheimers-disease, parkinsons-disease,
    ALS, neuroinflammation, tau, amyloid, microglia-trem2, synaptic, apoe4, mitochondrial,
    gut-brain, lewy, prion, tdp43, neurovascular, glymphatic, and 10 more).
    Idempotent: updates existing artifacts in-place. Ran successfully: 27/27 registered.

  • api.py — Added GET /field-trends/{field_slug} route that looks up
  • field-time-series-{slug}, passes field_slug and optional since runtime
    params, and renders via render_dashboard_page. Added "Field Trends" link to
    the /science/{field_slug} dashboard grid.

  • tests/test_field_time_series_dashboard.py — 10 tests: seeds 12 months of
  • synthetic data for a test field, asserts all 5 data sources return rows,
    cumulative counts are monotone, top-movers ordering is correct, cache_ttl=3600,
    and since param filters correctly. All 10 pass.

    Schema adaptation: The spec assumed tags arrays and a judge_elo table,
    neither of which exist. Implemented field filtering via hypotheses.disease ILIKE '%field_slug%', score distribution from price_history,
    and market liquidity via markets.liquidity (not liquidity_usd).

    Not yet implemented: scidex-field-time-series-monthly.timer systemd unit
    (snapshot ledger) — requires infrastructure access outside this task scope.

    Sibling Tasks in Quest (Live Dashboard Artifact Framework) ↗