From d525443de9d849c43769c7dfecb9ed96a64b7335 Mon Sep 17 00:00:00 2001 From: Victor Fraile Garcia Date: Wed, 27 May 2026 14:22:16 +0200 Subject: [PATCH] fix(ucepsa): simplify operator console tablet UI --- .../services/operator-stop-console/app.py | 548 +++++++++++------- 1 file changed, 345 insertions(+), 203 deletions(-) diff --git a/ucepsa/edge-oee-demo/services/operator-stop-console/app.py b/ucepsa/edge-oee-demo/services/operator-stop-console/app.py index beb4503..efbec26 100644 --- a/ucepsa/edge-oee-demo/services/operator-stop-console/app.py +++ b/ucepsa/edge-oee-demo/services/operator-stop-console/app.py @@ -22,10 +22,27 @@ PG_CONFIG = { "connect_timeout": 5, } -APP_TITLE = getenv("APP_TITLE", "MESAVAULT Edge-OEE - Consola de paros") +APP_TITLE = getenv("APP_TITLE", "MESAVAULT Edge-OEE - Consola de paros UCEPSA") + DEFAULT_MACHINE_ID = getenv("DEFAULT_MACHINE_ID", "CORT-01") DEFAULT_ASSET = getenv("DEFAULT_ASSET", "revpi_oee_node_01") +ALLOWED_MACHINES = [ + item.strip() + for item in getenv("ALLOWED_MACHINES", DEFAULT_MACHINE_ID).split(",") + if item.strip() +] + + +def resolve_machine_id(raw_value): + machine_id = (raw_value or DEFAULT_MACHINE_ID).strip() + + if machine_id not in ALLOWED_MACHINES: + return DEFAULT_MACHINE_ID + + return machine_id + + app = Flask(__name__) @@ -33,7 +50,7 @@ def get_conn(): return psycopg2.connect(**PG_CONFIG) -def fetch_pending_stops(): +def fetch_pending_stops(machine_id): sql = """ SELECT id, @@ -54,13 +71,14 @@ def fetch_pending_stops(): cost_eur FROM mv_hot.edge_oee_stops_recent WHERE classification_status = 'PENDING' + AND machine_id = %(machine_id)s ORDER BY started_at DESC LIMIT 50; """ with get_conn() as conn: with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur: - cur.execute(sql) + cur.execute(sql, {"machine_id": machine_id}) return cur.fetchall() @@ -81,7 +99,7 @@ def fetch_causes(): return cur.fetchall() -def classify_stop(stop_id, cause_code, operator_note): +def classify_stop(stop_id, machine_id, cause_code, operator_note): sql = """ UPDATE mv_hot.edge_oee_machine_stops SET @@ -90,6 +108,7 @@ def classify_stop(stop_id, cause_code, operator_note): classification_status = 'CLASSIFIED', updated_at = now() WHERE id = %(stop_id)s + AND machine_id = %(machine_id)s AND classification_status = 'PENDING'; """ @@ -99,300 +118,400 @@ def classify_stop(stop_id, cause_code, operator_note): sql, { "stop_id": stop_id, + "machine_id": machine_id, "cause_code": cause_code, "operator_note": operator_note, }, ) + return cur.rowcount -PAGE = """ +def fmt_dt(value): + if not value: + return "" + if isinstance(value, datetime): + return value.strftime("%Y-%m-%d %H:%M:%S") + return str(value) + + +def fmt_num(value, decimals=2): + if value is None: + return "0" + try: + return f"{float(value):.{decimals}f}" + except Exception: + return str(value) + + +HTML = """ {{ app_title }} - + -
-

{{ app_title }}

-
- Clasificación manual de paros detectados automáticamente por Edge-OEE. - Última carga: {{ now }} -
-
- -
-
-
-
Paros pendientes
-
{{ stops|length }}
+
+

{{ app_title }}

+
+ Clasificación manual de paros detectados por Edge-OEE · + Máquina seleccionada: {{ selected_machine }} · + Última carga: {{ loaded_at }}
+
- -
-
Máquina monitorizada
-
{{ default_machine }}
-
{{ default_asset }}
-
- - -
- - {% if stops|length == 0 %} -
- No hay paros pendientes de clasificar. -
- {% else %} - - - - - - - - - - - - - - - - - {% for stop in stops %} - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+ {% for machine in allowed_machines %} + + {{ machine }} + {% endfor %} -
-
IDMáquinaInicioFinEstadoDuraciónCosteCausaNotaGuardar
#{{ stop.id }} - {{ stop.machine_id or "-" }} -
{{ stop.asset }}
-
- {{ stop.started_at }} - - {{ stop.ended_at or "Abierto" }} - - {% if stop.status == 'OPEN' %} - OPEN - {% else %} - CLOSED - {% endif %} -
- {{ stop.classification_status }} -
- {{ "%.2f"|format(stop.duration_min or 0) }} min - - {{ "%.2f"|format(stop.cost_eur or 0) }} € - - - - - - -
- {% endif %} -
+ + +
+
+
Paros pendientes
+
{{ pending_count }}
+
+ +
+
Máquina monitorizada
+
{{ selected_machine }}
+
{{ default_asset }}
+
+ +
+ +
+ {% if stops %} + + + + + + + + + + + + + + + + + {% for stop in stops %} + + + + + + + + + + + + + + + + + + + + + {% endfor %} + +
IDMáquinaInicioFinEstadoDuraciónCosteCausaNotaGuardar
#{{ stop.id }} +
{{ stop.machine_id }}
+
{{ stop.asset }}
+
{{ fmt_dt(stop.started_at) }} + {% if stop.ended_at %} + {{ fmt_dt(stop.ended_at) }} + {% else %} + Abierto + {% endif %} + + {% if stop.status == "OPEN" %} + OPEN + {% else %} + {{ stop.status }} + {% endif %} +
+ {{ stop.classification_status }} +
{{ fmt_num(stop.duration_min, 2) }} min{{ fmt_num(stop.cost_eur, 2) }} € +
+ + + +
+ + + + +
+ {% else %} +
+ No hay paros pendientes para {{ selected_machine }}. +
+ {% endif %} +
+ + """ @@ -400,30 +519,45 @@ PAGE = """ @app.route("/", methods=["GET"]) def index(): - stops = fetch_pending_stops() + selected_machine = resolve_machine_id(request.args.get("machine_id")) + + stops = fetch_pending_stops(selected_machine) causes = fetch_causes() return render_template_string( - PAGE, + HTML, app_title=APP_TITLE, stops=stops, causes=causes, - now=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - default_machine=DEFAULT_MACHINE_ID, + pending_count=len(stops), + selected_machine=selected_machine, + allowed_machines=ALLOWED_MACHINES, + default_machine=selected_machine, default_asset=DEFAULT_ASSET, + loaded_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + fmt_dt=fmt_dt, + fmt_num=fmt_num, ) -@app.route("/classify", methods=["POST"]) -def classify(): - stop_id = request.form.get("stop_id") - cause_code = request.form.get("cause_code") - operator_note = request.form.get("operator_note", "").strip() or None +@app.route("/classify/", methods=["POST"]) +def classify(stop_id): + selected_machine = resolve_machine_id( + request.form.get("machine_id") or request.args.get("machine_id") + ) - if stop_id and cause_code: - classify_stop(stop_id, cause_code, operator_note) + cause_code = (request.form.get("cause_code") or "").strip() + operator_note = (request.form.get("operator_note") or "").strip() - return redirect(url_for("index")) + if cause_code: + classify_stop( + stop_id=stop_id, + machine_id=selected_machine, + cause_code=cause_code, + operator_note=operator_note, + ) + + return redirect(url_for("index", machine_id=selected_machine)) @app.route("/health", methods=["GET"]) @@ -432,6 +566,14 @@ def health(): with get_conn() as conn: with conn.cursor() as cur: cur.execute("SELECT 1;") + cur.fetchone() + return {"status": "ok"}, 200 + except Exception as exc: return {"status": "error", "error": str(exc)}, 500 + + +if __name__ == "__main__": + port = int(getenv("APP_PORT", "8080")) + app.run(host="0.0.0.0", port=port)