klg-asutk-app/backend/app/api/routes/checklist_audits.py
Yuriy 0150aba4f5 Consolidation: KLG ASUTK + PAPA integration
- Unify API: lib/api.ts uses /api/v1, inbox uses /api/inbox (rewrites)
- Remove localhost refs: openapi, inbox page
- Add rewrites: /api/inbox|tmc -> inbox-server, /api/v1 -> FastAPI
- Add stub routes: knowledge/insights, recommendations, search, log-error
- Transfer from PAPA: prompts (inspection, tmc), scripts, supabase, data/tmc-requests
- Fix inbox-server: ORDER BY created_at, package.json
- Remove redundant app/api/inbox/files route (rewrites handle it)
- knowledge/ in gitignore (large PDFs)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 17:18:31 +03:00

218 lines
7.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""API для проведения аудитов (проверок) по чек-листам."""
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, require_roles
from app.db.session import get_db
from app.models import Audit, AuditResponse, Finding, ChecklistTemplate, ChecklistItem, Aircraft
from app.schemas.audit import (
AuditCreate, AuditOut, AuditResponseCreate, AuditResponseOut, FindingOut
)
router = APIRouter(tags=["checklist-audits"])
@router.get("/audits", response_model=list[AuditOut])
def list_audits(
aircraft_id: str | None = None,
status: str | None = None,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Список аудитов."""
q = db.query(Audit)
if aircraft_id:
q = q.filter(Audit.aircraft_id == aircraft_id)
if status:
q = q.filter(Audit.status == status)
# Фильтр по организации для операторов
if user.role.startswith("operator") and user.organization_id:
q = q.join(Aircraft).filter(Aircraft.operator_id == user.organization_id)
audits = q.order_by(Audit.created_at.desc()).limit(200).all()
return [AuditOut.model_validate(a) for a in audits]
@router.post(
"/audits",
response_model=AuditOut,
dependencies=[Depends(require_roles("admin", "authority_inspector", "operator_manager"))],
)
def create_audit(
payload: AuditCreate,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Создаёт новый аудит."""
template = db.query(ChecklistTemplate).filter(ChecklistTemplate.id == payload.template_id).first()
if not template:
raise HTTPException(status_code=404, detail="Шаблон не найден")
aircraft = db.query(Aircraft).filter(Aircraft.id == payload.aircraft_id).first()
if not aircraft:
raise HTTPException(status_code=404, detail="ВС не найдено")
audit = Audit(
template_id=payload.template_id,
aircraft_id=payload.aircraft_id,
planned_at=payload.planned_at,
inspector_user_id=user.id,
status="draft"
)
db.add(audit)
db.commit()
db.refresh(audit)
return AuditOut.model_validate(audit)
@router.get("/audits/{audit_id}", response_model=AuditOut)
def get_audit(
audit_id: str,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Получает аудит с ответами и находками."""
audit = db.query(Audit).filter(Audit.id == audit_id).first()
if not audit:
raise HTTPException(status_code=404, detail="Аудит не найден")
return AuditOut.model_validate(audit)
@router.post(
"/audits/{audit_id}/responses",
response_model=AuditResponseOut,
dependencies=[Depends(require_roles("admin", "authority_inspector", "operator_manager"))],
)
def submit_response(
audit_id: str,
payload: AuditResponseCreate,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Отправляет ответ по пункту чек-листа. Автоматически создаёт Finding при non_compliant."""
audit = db.query(Audit).filter(Audit.id == audit_id).first()
if not audit:
raise HTTPException(status_code=404, detail="Аудит не найден")
if audit.status == "completed":
raise HTTPException(status_code=400, detail="Аудит уже завершён")
item = db.query(ChecklistItem).filter(ChecklistItem.id == payload.item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Пункт чек-листа не найден")
# Проверяем, нет ли уже ответа
existing = db.query(AuditResponse).filter(
AuditResponse.audit_id == audit_id,
AuditResponse.item_id == payload.item_id
).first()
if existing:
existing.answer = payload.answer
existing.comment = payload.comment
existing.evidence_attachment_ids = payload.evidence_attachment_ids
response = existing
else:
response = AuditResponse(
audit_id=audit_id,
item_id=payload.item_id,
answer=payload.answer,
comment=payload.comment,
evidence_attachment_ids=payload.evidence_attachment_ids
)
db.add(response)
# Автоматически создаём Finding при non_compliant
if payload.answer == "non_compliant":
existing_finding = db.query(Finding).filter(
Finding.audit_id == audit_id,
Finding.item_id == payload.item_id
).first()
if not existing_finding:
# Определяем severity и risk_score на основе типа пункта
severity = "high"
risk_score = 75
if "критич" in item.text.lower() or "безопасн" in item.text.lower():
severity = "critical"
risk_score = 100
elif "рекоменд" in item.text.lower():
severity = "medium"
risk_score = 50
finding = Finding(
audit_id=audit_id,
response_id=response.id if hasattr(response, 'id') else None,
item_id=payload.item_id,
severity=severity,
risk_score=risk_score,
status="open",
description=f"Несоответствие по пункту {item.code}: {item.text}"
)
db.add(finding)
# Обновляем статус аудита
if audit.status == "draft":
audit.status = "in_progress"
db.commit()
db.refresh(response)
return AuditResponseOut.model_validate(response)
@router.get("/audits/{audit_id}/responses", response_model=list[AuditResponseOut])
def list_responses(
audit_id: str,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Список ответов по аудиту."""
audit = db.query(Audit).filter(Audit.id == audit_id).first()
if not audit:
raise HTTPException(status_code=404, detail="Аудит не найден")
responses = db.query(AuditResponse).filter(AuditResponse.audit_id == audit_id).all()
return [AuditResponseOut.model_validate(r) for r in responses]
@router.get("/audits/{audit_id}/findings", response_model=list[FindingOut])
def list_findings(
audit_id: str,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Список находок (несоответствий) по аудиту."""
audit = db.query(Audit).filter(Audit.id == audit_id).first()
if not audit:
raise HTTPException(status_code=404, detail="Аудит не найден")
findings = db.query(Finding).filter(Finding.audit_id == audit_id).order_by(Finding.severity.desc()).all()
return [FindingOut.model_validate(f) for f in findings]
@router.patch(
"/audits/{audit_id}/complete",
response_model=AuditOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
)
def complete_audit(
audit_id: str,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Завершает аудит."""
audit = db.query(Audit).filter(Audit.id == audit_id).first()
if not audit:
raise HTTPException(status_code=404, detail="Аудит не найден")
audit.status = "completed"
audit.completed_at = datetime.now(timezone.utc)
db.commit()
db.refresh(audit)
return AuditOut.model_validate(audit)