fix simulation reporting and research resilience

This commit is contained in:
2026-09-28 15:43:23 +08:00
parent 23297be2d7
commit a428214c53
25 changed files with 1488 additions and 244 deletions
+37 -14
View File
@@ -17,6 +17,7 @@ from app.services.census import CensusService
from app.services.i18n import locale_from_request, translate
from app.services.sim_logger import SimulationLogger
from app.services.simulation_logging import bind_simulation, unbind_simulation
from app.services.narrator import REPORT_SCHEMA_VERSION
from enum import Enum
@@ -213,8 +214,11 @@ async def simulate(req: SimulateRequest, request: Request):
"模拟 %s:居民生成完成,耗时=%.2f秒,实际数量=%d",
sim_id, time.monotonic() - t_stage, len(agents),
)
fallback_count = sum(1 for agent in agents if agent.generation_source != "llm")
await store.set_meta(sim_id, "citizen_generation_fallback_count", fallback_count)
sim_log.log_event("citizens_generated", {
"count": len(agents),
"fallback_count": fallback_count,
"elapsed_s": round(time.monotonic() - t_stage, 3),
})
t_stage = time.monotonic()
@@ -449,10 +453,10 @@ async def get_report(sim_id: str, request: Request):
# Cancelled: client should not keep polling, but partial data (if any) is
# still in the report table. Try to return it before bailing.
if sim.get("status") == "cancelled":
result = await store.get_report(sim_id)
if result.get("status") == "ready":
return result["report"]
if sim.get("status") == "cancelled":
result = await store.get_report(sim_id)
if result.get("status") == "ready":
return result["report"]
return {"status": "cancelled", "message": translate("simulation_cancelled", locale)}
# Interrupted (typically because the backend restarted and the pipeline
@@ -468,12 +472,27 @@ async def get_report(sim_id: str, request: Request):
"message": translate("simulation_interrupted_no_report", locale),
}
result = await store.get_report(sim_id)
if result.get("status") == "ready":
report = result["report"]
report["rules_text"] = sim.get("rules_text", "") or ""
return report
return result
result = await store.get_report(sim_id)
if (
result.get("status") == "ready"
and result.get("report", {}).get("report_schema_version") == REPORT_SCHEMA_VERSION
):
report = result["report"]
report["rules_text"] = sim.get("rules_text", "") or ""
return report
if result.get("status") == "ready":
# A completed simulation may have a cached report from an older
# schema. Regenerate it once so report changes are not invisible to
# users who already ran the simulation.
narrator = getattr(request.app.state, "narrator", None)
if narrator is not None and hasattr(narrator, "set_locale"):
narrator.set_locale(locale)
report = await request.app.state.narrator.generate_report(sim_id, store)
if "error" not in report:
await store.save_report(sim_id, report)
report["rules_text"] = sim.get("rules_text", "") or ""
return report
return result
@router.post("/simulation/{sim_id}/publish")
@@ -513,10 +532,14 @@ async def compare(sim_id: str, fork_id: str, request: Request):
source_agents = await store.get_all_agents(sim_id)
fork_agents = await store.get_all_agents(fork_id)
async def _get_or_generate_report(sid):
cached = await store.get_report(sid)
if cached and cached.get("status") == "ready":
return cached["report"]
async def _get_or_generate_report(sid):
cached = await store.get_report(sid)
if (
cached
and cached.get("status") == "ready"
and cached.get("report", {}).get("report_schema_version") == REPORT_SCHEMA_VERSION
):
return cached["report"]
rpt = await narrator.generate_report(sid, store)
if "error" not in rpt:
await store.save_report(sid, rpt)