Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Раздел «Анализ надёжности» (инженеры и руководители; включается в настройках сервера)."""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import config, reliability as R, schemas as S, scope, settings_store as ss
|
||||
from ..db import get_db
|
||||
from ..models import Equipment, User
|
||||
from ..security import require_engineer
|
||||
|
||||
router = APIRouter(prefix="/reliability", tags=["reliability"])
|
||||
|
||||
|
||||
def enabled(db: Session) -> bool:
|
||||
return ss.get(db, "reliability") != "0"
|
||||
|
||||
|
||||
def guard(db: Session = Depends(get_db), u: User = Depends(require_engineer)) -> User:
|
||||
if not enabled(db):
|
||||
raise HTTPException(404, "Анализ надёжности выключен в настройках сервера")
|
||||
return u
|
||||
|
||||
|
||||
def period(date_from: Optional[datetime], date_to: Optional[datetime]) -> tuple[datetime, datetime]:
|
||||
end = S._dt_in(date_to) if date_to else config.now()
|
||||
start = S._dt_in(date_from) if date_from else end - timedelta(days=365)
|
||||
if start >= end:
|
||||
raise HTTPException(422, "Начало периода должно быть раньше конца")
|
||||
return start, end
|
||||
|
||||
|
||||
def machines(db: Session, u: User, department_id: Optional[int], cranes: Optional[bool], equipment_id: Optional[int]) -> set[int]:
|
||||
"""Машины, которые видит пользователь (отделы), с фильтрами отчёта."""
|
||||
stmt = select(Equipment.id)
|
||||
cond = scope.equipment_filter(db, u)
|
||||
if cond is not None:
|
||||
stmt = stmt.where(cond)
|
||||
if department_id:
|
||||
stmt = stmt.where(Equipment.department_id.in_(scope.dept_tree(db, department_id)))
|
||||
if cranes is not None:
|
||||
stmt = stmt.where(Equipment.is_crane.is_(cranes))
|
||||
if equipment_id:
|
||||
stmt = stmt.where(Equipment.id == equipment_id)
|
||||
return set(db.scalars(stmt))
|
||||
|
||||
|
||||
@router.get("/dictionaries")
|
||||
def dictionaries(_: User = Depends(guard)):
|
||||
"""Справочники формы закрытия отказа: причины, характер, что сделано, признаки заранее."""
|
||||
return {"cause": R.CAUSE_RU, "nature": R.NATURE_RU, "action": R.ACTION_RU, "precursor": R.PRECURSOR_RU}
|
||||
|
||||
|
||||
@router.get("")
|
||||
def summary(date_from: Optional[datetime] = Query(None, alias="from"), date_to: Optional[datetime] = Query(None, alias="to"),
|
||||
department_id: Optional[int] = None, cranes: Optional[bool] = None, equipment_id: Optional[int] = None,
|
||||
confidence: float = Query(0.9, ge=0.5, le=0.99), horizon_days: int = Query(30, ge=1, le=365),
|
||||
db: Session = Depends(get_db), u: User = Depends(guard)):
|
||||
"""Показатели надёжности по машинам и парку за период: λ, θ, оценки по малоотказному опыту, Tв, Kг, прогноз, причины."""
|
||||
start, end = period(date_from, date_to)
|
||||
return R.analyze(db, start, end, machines(db, u, department_id, cranes, equipment_id), confidence, horizon_days)
|
||||
|
||||
|
||||
@router.get("/drift")
|
||||
def drift(days: int = Query(180, ge=14, le=1095), horizon: int = Query(90, ge=7, le=730), department_id: Optional[int] = None,
|
||||
cranes: Optional[bool] = None, equipment_id: Optional[int] = None, db: Session = Depends(get_db), u: User = Depends(guard)):
|
||||
"""«Параметр — граница работоспособности»: дрейф числовых пунктов чек-листов к границе нормы и выходы за неё."""
|
||||
return R.drift(db, machines(db, u, department_id, cranes, equipment_id), days, horizon)
|
||||
|
||||
|
||||
@router.get("/export")
|
||||
def export(kind: Literal["failures", "service"] = "failures",
|
||||
date_from: Optional[datetime] = Query(None, alias="from"), date_to: Optional[datetime] = Query(None, alias="to"),
|
||||
department_id: Optional[int] = None, cranes: Optional[bool] = None, equipment_id: Optional[int] = None,
|
||||
db: Session = Depends(get_db), u: User = Depends(guard)):
|
||||
"""CSV для расчётов: failures — отказы и неисправности (строка на запись); service — история обслуживания узлов."""
|
||||
start, end = period(date_from, date_to)
|
||||
ids = machines(db, u, department_id, cranes, equipment_id)
|
||||
text = (R.export_failures if kind == "failures" else R.export_service_events)(db, start, end, ids)
|
||||
name = f"{'failures' if kind == 'failures' else 'service_events'}_{start:%Y%m%d}_{end:%Y%m%d}.csv"
|
||||
return Response(text, media_type="text/csv; charset=utf-8", headers={"Content-Disposition": f'attachment; filename="{name}"'})
|
||||
Reference in New Issue
Block a user