klg-asutk-app/backend/app/api/routes/checklists.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

246 lines
9.3 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 fastapi import APIRouter, Depends, HTTPException, File, UploadFile
from sqlalchemy.orm import Session
import csv
import io
from app.api.deps import get_current_user, require_roles
from app.db.session import get_db
from app.models import ChecklistTemplate, ChecklistItem
from app.schemas.audit import (
ChecklistTemplateCreate, ChecklistTemplateOut,
ChecklistItemCreate, ChecklistItemOut
)
router = APIRouter(tags=["checklists"])
@router.get("/checklists/templates", response_model=list[ChecklistTemplateOut])
def list_templates(
domain: str | None = None,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Список шаблонов чек-листов."""
q = db.query(ChecklistTemplate).filter(ChecklistTemplate.is_active == True)
if domain:
q = q.filter(ChecklistTemplate.domain == domain)
templates = q.order_by(ChecklistTemplate.name, ChecklistTemplate.version.desc()).all()
result = []
for t in templates:
items = db.query(ChecklistItem).filter(ChecklistItem.template_id == t.id).order_by(ChecklistItem.sort_order).all()
out = ChecklistTemplateOut.model_validate(t)
out.items = [ChecklistItemOut.model_validate(i) for i in items]
result.append(out)
return result
@router.post(
"/checklists/templates",
response_model=ChecklistTemplateOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
)
def create_template(
payload: ChecklistTemplateCreate,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Создаёт новый шаблон чек-листа."""
template = ChecklistTemplate(
name=payload.name,
version=payload.version,
description=payload.description,
domain=payload.domain
)
db.add(template)
db.flush()
if payload.items:
for item_data in payload.items:
item = ChecklistItem(
template_id=template.id,
code=item_data.code,
text=item_data.text,
domain=item_data.domain,
sort_order=item_data.sort_order
)
db.add(item)
db.commit()
db.refresh(template)
out = ChecklistTemplateOut.model_validate(template)
if payload.items:
items = db.query(ChecklistItem).filter(ChecklistItem.template_id == template.id).all()
out.items = [ChecklistItemOut.model_validate(i) for i in items]
return out
@router.post(
"/checklists/generate",
response_model=ChecklistTemplateOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
)
def generate_checklist(
source: str, # "fap_m_inspection" | "ata" | "custom"
name: str,
items: list[ChecklistItemCreate] | None = None,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Автоматически генерирует чек-лист из предустановленного шаблона или пользовательских данных."""
template = ChecklistTemplate(name=name, version=1, domain=source)
db.add(template)
db.flush()
if source == "fap_m_inspection":
# Предустановленный шаблон для проверки по ФАП-М
preset_items = [
ChecklistItemCreate(code="M.A.301", text="ВС имеет действующий сертификат лётной годности", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.302", text="ВС эксплуатируется в соответствии с CAME", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.303", text="Выполнены все требования по техническому обслуживанию", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.304", text="Все дефекты устранены или имеют действующие разрешения на полёты", domain="ФАП-М"),
ChecklistItemCreate(code="M.A.305", text="Документация по ВС актуальна и соответствует требованиям", domain="ФАП-М"),
]
items = preset_items
elif source == "custom" and items:
pass # Используем переданные items
else:
raise HTTPException(status_code=400, detail="Неверный source или отсутствуют items")
for item_data in items:
item = ChecklistItem(
template_id=template.id,
code=item_data.code,
text=item_data.text,
domain=item_data.domain,
sort_order=item_data.sort_order
)
db.add(item)
db.commit()
db.refresh(template)
out = ChecklistTemplateOut.model_validate(template)
items_db = db.query(ChecklistItem).filter(ChecklistItem.template_id == template.id).all()
out.items = [ChecklistItemOut.model_validate(i) for i in items_db]
return out
@router.get("/checklists/templates/{template_id}", response_model=ChecklistTemplateOut)
def get_template(
template_id: str,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Получает шаблон с пунктами."""
template = db.query(ChecklistTemplate).filter(ChecklistTemplate.id == template_id).first()
if not template:
raise HTTPException(status_code=404, detail="Шаблон не найден")
items = db.query(ChecklistItem).filter(ChecklistItem.template_id == template_id).order_by(ChecklistItem.sort_order).all()
out = ChecklistTemplateOut.model_validate(template)
out.items = [ChecklistItemOut.model_validate(i) for i in items]
return out
@router.post(
"/checklists/generate-from-csv",
response_model=ChecklistTemplateOut,
dependencies=[Depends(require_roles("admin", "authority_inspector"))],
)
async def generate_checklist_from_csv(
file: UploadFile = File(...),
name: str | None = None,
domain: str | None = None,
db: Session = Depends(get_db),
user=Depends(get_current_user),
):
"""Генерирует чек-лист из CSV файла (например, из REFLY_Jira_Backlog_Subtasks_Dependencies.csv).
Ожидаемые колонки в CSV:
- Issue Id (или Issue Id) - код пункта
- Summary (или Description) - текст требования
- Domain (опционально) - домен
- Story Points (опционально) - для sort_order
"""
content = await file.read()
# Парсим CSV
try:
text = content.decode('utf-8-sig')
except UnicodeDecodeError:
text = content.decode('cp1251')
reader = csv.DictReader(io.StringIO(text))
fieldnames = reader.fieldnames or []
rows = list(reader)
if not rows:
raise HTTPException(status_code=400, detail="CSV файл пуст или неверного формата")
# Определяем колонки (поддерживаем разные варианты названий)
code_col = None
text_col = None
domain_col = None
order_col = None
for col in fieldnames:
col_lower = col.lower()
if 'issue' in col_lower and 'id' in col_lower and not code_col:
code_col = col
elif ('summary' in col_lower or 'description' in col_lower) and not text_col:
text_col = col
elif 'domain' in col_lower and not domain_col:
domain_col = col
elif ('story' in col_lower and 'point' in col_lower) or 'order' in col_lower:
order_col = col
if not code_col or not text_col:
raise HTTPException(status_code=400, detail="Не найдены обязательные колонки: Issue Id и Summary/Description")
# Создаём шаблон
template_name = name or file.filename.replace('.csv', '').replace('_', ' ').title()
template = ChecklistTemplate(
name=template_name,
version=1,
domain=domain or "REFLY_CSV"
)
db.add(template)
db.flush()
# Создаём пункты
items_created = []
for idx, row in enumerate(rows):
code = str(row.get(code_col, f"ITEM_{idx+1}")).strip()
text = str(row.get(text_col, "")).strip()
if not code or not text:
continue # Пропускаем пустые строки
item_domain = domain or (row.get(domain_col, "") if domain_col else None) or None
sort_order = int(row.get(order_col, idx + 1)) if order_col and row.get(order_col) else idx + 1
item = ChecklistItem(
template_id=template.id,
code=code,
text=text,
domain=item_domain,
sort_order=sort_order
)
db.add(item)
items_created.append(item)
if not items_created:
db.rollback()
raise HTTPException(status_code=400, detail="Не удалось создать ни одного пункта из CSV")
db.commit()
db.refresh(template)
out = ChecklistTemplateOut.model_validate(template)
items_db = db.query(ChecklistItem).filter(ChecklistItem.template_id == template.id).order_by(ChecklistItem.sort_order).all()
out.items = [ChecklistItemOut.model_validate(i) for i in items_db]
return out