Fix score refresh, add granular fetch and live job status
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 22s

Scores never updated ("101d ago"): get_score only recomputes stale/
missing dimensions, but nothing marked them stale on new data, and there
was no scheduled scoring job.
- Fetch endpoint force-recomputes dimensions + composite.
- Scheduled scan (scan_all_tickers) refreshes scores per ticker, so
  scores stay current globally, not just on manual fetch.

Granular fetch: /ingestion/fetch accepts a sources filter; the freshness
bar gets a per-row refresh button (OHLCV/Sentiment/Fundamentals fetch
that provider only — marked paid; S/R/Scores recompute for free). Header
button is now "Fetch All".

Job visibility: GET /jobs/running (any user) + sidebar live indicator
showing running scheduled jobs with progress, polled every 10s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 13:10:15 +02:00
parent 3aebfd72d3
commit 316226096b
10 changed files with 296 additions and 94 deletions
+37
View File
@@ -0,0 +1,37 @@
"""Lightweight job status for any authenticated user.
The admin Jobs page has full control; this exposes only which scheduled jobs
are currently running (name + progress) so the UI can show a live activity
indicator without admin rights.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends
from app.dependencies import require_access
from app.schemas.common import APIEnvelope
from app.services.admin_service import JOB_LABELS
router = APIRouter(tags=["jobs"])
@router.get("/jobs/running", response_model=APIEnvelope)
async def list_running_jobs(_user=Depends(require_access)) -> APIEnvelope:
"""Return scheduled jobs that are currently running, with progress."""
from app.scheduler import get_job_runtime_snapshot
snapshot = get_job_runtime_snapshot()
running = []
for name, meta in snapshot.items():
if meta.get("running"):
running.append({
"name": name,
"label": JOB_LABELS.get(name, name),
"progress_pct": meta.get("progress_pct"),
"processed": meta.get("processed"),
"total": meta.get("total"),
"current_ticker": meta.get("current_ticker"),
})
running.sort(key=lambda j: j["name"])
return APIEnvelope(status="success", data={"running": running})