Dashboard web FastAPI + Chart.js
- 4 vues : spectre temps reel, historique detections, background, timeline CPS - API REST : /api/status, /api/spectrum/current, /api/spectrum/difference, /api/background, /api/background/spectrum, /api/history, /api/cps/timeline - Frontend vanilla JS + Chart.js (pas de Node.js, leger pour Pi 4) - Moniteur modifie pour exporter son etat dans /data/monitor_state.json et le CPS dans /data/cps_log.jsonl chaque cycle - Nouveau conteneur Docker 'web' sur port 8080 - Theme sombre, calibration energie (E = 0.33 + 2.97 * canal) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
76
web/app/routers/spectrum.py
Normal file
76
web/app/routers/spectrum.py
Normal file
@ -0,0 +1,76 @@
|
||||
import json
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from app.config import STATE_PATH, BACKGROUND_PATH, energy_axis, NUM_CHANNELS
|
||||
import numpy as np
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/current")
|
||||
async def get_current_spectrum():
|
||||
"""Current accumulated spectrum with energy axis."""
|
||||
if not STATE_PATH.exists():
|
||||
raise HTTPException(status_code=503, detail="Monitor not started yet")
|
||||
|
||||
try:
|
||||
with open(STATE_PATH) as f:
|
||||
state = json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
raise HTTPException(status_code=503, detail="Monitor state file corrupt")
|
||||
|
||||
return {
|
||||
"timestamp": state.get("timestamp", ""),
|
||||
"connected": state.get("connected", False),
|
||||
"cumulated_live_time_s": state.get("cumulated_live_time_s", 0),
|
||||
"cumulated_live_time_h": state.get("cumulated_live_time_h", 0),
|
||||
"cps": state.get("cps", 0),
|
||||
"total_counts": state.get("total_counts", 0),
|
||||
"background_subtracted": state.get("background_subtracted", False),
|
||||
"isotopes_detected": state.get("isotopes_detected", []),
|
||||
"channels": list(range(NUM_CHANNELS)),
|
||||
"energy_kev": energy_axis(),
|
||||
"counts": state.get("counts", [0] * NUM_CHANNELS),
|
||||
}
|
||||
|
||||
|
||||
@router.get("/difference")
|
||||
async def get_difference_spectrum():
|
||||
"""Background-subtracted spectrum (net signal)."""
|
||||
if not STATE_PATH.exists():
|
||||
raise HTTPException(status_code=503, detail="Monitor not started yet")
|
||||
|
||||
try:
|
||||
with open(STATE_PATH) as f:
|
||||
state = json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
raise HTTPException(status_code=503, detail="Monitor state file corrupt")
|
||||
|
||||
counts = np.array(state.get("counts", [0] * NUM_CHANNELS), dtype=np.float64)
|
||||
live_time = state.get("cumulated_live_time_s", 0)
|
||||
|
||||
if live_time <= 0:
|
||||
raise HTTPException(status_code=503, detail="No live time data yet")
|
||||
|
||||
rate = counts / live_time
|
||||
|
||||
if BACKGROUND_PATH.exists():
|
||||
bg_data = np.load(str(BACKGROUND_PATH), allow_pickle=True).item()
|
||||
bg_counts = bg_data["counts"].astype(np.float64)
|
||||
bg_live_time = float(bg_data["duration"])
|
||||
bg_rate = bg_counts / bg_live_time
|
||||
net_rate = np.clip(rate - bg_rate, 0, None)
|
||||
net_counts = net_rate * live_time
|
||||
bg_available = True
|
||||
else:
|
||||
net_counts = counts
|
||||
bg_available = False
|
||||
|
||||
return {
|
||||
"timestamp": state.get("timestamp", ""),
|
||||
"cumulated_live_time_s": live_time,
|
||||
"background_subtracted": bg_available,
|
||||
"channels": list(range(NUM_CHANNELS)),
|
||||
"energy_kev": energy_axis(),
|
||||
"counts": [round(float(c), 1) for c in net_counts],
|
||||
"raw_counts": state.get("counts", []),
|
||||
}
|
||||
Reference in New Issue
Block a user