Initial Plast Track MVP

This commit is contained in:
masterdev
2026-06-15 13:35:22 +01:00
commit 3bf4c622d8
47 changed files with 7339 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
from __future__ import annotations
from datetime import datetime, time, timedelta, timezone
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from ..models import Cycle, Downtime, Machine, ProductionOrder, Scrap
from .events import get_open_downtime, get_running_order
from .oee import compute_machine_oee
UTC = timezone.utc
def _today_bounds() -> tuple[datetime, datetime]:
start = datetime.combine(datetime.now(UTC).date(), time.min, tzinfo=UTC)
return start, start + timedelta(days=1)
def serialize_machine_config(machine: Machine) -> dict:
return {
"name": machine.name,
"brand": machine.brand,
"model": machine.model,
"is_active": machine.is_active,
"cycle_signal_edge": machine.cycle_signal_edge,
"debounce_ms": machine.debounce_ms,
"min_cycle_time_sec": machine.min_cycle_time_sec,
"max_cycle_time_sec": machine.max_cycle_time_sec,
"stop_detection_delay_sec": machine.stop_detection_delay_sec,
}
def serialize_machine_card(session: Session, machine: Machine) -> dict:
running_order = get_running_order(session, machine.id)
open_downtime = get_open_downtime(session, machine.id)
today_start, today_end = _today_bounds()
recent_cycles = session.execute(
select(Cycle.cycle_time_sec).where(Cycle.machine_id == machine.id).order_by(Cycle.timestamp.desc()).limit(20)
).scalars().all()
avg_cycle = sum(recent_cycles) / len(recent_cycles) if recent_cycles else 0
order_produced_qty = 0
order_scrap_qty = 0
progress = 0
if running_order is not None:
order_produced_qty = session.execute(
select(func.coalesce(func.sum(Cycle.produced_qty), 0)).where(
Cycle.production_order_id == running_order.id,
Cycle.is_valid.is_(True),
)
).scalar_one()
order_scrap_qty = session.execute(
select(func.coalesce(func.sum(Scrap.quantity), 0)).where(Scrap.production_order_id == running_order.id)
).scalar_one()
progress = min(order_produced_qty / running_order.planned_qty, 1) if running_order.planned_qty else 0
daily_oee = compute_machine_oee(session, machine.id, today_start, today_end)
latest_downtime = session.execute(
select(Downtime).where(Downtime.machine_id == machine.id).order_by(Downtime.start_time.desc()).limit(1)
).scalar_one_or_none()
return {
"id": machine.id,
"code": machine.code,
"name": machine.name,
"brand": machine.brand,
"model": machine.model,
"status": machine.status,
"status_since": machine.status_since.isoformat() if machine.status_since else None,
"powered_on": machine.powered_on,
"config": serialize_machine_config(machine),
"active_order": None
if running_order is None
else {
"id": running_order.id,
"order_number": running_order.order_number,
"article_ref": running_order.article_ref,
"article_name": running_order.article_name,
"mold_ref": running_order.mold_ref,
"material_ref": running_order.material_ref,
"planned_qty": running_order.planned_qty,
"cavities": running_order.cavities,
"theoretical_cycle_time_sec": running_order.theoretical_cycle_time_sec,
"status": running_order.status,
},
"cycle_real_avg_sec": round(avg_cycle, 2),
"produced_qty": int(order_produced_qty),
"scrap_qty": int(order_scrap_qty),
"order_progress": round(progress, 4),
"today_oee": daily_oee,
"open_downtime": None
if open_downtime is None
else {
"id": open_downtime.id,
"reason_code": open_downtime.reason_code,
"start_time": open_downtime.start_time.isoformat(),
"comment": open_downtime.comment,
},
"last_downtime": None
if latest_downtime is None
else {
"id": latest_downtime.id,
"reason_code": latest_downtime.reason_code,
"start_time": latest_downtime.start_time.isoformat(),
"end_time": latest_downtime.end_time.isoformat() if latest_downtime.end_time else None,
"duration_sec": latest_downtime.duration_sec,
},
}
def build_dashboard_snapshot(session: Session) -> dict:
machines = session.execute(select(Machine).where(Machine.is_active.is_(True)).order_by(Machine.code)).scalars().all()
return {
"timestamp": datetime.now(UTC).isoformat(),
"machines": [serialize_machine_card(session, machine) for machine in machines],
}