328 lines
17 KiB
Python
328 lines
17 KiB
Python
"""
|
||
Полное наполнение демо-данными для доклада: пользователи, аудиты, дефекты, заявки, риски, персонал ПЛГ.
|
||
Запускается из main.py lifespan после seed_checklists и seed_organizations.
|
||
"""
|
||
import logging
|
||
import uuid
|
||
from datetime import datetime, timezone, timedelta
|
||
|
||
from app.db.session import SessionLocal
|
||
from app.models import (
|
||
User,
|
||
Organization,
|
||
Audit,
|
||
ChecklistTemplate,
|
||
CertApplication,
|
||
CertApplicationStatus,
|
||
RiskAlert,
|
||
AirworthinessCertificate,
|
||
)
|
||
from app.models.aircraft_db import Aircraft
|
||
from app.models.personnel_plg import PLGSpecialist, PLGQualification
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def _get_org_id(db, name_substr: str):
|
||
o = db.query(Organization).filter(Organization.name.ilike(f"%{name_substr}%")).first()
|
||
return str(o.id) if o else None
|
||
|
||
|
||
def _get_aircraft_id_by_reg(db, reg: str):
|
||
a = db.query(Aircraft).filter(Aircraft.registration_number == reg).first()
|
||
return str(a.id) if a else None
|
||
|
||
|
||
def _get_first_aircraft_id(db):
|
||
a = db.query(Aircraft).first()
|
||
return str(a.id) if a else None
|
||
|
||
|
||
def _get_template_id(db):
|
||
t = db.query(ChecklistTemplate).first()
|
||
return str(t.id) if t else None
|
||
|
||
|
||
def seed_full_demo():
|
||
db = SessionLocal()
|
||
try:
|
||
# ─── 1. Пользователи ─────────────────────────────────────────────
|
||
demo_users = [
|
||
("Иванов Сергей А.", "ivanov@refly.ru", "admin", None),
|
||
("Петрова Елена В.", "petrova@refly.ru", "authority_inspector", None),
|
||
("Козлов Дмитрий И.", "kozlov@refly.ru", "operator_manager", "S7"),
|
||
("Сидорова Анна М.", "sidorova@refly.ru", "operator_user", "S7"),
|
||
("Волков Алексей Н.", "volkov@refly.ru", "mro_manager", "Домодедово"),
|
||
("Морозова Ольга С.", "morozova@refly.ru", "mro_user", "Домодедово"),
|
||
("Николаев Павел Р.", "nikolaev@refly.ru", "authority_inspector", None),
|
||
("Федорова Мария К.", "fedorova@refly.ru", "operator_user", "Smartavia"),
|
||
]
|
||
created_users = {}
|
||
for display_name, email, role, org_key in demo_users:
|
||
if db.query(User).filter(User.email == email).first():
|
||
continue
|
||
org_id = _get_org_id(db, org_key) if org_key else None
|
||
uid = str(uuid.uuid4())
|
||
db.add(
|
||
User(
|
||
id=uid,
|
||
external_subject=f"demo-{email}",
|
||
display_name=display_name,
|
||
email=email,
|
||
role=role,
|
||
organization_id=org_id,
|
||
)
|
||
)
|
||
created_users[email] = uid
|
||
db.commit()
|
||
logger.info("seed_full_demo: users checked/created")
|
||
|
||
# ─── 2. Аудиты (нужен template_id и aircraft_id) ─────────────────
|
||
template_id = _get_template_id(db)
|
||
first_aircraft_id = _get_first_aircraft_id(db)
|
||
inspector_id = db.query(User).filter(User.role == "authority_inspector").first()
|
||
inspector_id = str(inspector_id.id) if inspector_id else None
|
||
|
||
if template_id and first_aircraft_id and inspector_id:
|
||
audits_data = [
|
||
("Плановая проверка ТОиР S7 Airlines", "completed", "scheduled"),
|
||
("Внеплановая проверка после инцидента", "in_progress", "unscheduled"),
|
||
("Сертификационный аудит АТЦ Домодедово", "draft", "certification"),
|
||
("Проверка СМК авиакомпании Smartavia", "completed", "scheduled"),
|
||
("Инспекция рампы Шереметьево", "in_progress", "ramp"),
|
||
("Аудит системы управления безопасностью", "draft", "sms"),
|
||
]
|
||
base_date = datetime(2025, 6, 1, tzinfo=timezone.utc)
|
||
existing_count = db.query(Audit).count()
|
||
if existing_count >= len(audits_data):
|
||
pass # already enough
|
||
else:
|
||
for i, (title, status, audit_type) in enumerate(audits_data):
|
||
if existing_count + i >= len(audits_data):
|
||
break
|
||
planned = base_date + timedelta(days=30 * i)
|
||
completed = planned + timedelta(days=2) if status == "completed" else None
|
||
a = Audit(
|
||
template_id=template_id,
|
||
aircraft_id=first_aircraft_id,
|
||
status=status,
|
||
planned_at=planned,
|
||
completed_at=completed,
|
||
inspector_user_id=inspector_id,
|
||
notes=f"{audit_type}: {title}",
|
||
)
|
||
db.add(a)
|
||
db.commit()
|
||
logger.info("seed_full_demo: audits checked/created")
|
||
|
||
# ─── 3. Дефекты (in-memory _defects в роуте) ───────────────────
|
||
try:
|
||
from app.api.routes.defects import _defects
|
||
|
||
aircraft_regs = [r[0] for r in db.query(Aircraft.registration_number).limit(5).all()]
|
||
reg = aircraft_regs[0] if aircraft_regs else "RA-00000"
|
||
defects_demo = [
|
||
("Трещина обшивки фюзеляжа секция 41", "critical", "open"),
|
||
("Течь гидросистемы левая стойка шасси", "high", "deferred"),
|
||
("Коррозия лонжерона крыла зона 2", "high", "open"),
|
||
("Неисправность датчика угла атаки", "critical", "rectified"),
|
||
("Износ тормозных дисков выше допуска", "medium", "open"),
|
||
("Повреждение обтекателя антенны", "low", "closed"),
|
||
("Утечка топлива бак №2", "critical", "open"),
|
||
("Вибрация двигателя №1 выше нормы", "high", "deferred"),
|
||
]
|
||
for desc, severity, status in defects_demo:
|
||
if any(d.get("description") == desc for d in _defects.values()):
|
||
continue
|
||
did = str(uuid.uuid4())
|
||
_defects[did] = {
|
||
"id": did,
|
||
"aircraft_reg": reg,
|
||
"description": desc,
|
||
"severity": severity,
|
||
"status": status,
|
||
"ata_chapter": "32" if "шасси" in desc else "53" if "топлив" in desc else "21",
|
||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|
||
logger.info("seed_full_demo: defects (in-memory) populated")
|
||
except Exception as e:
|
||
logger.warning("seed_full_demo: defects skip %s", e)
|
||
|
||
# ─── 4. Заявки на сертификацию ─────────────────────────────────
|
||
app_org = db.query(Organization).filter(Organization.kind == "operator").first()
|
||
app_user = db.query(User).filter(User.role == "operator_manager").first()
|
||
if app_org and app_user:
|
||
apps_data = [
|
||
("Заявка на СЛГ RA-89060", CertApplicationStatus.APPROVED, "airworthiness_certificate"),
|
||
("Заявка на одобрение ТОиР АТЦ Кольцово", CertApplicationStatus.UNDER_REVIEW, "mro_approval"),
|
||
("Продление СЛГ RA-73801", CertApplicationStatus.SUBMITTED, "renewal"),
|
||
("Заявка на допуск ETOPS", CertApplicationStatus.DRAFT, "special_approval"),
|
||
("Сертификация нового типа Ту-214", CertApplicationStatus.UNDER_REVIEW, "type_certificate"),
|
||
]
|
||
for i, (subject, status, app_type) in enumerate(apps_data):
|
||
num = f"KLG-DEMO-{datetime.now().strftime('%Y%m%d')}-{1001 + i}"
|
||
if db.query(CertApplication).filter(CertApplication.number == num).first():
|
||
continue
|
||
db.add(
|
||
CertApplication(
|
||
applicant_org_id=str(app_org.id),
|
||
created_by_user_id=str(app_user.id),
|
||
number=num,
|
||
status=status,
|
||
subject=f"[{app_type}] {subject}",
|
||
description=subject,
|
||
submitted_at=datetime.now(timezone.utc) - timedelta(days=5 - i)
|
||
if status != CertApplicationStatus.DRAFT
|
||
else None,
|
||
)
|
||
)
|
||
db.commit()
|
||
logger.info("seed_full_demo: cert applications checked/created")
|
||
|
||
# ─── 5. Риски (RiskAlert) ──────────────────────────────────────
|
||
risk_titles = [
|
||
("Просрочка директивы лётной годности AD-2025-0142", "critical", False),
|
||
("Недостаточная квалификация персонала ТОиР", "high", True),
|
||
("Устаревшее оборудование контроля", "medium", False),
|
||
("Нарушение сроков периодического ТО", "high", False),
|
||
("Отсутствие резервных компонентов", "medium", True),
|
||
("Риск усталостного разрушения фюзеляжа", "critical", True),
|
||
]
|
||
ac_id = _get_first_aircraft_id(db)
|
||
for title, severity, resolved in risk_titles:
|
||
if db.query(RiskAlert).filter(RiskAlert.title == title).first():
|
||
continue
|
||
db.add(
|
||
RiskAlert(
|
||
entity_type="directive",
|
||
entity_id=str(uuid.uuid4()),
|
||
aircraft_id=ac_id,
|
||
severity=severity,
|
||
title=title,
|
||
message=title,
|
||
due_at=datetime.now(timezone.utc) + timedelta(days=30),
|
||
is_resolved=resolved,
|
||
resolved_at=datetime.now(timezone.utc) if resolved else None,
|
||
)
|
||
)
|
||
db.commit()
|
||
logger.info("seed_full_demo: risk_alerts checked/created")
|
||
|
||
# ─── 6. Персонал ПЛГ (PLGSpecialist + PLGQualification) ──────────
|
||
plg_org = db.query(Organization).filter(Organization.kind == "operator").first()
|
||
if plg_org:
|
||
specialists_data = [
|
||
("Петрова Елена В.", "ПЕТРОВА-001", "Инспектор ЛГ", "I", "LIC-2024-001", "2026-12-01"),
|
||
("Волков Алексей Н.", "ВОЛКОВ-001", "Инженер по ТО", "B2", "LIC-2024-002", "2026-08-15"),
|
||
("Морозова Ольга С.", "МОРОЗОВА-001", "Авиатехник B1", "B1", "LIC-2024-003", "2026-03-20"),
|
||
("Козлов Дмитрий И.", "КОЗЛОВ-001", "Инженер CAT.A", "CAT-A", "LIC-2024-004", "2027-01-10"),
|
||
("Николаев Павел Р.", "НИКОЛАЕВ-001", "Инспектор Росавиации", "I", "LIC-2024-005", "2026-09-30"),
|
||
("Сидорова Анна М.", "СИДОРОВА-001", "Диспетчер ТОиР", "D", "LIC-2024-006", "2026-11-15"),
|
||
]
|
||
for full_name, personnel_number, position, category, license_no, expires in specialists_data:
|
||
if db.query(PLGSpecialist).filter(PLGSpecialist.personnel_number == personnel_number).first():
|
||
continue
|
||
exp_dt = datetime.strptime(expires, "%Y-%m-%d").date()
|
||
spec = PLGSpecialist(
|
||
organization_id=str(plg_org.id),
|
||
full_name=full_name,
|
||
personnel_number=personnel_number,
|
||
position=position,
|
||
category=category,
|
||
license_number=license_no,
|
||
license_expires=exp_dt,
|
||
status="active",
|
||
)
|
||
db.add(spec)
|
||
db.flush()
|
||
# одна квалификация на специалиста
|
||
q = PLGQualification(
|
||
specialist_id=spec.id,
|
||
program_id="PQ-001",
|
||
program_name=f"ПК по {position}",
|
||
program_type="periodic",
|
||
date_start=exp_dt - timedelta(days=365),
|
||
date_end=exp_dt,
|
||
hours_total=40,
|
||
result="passed",
|
||
next_due=exp_dt,
|
||
)
|
||
db.add(q)
|
||
db.commit()
|
||
logger.info("seed_full_demo: personnel PLG checked/created")
|
||
|
||
# ─── 7. Сертификаты лётной годности (СЛГ) ─────────────────────────
|
||
issuer = db.query(User).filter(User.role.in_(["admin", "authority_inspector"])).first()
|
||
issuer_id = str(issuer.id) if issuer else None
|
||
certs_data = [
|
||
("KLG-2025-001", "RA-89060", "2026-08-15", "valid"),
|
||
("KLG-2025-002", "RA-89061", "2026-05-20", "valid"),
|
||
("KLG-2024-015", "RA-73801", "2026-02-28", "expiring_soon"),
|
||
("KLG-2025-003", "RA-12345", "2027-01-10", "valid"),
|
||
]
|
||
for cert_num, reg, valid_until, status in certs_data:
|
||
if db.query(AirworthinessCertificate).filter(AirworthinessCertificate.certificate_number == cert_num).first():
|
||
continue
|
||
ac_id = _get_aircraft_id_by_reg(db, reg)
|
||
if not ac_id:
|
||
ac_id = _get_first_aircraft_id(db)
|
||
if not ac_id or not issuer_id:
|
||
continue
|
||
exp_d = datetime.strptime(valid_until, "%Y-%m-%d").date()
|
||
issue_d = exp_d - timedelta(days=365)
|
||
db.add(
|
||
AirworthinessCertificate(
|
||
aircraft_id=ac_id,
|
||
certificate_number=cert_num,
|
||
certificate_type="standard",
|
||
issue_date=datetime(issue_d.year, issue_d.month, issue_d.day, tzinfo=timezone.utc),
|
||
expiry_date=datetime(exp_d.year, exp_d.month, exp_d.day, tzinfo=timezone.utc),
|
||
issuing_authority="Росавиация",
|
||
issued_by_user_id=issuer_id,
|
||
status=status,
|
||
)
|
||
)
|
||
db.commit()
|
||
logger.info("seed_full_demo: airworthiness certificates checked/created")
|
||
|
||
# ─── 8. Наряды на работу (Work Orders, in-memory) ──────────────────
|
||
try:
|
||
from app.api.routes.work_orders import _work_orders
|
||
aircraft_regs = [r[0] for r in db.query(Aircraft.registration_number).limit(5).all()]
|
||
reg1 = aircraft_regs[0] if aircraft_regs else "RA-89060"
|
||
reg2 = aircraft_regs[1] if len(aircraft_regs) > 1 else "RA-89061"
|
||
reg3 = aircraft_regs[2] if len(aircraft_regs) > 2 else "RA-73801"
|
||
reg4 = aircraft_regs[3] if len(aircraft_regs) > 3 else "RA-12345"
|
||
wos_demo = [
|
||
("WO-2026-001", reg1, "Периодическое ТО A-Check", "in_progress", "urgent"),
|
||
("WO-2026-002", reg2, "Замена колеса основной стойки", "open", "normal"),
|
||
("WO-2026-003", reg3, "Устранение течи гидросистемы", "completed", "urgent"),
|
||
("WO-2026-004", reg1, "Плановая замена фильтров двигателя", "open", "normal"),
|
||
("WO-2026-005", reg4, "Внеплановое ТО после bird strike", "in_progress", "urgent"),
|
||
]
|
||
for wo_num, reg, title, status, priority in wos_demo:
|
||
if any(w.get("wo_number") == wo_num for w in _work_orders.values()):
|
||
continue
|
||
wid = str(uuid.uuid4())
|
||
_work_orders[wid] = {
|
||
"id": wid, "wo_number": wo_num, "aircraft_reg": reg, "title": title,
|
||
"wo_type": "scheduled" if "Планов" in title else "unscheduled",
|
||
"description": title, "status": status, "priority": priority,
|
||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||
}
|
||
logger.info("seed_full_demo: work orders (in-memory) populated")
|
||
except Exception as e:
|
||
logger.warning("seed_full_demo: work orders skip %s", e)
|
||
|
||
except Exception as e:
|
||
db.rollback()
|
||
logger.exception("seed_full_demo failed: %s", e)
|
||
raise
|
||
finally:
|
||
db.close()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
logging.basicConfig(level=logging.INFO)
|
||
seed_full_demo()
|