import json from fastapi import APIRouter, HTTPException from app.config import BACKGROUND_SNAPSHOT_PATH, BACKGROUND_PATH, energy_axis, NUM_CHANNELS from app.theoretical_bg import generate_theoretical_bg, generate_continuum_only import numpy as np router = APIRouter() def _load_snapshot(): """Load the live snapshot file, or raise 404.""" if not BACKGROUND_SNAPSHOT_PATH.exists(): raise HTTPException(status_code=404, detail="Background capture not available yet") try: with open(BACKGROUND_SNAPSHOT_PATH) as f: return json.load(f) except (json.JSONDecodeError, OSError): raise HTTPException(status_code=500, detail="Background snapshot file corrupt") def _load_reference(): """Load the 24h reference background, or return None.""" if not BACKGROUND_PATH.exists(): return None try: bg_data = np.load(str(BACKGROUND_PATH), allow_pickle=True).item() return { "counts": [round(float(c), 1) for c in bg_data["counts"][:NUM_CHANNELS]], "live_time_s": round(float(bg_data["duration"]), 1), } except Exception: return None @router.get("") async def get_background_info(): """Background metadata: elapsed time, CPS, top peaks.""" snapshot = _load_snapshot() full_available = BACKGROUND_PATH.exists() return { "elapsed_hours": snapshot.get("elapsed_hours", 0), "live_time_s": snapshot.get("live_time_s", 0), "total_counts": snapshot.get("total_counts", 0), "cps": snapshot.get("cps", 0), "top_peaks": snapshot.get("top_peaks", []), "full_background_available": full_available, } @router.get("/spectrum") async def get_background_spectrum(): """Live background spectrum (from snapshot) with energy axis.""" snapshot = _load_snapshot() live_time = snapshot.get("live_time_s", 0) return { "channels": list(range(NUM_CHANNELS)), "energy_kev": energy_axis(), "counts": snapshot.get("spectrum", [0] * 1024)[:NUM_CHANNELS], "live_time_s": live_time, "cps": snapshot.get("cps", 0), "top_peaks": snapshot.get("top_peaks", []), "reference_available": BACKGROUND_PATH.exists(), } @router.get("/reference") async def get_background_reference(): """24h reference background spectrum for overlay comparison.""" ref = _load_reference() if ref is None: raise HTTPException(status_code=404, detail="No 24h reference background available") return { "channels": list(range(NUM_CHANNELS)), "energy_kev": energy_axis(), "counts": ref["counts"], "live_time_s": ref["live_time_s"], } @router.get("/theoretical") async def get_theoretical_bg(cps: float = 6.0, live_time_s: float = 3600.0): """Theoretical natural background spectrum (K-40, U-238 chain, Th-232 chain).""" return generate_theoretical_bg(cps=cps, live_time_s=live_time_s) @router.get("/continuum") async def get_continuum(cps: float = 6.0, live_time_s: float = 3600.0): """CsI(Tl) continuum shape only (hump + Compton tail, no photopeaks, no noise). Matches the model used in training (generate_realistic_continuum). """ return generate_continuum_only(cps=cps, live_time_s=live_time_s)