fix: surface the fundamentals cache result on every SEC job outcome

The runtime message only appended the cache summary when the import itself
completed. On a deferred, failed or source-locked run Admin → Jobs showed just
the import outcome, so an operator had no signal that `fundamental_data` had
advanced — contradicting the claim now made in the docstring, the schedule hint
and the deployment doc.

The import status still varies and stays the headline; the cache summary is
appended to all of them. Adds a source-locked regression test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-07 11:22:28 +02:00
co-authored by Claude Opus 5
parent 3e83d63b05
commit 13f3636b6a
2 changed files with 56 additions and 18 deletions
+15 -16
View File
@@ -905,24 +905,23 @@ async def run_sec_fundamentals_import() -> None:
f"cache {summary['refreshed']} · "
f"{summary['score_inputs_changed']} score inputs changed"
)
# Every outcome carries the cache summary — including deferred, failed and
# source-locked ones. The import status is what varies; the refresh always
# happened, and Admin → Jobs is the only place an operator sees that.
runtime = get_job_runtime_snapshot(job_name)
if not import_ran:
_runtime_finish(
job_name,
"completed",
processed=1,
total=1,
message=f"Import disabled · {cache_message}",
)
elif runtime.get("status") == "completed":
if import_ran:
status = str(runtime.get("status") or "completed")
import_message = runtime.get("message") or "import completed"
_runtime_finish(
job_name,
"completed",
processed=1,
total=1,
message=f"{import_message} · {cache_message}",
)
processed = 1 if status == "completed" else 0
else:
status, import_message, processed = "completed", "Import disabled", 1
_runtime_finish(
job_name,
status,
processed=processed,
total=1,
message=f"{import_message} · {cache_message}",
)
# ---------------------------------------------------------------------------
+41 -2
View File
@@ -276,7 +276,7 @@ class TestShadowImportJobs:
assert runtime["status"] == "skipped"
assert runtime["message"] == "Disabled"
async def test_sec_failure_still_runs_activated_local_refresh(self, monkeypatch):
async def test_sec_failure_still_runs_local_cache_refresh(self, monkeypatch):
calls = []
async def enabled(db, job_name):
@@ -307,7 +307,10 @@ class TestShadowImportJobs:
assert len(calls) == 1
runtime = get_job_runtime_snapshot("sec_fundamentals_import")
assert runtime["status"] == "error"
assert runtime["message"] == "SEC unavailable"
# the failure stays the headline, but the cache result is still visible
assert runtime["message"] == (
"SEC unavailable · cache 511 · 2 score inputs changed"
)
async def test_sec_success_surfaces_cache_refresh_summary(self, monkeypatch):
async def enabled(db, job_name):
@@ -342,6 +345,42 @@ class TestShadowImportJobs:
"no_op · abcdef123456 · cache 511 · 2 score inputs changed"
)
async def test_source_locked_sec_run_still_reports_the_cache_refresh(
self, monkeypatch
):
"""A skipped import keeps its skip status but shows the cache advanced."""
async def enabled(db, job_name):
return True
async def locked(importer):
return None # another import owns the source lock
async def refreshed(db):
return {
"refreshed": 511,
"score_inputs_changed": 0,
"dimension_scores_staled": 0,
"composite_scores_staled": 0,
}
monkeypatch.setattr("app.scheduler.async_session_factory", self._session_factory)
monkeypatch.setattr("app.scheduler._is_job_enabled", enabled)
monkeypatch.setattr("app.scheduler.run_import", locked)
monkeypatch.setattr(
"app.scheduler.fundamental_data_refresh_service.refresh",
refreshed,
)
await run_sec_fundamentals_import()
runtime = get_job_runtime_snapshot("sec_fundamentals_import")
assert runtime["status"] == "skipped"
assert runtime["message"] == (
"Another import for this source is already running · "
"cache 511 · 0 score inputs changed"
)
async def test_disabled_sec_job_still_refreshes_local_cache(self, monkeypatch):
"""Disabling the job stops the SEC fetch, not the local cache.