feat: complete 5 GAP requirements — auto counters, inspection alarms, machine specs, responsive CSS
All checks were successful
Deploy to Production / deploy (push) Successful in 1m8s

- GAP 2+4: auto_time/date counter auto-computation with manual update blocking
- GAP 3: auto-generate alarms on inspection completion for failed items
- GAP 1: machine spec fields (manufacturer, location, capacity, power, description)
- GAP 5: enhanced mobile responsive CSS (768px/480px breakpoints)
- Alembic migration for new columns, seed data enriched with specs and date-type parts
This commit is contained in:
Johngreen
2026-02-10 15:45:51 +09:00
parent ca29e5b809
commit 00a17c0b86
10 changed files with 564 additions and 22 deletions

View File

@@ -8,7 +8,16 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from src.database.config import get_db
from src.database.models import Alarm, EquipmentPart, PartCounter, Machine
from src.database.models import (
Alarm,
EquipmentPart,
PartCounter,
Machine,
InspectionSession,
InspectionRecord,
InspectionTemplate,
InspectionTemplateItem,
)
from src.auth.models import TokenData
from src.auth.dependencies import require_auth, verify_tenant_access
@@ -27,12 +36,19 @@ def _alarm_to_dict(alarm: Alarm) -> dict:
return {
"id": str(alarm.id),
"tenant_id": str(alarm.tenant_id),
"equipment_part_id": str(alarm.equipment_part_id),
"machine_id": str(alarm.machine_id),
"equipment_part_id": str(alarm.equipment_part_id)
if alarm.equipment_part_id
else None,
"machine_id": str(alarm.machine_id) if alarm.machine_id else None,
"inspection_session_id": str(alarm.inspection_session_id)
if alarm.inspection_session_id
else None,
"alarm_type": str(alarm.alarm_type),
"severity": str(alarm.severity),
"message": str(alarm.message),
"lifecycle_pct_at_trigger": float(alarm.lifecycle_pct_at_trigger),
"lifecycle_pct_at_trigger": float(alarm.lifecycle_pct_at_trigger)
if alarm.lifecycle_pct_at_trigger is not None
else None,
"is_acknowledged": bool(alarm.is_acknowledged),
"acknowledged_by": str(alarm.acknowledged_by)
if alarm.acknowledged_by
@@ -182,3 +198,33 @@ async def generate_alarms_for_part(
lifecycle_pct_at_trigger=pct,
)
db.add(alarm)
async def generate_alarms_for_inspection(
db: AsyncSession,
tenant_id: str,
session: InspectionSession,
fail_records: list,
template_name: str,
):
if not fail_records:
return
fail_count = len(fail_records)
severity = "critical" if fail_count >= 3 else "warning"
for record, item_name, spec_info in fail_records:
message = (
f"검사 '{template_name}''{item_name}' 항목이 불합격입니다 ({spec_info})"
)
if len(message) > 500:
message = message[:497] + "..."
alarm = Alarm(
tenant_id=tenant_id,
inspection_session_id=session.id,
alarm_type="inspection_fail",
severity=severity,
message=message,
)
db.add(alarm)