bcd807e57d
Aggiunge servizio SPC con calcoli Cp/Cpk/Pp/Ppk, carta di controllo (UCL/LCL), istogramma con curva normale. Router FastAPI con 5 endpoint statistics, blueprint Flask con proxy AJAX, dashboard interattiva Alpine.js + Plotly.js con filtri per ricetta/subtask/date, riepilogo pass/fail, gauge Cpk e i18n IT/EN completo. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
"""Metrologist blueprint - SPC statistics dashboard and API proxies."""
|
|
from flask import Blueprint, jsonify, render_template, request
|
|
from flask_babel import gettext as _
|
|
|
|
from blueprints.auth import login_required, role_required
|
|
from services.api_client import api_client
|
|
|
|
statistics_bp = Blueprint("statistics", __name__)
|
|
|
|
|
|
# ============================================================================
|
|
# PAGINE
|
|
# ============================================================================
|
|
|
|
@statistics_bp.route("/dashboard")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def dashboard():
|
|
"""SPC dashboard overview — loads recipes for filter dropdown."""
|
|
resp = api_client.get("/api/recipes", params={"per_page": 100})
|
|
if isinstance(resp, dict) and resp.get("error"):
|
|
recipes = []
|
|
else:
|
|
recipes = resp.get("items", []) if isinstance(resp, dict) else []
|
|
|
|
return render_template("statistics/dashboard.html", recipes=recipes)
|
|
|
|
|
|
# ============================================================================
|
|
# PROXY API AJAX → FastAPI
|
|
# ============================================================================
|
|
|
|
def _proxy_statistics(endpoint: str):
|
|
"""Forward query params to a FastAPI statistics endpoint."""
|
|
params = {}
|
|
for key in [
|
|
"recipe_id", "version_id", "subtask_id",
|
|
"date_from", "date_to", "operator_id",
|
|
"lot_number", "serial_number", "n_bins",
|
|
]:
|
|
val = request.args.get(key)
|
|
if val is not None and val != "":
|
|
params[key] = val
|
|
|
|
resp = api_client.get(f"/api/statistics/{endpoint}", params=params)
|
|
|
|
if isinstance(resp, dict) and resp.get("error"):
|
|
return jsonify({"error": True, "detail": resp.get("detail", "")}), resp.get("status_code", 500)
|
|
|
|
return jsonify(resp)
|
|
|
|
|
|
@statistics_bp.route("/api/summary")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def api_summary():
|
|
"""Proxy: summary pass/fail/warning."""
|
|
return _proxy_statistics("summary")
|
|
|
|
|
|
@statistics_bp.route("/api/capability")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def api_capability():
|
|
"""Proxy: capability indices."""
|
|
return _proxy_statistics("capability")
|
|
|
|
|
|
@statistics_bp.route("/api/control-chart")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def api_control_chart():
|
|
"""Proxy: control chart data."""
|
|
return _proxy_statistics("control-chart")
|
|
|
|
|
|
@statistics_bp.route("/api/histogram")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def api_histogram():
|
|
"""Proxy: histogram data."""
|
|
return _proxy_statistics("histogram")
|
|
|
|
|
|
@statistics_bp.route("/api/subtasks")
|
|
@login_required
|
|
@role_required("Metrologist")
|
|
def api_subtasks():
|
|
"""Proxy: get subtasks for recipe filter."""
|
|
params = {}
|
|
recipe_id = request.args.get("recipe_id")
|
|
if recipe_id:
|
|
params["recipe_id"] = recipe_id
|
|
version_id = request.args.get("version_id")
|
|
if version_id:
|
|
params["version_id"] = version_id
|
|
|
|
resp = api_client.get("/api/statistics/subtasks", params=params)
|
|
|
|
if isinstance(resp, dict) and resp.get("error"):
|
|
return jsonify({"error": True, "detail": resp.get("detail", "")}), resp.get("status_code", 500)
|
|
|
|
# Response is a list
|
|
if isinstance(resp, list):
|
|
return jsonify(resp)
|
|
|
|
return jsonify(resp)
|