Files
factoryOps-v2/planning/development-plan.md
Johngreen ab2a3e35b2 feat: Phase 0-2 complete — auth, machines, equipment parts with full CRUD
Multi-tenant factory inspection system (SpiFox, Enkid, Alpet):
- FastAPI backend with JWT auth, PostgreSQL (asyncpg)
- Next.js 16 frontend with App Router, SWR data fetching
- Machines CRUD with equipment parts management
- Part lifecycle tracking (hours/count/date) with counters
- Partial unique index for soft-delete support
- 24 pytest tests passing, E2E verified

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-02-10 12:05:22 +09:00

1049 lines
137 KiB
Markdown
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.
# FactoryOps v2 — 개발 계획서 (Complete Plan)
> 작성일: 2026-02-09 (최종 업데이트: 2026-02-10)
> 기반 자료: 개발회의.md (멘토 회의), spifox-inspection-analysis.md (아키텍처 분석), integrated-analysis.md (3사 통합분석), Oracle 컨설팅
> 대상 고객사: 스피폭스(SpiFox), 엔키드(Enkid), 알펫(Alpet)
> ⚠️ **새 프로젝트** — 기존 factoryOps 코드베이스는 **참고만** 하며, 필요한 코드만 발췌하여 새로 작성합니다.
> ⚠️ **PostgreSQL 전용** — SQLite fallback 없음. 테스트 DB도 PostgreSQL. JSONB, Array 등 PostgreSQL 네이티브 기능 적극 활용.
### 핵심 설계 원칙 (2026-02-10 추가)
> **"TYPE 필드로 분기하고, 테넌트 ID로는 절대 분기하지 않는다."**
> — Oracle Architecture Consultation
- 업체별 차이는 `lifecycle_type`, `data_type`, `inspection_mode`, `alarm_type` 등 엔티티의 TYPE 필드에서 결정
- 코어 앱에 `if tenant == "spifox"` 같은 분기 = **0개**
- 상세 분석: `planning/integrated-analysis.md` 참조
---
## 0. 프로젝트 셋업 절차
### 디렉토리 전환
```bash
mv /Users/johngreen/Dev/factoryOps /Users/johngreen/Dev/factoryops_backup
mkdir /Users/johngreen/Dev/factoryOps
cd /Users/johngreen/Dev/factoryOps
git init
```
### 새 프로젝트 디렉토리 구조
```
factoryOps/
├── main.py # FastAPI 앱 엔트리포인트 (최소화, 목표 100줄 이내)
├── requirements.txt # Python 의존성 (경량화)
├── alembic.ini # DB 마이그레이션 설정
├── alembic/
│ ├── env.py
│ └── versions/ # 마이그레이션 파일
├── src/
│ ├── __init__.py
│ ├── auth/ # ✨ 기존 참고하여 새로 작성
│ │ ├── __init__.py
│ │ ├── jwt_handler.py # JWT 토큰 생성/검증 (HS256, 24h 만료)
│ │ ├── password.py # bcrypt 해싱
│ │ ├── dependencies.py # FastAPI auth 의존성 (get_current_user, require_auth 등)
│ │ ├── models.py # Pydantic auth 모델 (User, Token, TokenData 등)
│ │ ├── service.py # 인증 비즈니스 로직 (login, create_user 등)
│ │ └── router.py # 인증 API 엔드포인트 (/api/auth/login, /me 등)
│ ├── database/
│ │ ├── __init__.py
│ │ ├── config.py # ✨ 새로 작성 (PostgreSQL 전용, asyncpg + SQLAlchemy async)
│ │ └── models.py # ✨ 새로 작성 (아래 스키마 참조)
│ ├── tenant/
│ │ ├── __init__.py
│ │ └── manager.py # ✨ 기존 참고하여 새로 작성 (간소화)
│ ├── api/ # ✨ 모두 새로 작성
│ │ ├── __init__.py
│ │ ├── machines.py # 설비 CRUD
│ │ ├── equipment_parts.py # 설비 부품 관리
│ │ ├── templates.py # 검사 템플릿 CRUD
│ │ ├── sessions.py # 검사 세션 (실행 + 자동저장)
│ │ ├── counters.py # 부품 카운터 + 교체
│ │ └── alarms.py # 알람 조회/확인
│ └── services/ # ✨ 비즈니스 로직 분리
│ ├── __init__.py
│ ├── counter_service.py # 카운터 갱신/리셋 로직 (optimistic locking)
│ └── alarm_service.py # 알람 발생/조회 로직
├── tests/
│ ├── conftest.py # pytest fixtures (PostgreSQL 테스트 DB, test client, test user)
│ ├── test_auth.py
│ ├── test_machines.py
│ ├── test_templates.py
│ ├── test_sessions.py
│ ├── test_counters.py
│ └── test_alarms.py
├── dashboard/ # Next.js 프론트엔드
│ ├── app/
│ │ ├── layout.tsx # ✨ 기존 참고하여 새로 작성 (Providers 래핑)
│ │ ├── page.tsx # 테넌트 선택 (superadmin) / 리다이렉트
│ │ ├── login/page.tsx # ✨ 기존 참고하여 새로 작성
│ │ ├── globals.css # ✨ 기존 참고하여 새로 작성 (Material Design 3 CSS 변수)
│ │ └── [tenant]/
│ │ ├── layout.tsx # 테넌트 레이아웃 (TopNav + AuthGuard)
│ │ ├── page.tsx # 대시보드 (설비 목록 + 부품 현황 + 교체 임박)
│ │ ├── machines/
│ │ │ └── [id]/page.tsx # 설비 상세 (부품 탭, 카운터 탭, 교체 이력)
│ │ ├── templates/
│ │ │ ├── page.tsx # 검사 템플릿 목록 (설비/품질 탭)
│ │ │ ├── new/page.tsx # 템플릿 생성
│ │ │ └── [id]/page.tsx # 템플릿 편집
│ │ ├── inspections/
│ │ │ ├── page.tsx # 검사 세션 목록 (진행중/완료 탭)
│ │ │ └── [id]/page.tsx # 검사 실행 화면 (자동저장)
│ │ ├── alarms/
│ │ │ └── page.tsx # 알람 목록 (필터: 타입/심각도/확인여부)
│ │ └── settings/
│ │ └── page.tsx # 테넌트 설정
│ ├── components/ # ✨ 새로 작성 (기존 패턴 참고)
│ │ ├── TopNav.tsx # ✨ 새로 작성 (설비/검사/알람 메뉴)
│ │ ├── AuthGuard.tsx # ✨ 기존 참고하여 새로 작성
│ │ ├── Toast.tsx # ✨ 기존 참고하여 새로 작성
│ │ ├── Card.tsx # ✨ 기존 참고하여 새로 작성
│ │ ├── MachineList.tsx # 설비 목록 (간소화 재작성)
│ │ ├── PartLifecycleGauge.tsx # 부품 수명 게이지 (원형/바, 새로)
│ │ ├── InspectionForm.tsx # 검사 실행 폼 (핵심, 새로, 자동저장)
│ │ ├── TemplateEditor.tsx # 템플릿 항목 편집기 (새로)
│ │ ├── AlarmBadge.tsx # 알람 뱃지 (새로)
│ │ └── CounterCard.tsx # 카운터 현황 카드 (새로)
│ ├── lib/
│ │ ├── api.ts # ✨ 기존 참고하여 새로 작성 (fetcher, api.get/post/put/delete)
│ │ ├── auth-context.tsx # ✨ 기존 참고하여 새로 작성 (JWT localStorage 관리)
│ │ ├── tenant-context.tsx # ✨ 기존 참고하여 새로 작성 (URL에서 tenant_id 추출)
│ │ ├── toast-context.tsx # ✨ 기존 참고하여 새로 작성
│ │ ├── hooks.ts # ✨ 새 도메인 훅 (useData 패턴 유지)
│ │ └── types.ts # ✨ 새 타입 정의
│ ├── package.json
│ ├── next.config.ts
│ └── tsconfig.json
├── planning/ # 작업 메모리
│ ├── task_plan.md
│ ├── findings.md
│ └── progress.md
├── CLAUDE.md # 프로젝트 가이드
└── .env.example # 환경변수 템플릿
```
---
## 1. 데이터베이스 스키마 (전체)
> **Note**: PostgreSQL 전용 — JSONB (인덱싱, 쿼리 가능), UUID 네이티브 타입, TIMESTAMP WITH TIME ZONE, GIN 인덱스 활용
> SQLite 호환성 고려 불필요. PostgreSQL 전용 기능을 적극 사용합니다.
### 기본 테이블 (3개) — 새로 작성
```sql
-- 사용자
User
id UUID PK DEFAULT gen_random_uuid()
email VARCHAR(255) UNIQUE NOT NULL
password_hash VARCHAR(255) NOT NULL
name VARCHAR(100) NOT NULL
role VARCHAR(20) NOT NULL -- 'superadmin' | 'tenant_admin' | 'user'
tenant_id VARCHAR(50) NULL
is_active BOOLEAN DEFAULT true
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
-- 테넌트
Tenant
id VARCHAR(50) PK -- 예: 'spifox'
name VARCHAR(100) NOT NULL
industry_type VARCHAR(50) DEFAULT 'general'
is_active BOOLEAN DEFAULT true
enabled_modules JSONB NULL -- {"inspection": true, ...}
workflow_config JSONB NULL -- GIN 인덱스 사용 가능
created_at TIMESTAMPTZ DEFAULT now()
-- 설비
Machine
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
name VARCHAR(100) NOT NULL -- "1호기", "프레스 A-01"
equipment_code VARCHAR(50) DEFAULT '' -- 설비코드
model VARCHAR(100) NULL -- 모델명
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
INDEX(tenant_id)
```
### 도메인 테이블 (7개) — 새로 작성
```sql
-- 1. 설비 장착 부품 (EquipmentPart)
-- 재고 부품이 아닌, 설비에 실제 장착된 부품 인스턴스
-- 각 부품은 수명 관리 방식(시간/타발수/날짜)과 한계값을 가짐
EquipmentPart
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
machine_id UUID FK machines.id NOT NULL
name VARCHAR(100) NOT NULL -- "상부 금형", "하부 금형", "플런저 팁"
part_number VARCHAR(50) NULL -- 부품번호 (optional, 재고 연결용)
category VARCHAR(50) NULL -- "mold", "tip", "sensor", "cylinder", "chemical", "heater"
lifecycle_type VARCHAR(20) NOT NULL -- "hours" | "count" | "date"
lifecycle_limit FLOAT NOT NULL -- 수명 한계값 (예: 10000타, 5000시간, 30일)
alarm_threshold FLOAT DEFAULT 90.0 -- 알람 시작 % (예: 90.0 = 90%에 도달하면 알람)
counter_source VARCHAR(20) DEFAULT 'manual' -- ✨ 신규: "auto_plc" | "auto_time" | "manual"
-- auto_plc: PLC에서 자동 갱신 (스피폭스 shotno, 엔키드 shots)
-- auto_time: 장착일 기준 경과시간 자동 계산 (알펫 히터, 약품)
-- manual: 사용자가 직접 입력
installed_at TIMESTAMPTZ NOT NULL -- 현재 부품 장착일
is_active BOOLEAN DEFAULT true
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
UNIQUE(tenant_id, machine_id, name)
INDEX(tenant_id)
INDEX(tenant_id, machine_id)
-- 2. 검사 템플릿 (InspectionTemplate)
-- 설비검사와 품질검사를 subject_type으로 구분하는 공용 템플릿
-- 하나의 엔진, 이중 대상 원칙 (선배 개발자 + Oracle 컨설팅 합의)
InspectionTemplate
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
name VARCHAR(200) NOT NULL -- "1호기 일일검사", "완제품 A-Type 검사"
subject_type VARCHAR(20) NOT NULL -- "equipment" | "product"
machine_id UUID FK machines.id NULL -- subject_type='equipment'일 때
product_code VARCHAR(50) NULL -- subject_type='product'일 때 (미래 확장)
schedule_type VARCHAR(20) NOT NULL -- "daily"|"weekly"|"monthly"|"yearly"|"ad_hoc"
inspection_mode VARCHAR(20) DEFAULT 'measurement' -- ✨ 신규: "checklist" | "measurement" | "monitoring"
-- checklist: 빠른 OK/NG 체크 (스피폭스 순회검사)
-- measurement: 정밀 측정 폼 (엔키드 품질검사)
-- monitoring: 연속 모니터링 (알펫 라인 관측)
-- 📌 테넌트가 아닌 템플릿에 설정 → 같은 업체도 검사별로 다른 모드 사용 가능
version INTEGER DEFAULT 1 -- 버전 관리 (수정 시 증가)
is_active BOOLEAN DEFAULT true
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
INDEX(tenant_id, subject_type)
INDEX(tenant_id, machine_id)
-- 3. 검사 항목 정의 (InspectionTemplateItem)
-- 각 템플릿에 속하는 검사 항목들. 사용자가 자유롭게 추가/삭제 가능
-- data_type에 따라 입력 방식이 달라짐 (numeric, boolean, text, select)
InspectionTemplateItem
id UUID PK DEFAULT gen_random_uuid()
template_id UUID FK inspection_templates.id ON DELETE CASCADE
sort_order INTEGER NOT NULL -- 표시 순서
name VARCHAR(200) NOT NULL -- "유압 압력", "금형 온도", "외관 상태"
category VARCHAR(100) NULL -- "안전", "작동", "품질", "청소"
data_type VARCHAR(20) NOT NULL -- "numeric"|"boolean"|"text"|"select"
unit VARCHAR(20) NULL -- "℃", "bar", "mm", "개" (numeric일 때)
spec_min FLOAT NULL -- 하한 스펙 (numeric일 때, NULL이면 체크 안 함)
spec_max FLOAT NULL -- 상한 스펙 (numeric일 때, NULL이면 체크 안 함)
warning_min FLOAT NULL -- ✨ 신규: 소프트 하한 (trend_warning용, 경고구간 시작)
warning_max FLOAT NULL -- ✨ 신규: 소프트 상한 (trend_warning용, 경고구간 시작)
trend_window INTEGER NULL -- ✨ 신규: 연속 N회 경고구간 진입 시 trend_warning 알람 (NULL=비활성)
select_options JSONB NULL -- data_type='select'일 때 ["양호","불량","N/A"]
equipment_part_id UUID FK equipment_parts.id NULL -- 부품 연결 (optional)
is_required BOOLEAN DEFAULT true -- 필수 입력 여부
created_at TIMESTAMPTZ DEFAULT now()
INDEX(template_id, sort_order)
-- 4. 검사 세션 (InspectionSession)
-- 검사 실행 단위. "진행 중 저장"이 핵심 요구사항.
-- 세션 시작 시 template_snapshot에 템플릿+항목 전체를 JSON으로 스냅샷하여
-- 이후 템플릿이 수정되어도 기존 검사 이력이 보호됨 (버전 보호)
InspectionSession
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
template_id UUID FK inspection_templates.id NOT NULL
template_snapshot JSONB NOT NULL -- 세션 시작 시 템플릿+항목 전체 스냅샷
status VARCHAR(20) NOT NULL DEFAULT 'in_progress'
-- "in_progress"|"completed"|"cancelled"
inspector_id UUID FK users.id NOT NULL -- 검사자
started_at TIMESTAMPTZ NOT NULL
completed_at TIMESTAMPTZ NULL
results JSONB NULL -- 완료 시 최종 결과 (항목별 값 + 판정)
partial_results JSONB NULL -- 진행 중 자동저장 데이터
-- {item_id: {value: ..., updated_at: ...}}
lot_number VARCHAR(100) NULL -- ✨ 신규: 로트/시리얼 번호 (엔키드 IATF 추적성용, optional)
notes TEXT NULL -- 비고/메모
created_at TIMESTAMPTZ DEFAULT now()
updated_at TIMESTAMPTZ DEFAULT now()
INDEX(tenant_id, status)
INDEX(tenant_id, template_id)
INDEX(tenant_id, inspector_id, status)
-- 5. 부품 카운터 (PartCounter)
-- 설비별 부품별 1:1 mutable 레코드. 누적값을 추적하며 부품 교체 시 리셋.
-- version 필드로 optimistic locking (PLC 갱신과 수동 리셋 동시 발생 방지)
PartCounter
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
equipment_part_id UUID FK equipment_parts.id UNIQUE NOT NULL
current_value FLOAT DEFAULT 0 -- 현재 누적값 (타발수, 시간 등)
lifecycle_pct FLOAT DEFAULT 0 -- 수명 진행률 (%) = current_value / lifecycle_limit * 100
last_reset_at TIMESTAMPTZ NOT NULL -- 마지막 리셋(교체) 시각
last_updated_at TIMESTAMPTZ NOT NULL -- 마지막 갱신 시각
version INTEGER DEFAULT 0 -- optimistic locking용 버전
INDEX(tenant_id)
INDEX(equipment_part_id)
-- 6. 부품 교체 이력 (PartReplacementLog)
-- 부품 교체 시점의 스냅샷을 보존. "언제, 누가, 누적값 얼마에서 교체했는가"
PartReplacementLog
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
equipment_part_id UUID FK equipment_parts.id NOT NULL
replaced_by UUID FK users.id NULL -- 교체한 사람
replaced_at TIMESTAMPTZ NOT NULL
counter_at_replacement FLOAT NOT NULL -- 교체 시점 카운터 값 (예: 9,850타)
lifecycle_pct_at_replacement FLOAT NOT NULL -- 교체 시점 수명 % (예: 98.5%)
reason VARCHAR(200) NULL -- 교체 사유 ("정기 교체", "마모", "파손" 등)
notes TEXT NULL
INDEX(tenant_id, equipment_part_id)
INDEX(tenant_id, replaced_at)
-- 7. 검사 알람 (InspectionAlarm)
-- 3가지 알람 유형:
-- part_lifecycle: 부품 수명 임계값 도달 (예: 90% 도달)
-- inspection_overdue: 정기검사 미실시 (예: 일일검사를 오늘 안 함)
-- spec_violation: 검사 결과 스펙 이탈 (예: 온도 65℃, 상한 60℃)
InspectionAlarm
id UUID PK DEFAULT gen_random_uuid()
tenant_id VARCHAR(50) FK tenants.id NOT NULL
alarm_type VARCHAR(30) NOT NULL -- "part_lifecycle"|"inspection_overdue"|"spec_violation"|"trend_warning"
severity VARCHAR(10) NOT NULL -- "warning"|"critical"
machine_id UUID FK machines.id NULL
equipment_part_id UUID FK equipment_parts.id NULL
session_id UUID FK inspection_sessions.id NULL
title VARCHAR(200) NOT NULL -- "1호기 상부금형 수명 92% 도달"
message TEXT NULL -- 상세 설명
is_acknowledged BOOLEAN DEFAULT false
acknowledged_by UUID FK users.id NULL
acknowledged_at TIMESTAMPTZ NULL
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
INDEX(tenant_id, is_acknowledged)
INDEX(tenant_id, alarm_type)
INDEX(tenant_id, created_at)
```
### 테이블 관계 다이어그램
```
Tenant (1) ──────┬──── (N) User
├──── (N) Machine
│ │
│ ├──── (N) EquipmentPart
│ │ │
│ │ ├──── (1) PartCounter
│ │ │
│ │ ├──── (N) PartReplacementLog
│ │ │
│ │ └──── (N) InspectionTemplateItem.equipment_part_id
│ │
│ └──── (N) InspectionTemplate.machine_id
│ │
│ ├──── (N) InspectionTemplateItem
│ │
│ └──── (N) InspectionSession
└──── (N) InspectionAlarm
```
---
## 2. Phase 분해
---
### Phase 0: 프로젝트 부트스트래핑 (0.5일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| --- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| 0-1 | 기존 프로젝트 백업 | `mv factoryOps factoryops_backup` (기존 코드는 참고용으로 보존) | infra | bash |
| 0-2 | 새 프로젝트 생성 | `mkdir factoryOps && cd factoryOps && git init` | infra | bash |
| 0-3 | `requirements.txt` 작성 | FastAPI==0.115.0, SQLAlchemy[asyncio]==2.0.36, asyncpg==0.30.0, PyJWT==2.9.0, bcrypt==4.2.0, alembic==1.14.0, uvicorn[standard], python-multipart, pytest==8.2.2, pytest-asyncio, httpx | infra | write |
| 0-4 | Next.js 프로젝트 초기화 | `npx create-next-app@latest dashboard --typescript --app --no-tailwind``npm install swr clsx` | infra | bash |
| 0-5 | `next.config.ts` 설정 | `output: 'standalone'`, 기존 참고하여 작성 | frontend | write |
| 0-6 | `tsconfig.json` 설정 | `@/*` path alias, strict mode, 기존 참고하여 작성 | frontend | write |
| 0-7 | `.env.example` 작성 | DATABASE_URL (postgresql+asyncpg://...), TEST_DATABASE_URL, JWT_SECRET_KEY, SQL_ECHO, NEXT_PUBLIC_API_URL | infra | write |
| 0-8 | `CLAUDE.md` 작성 | 새 프로젝트 기술스택, 구조, 스킬 사용법 가이드 | docs | write |
| 0-9 | `planning/` 디렉토리 생성 | task_plan.md, findings.md, progress.md 초기화 | docs | write |
**의존성**: 없음 (최초 작업)
**병렬 가능**: 0-3(Python)과 0-4(Node.js)는 0-2 이후 병렬 가능
**예상 소요**: 0.5일
---
### Phase 1: 인증 + DB 기반 (1.5일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| **Backend** | | | | |
| 1-1 | `src/database/config.py` 작성 | PostgreSQL 전용 (asyncpg). AsyncSession, async get_db(), async init_db(). SQLAlchemy async engine 사용 | backend | write |
| 1-2 | `src/database/models.py` 작성 | User, Tenant, Machine 3개 테이블만 (목표 150줄). Base = declarative_base(). PostgreSQL UUID, TIMESTAMPTZ, JSONB 네이티브 타입 사용 | backend | write |
| 1-3 | `src/auth/` 작성 | 기존 코드 참고하여 새로 작성. jwt_handler.py, password.py, dependencies.py, models.py, service.py, router.py,`__init__.py`. 핵심: login, logout, /me, create_user, list_users | backend | write |
| 1-4 | (1-3에 통합) | — | — | — |
| 1-5 | `src/tenant/manager.py` 작성 | 기존 참고하여 간소화 작성. 핵심만 유지: get_tenant(), create_tenant(), list_tenants(), verify_tenant_access() | backend | write |
| 1-6 | `main.py` 작성 | 최소 FastAPI app. auth_router 등록, CORS middleware, init_db() on startup. 기존 2000줄 → 목표 60~80줄 | backend | write |
| 1-7 | Alembic 설정 | `alembic init alembic`, env.py에 models import + database URL 설정, 초기 마이그레이션 `alembic revision --autogenerate -m "initial"` | backend | bash |
| 1-8 | `tests/conftest.py` 작성 | PostgreSQL 테스트 DB (TEST_DATABASE_URL), async test SessionLocal, TestClient fixture (httpx.AsyncClient), create_test_user() fixture (superadmin + regular user), get_auth_headers() helper. 각 테스트 후 트랜잭션 롤백으로 격리 | backend | write |
| 1-9 | `tests/test_auth.py` 작성 | 로그인 성공/실패, 토큰 검증, /me 엔드포인트, 권한 체크 (superadmin vs user) | backend | write |
| **Frontend** | | | | |
| 1-10 | 프론트엔드 기반 파일 작성 | 기존 참고하여 새로 작성:`lib/api.ts`, `lib/auth-context.tsx`, `lib/tenant-context.tsx`, `lib/toast-context.tsx`, `app/layout.tsx`, `app/globals.css` (Material Design 3), `components/AuthGuard.tsx`, `components/Toast.tsx`, `components/Card.tsx` | frontend | write |
| 1-11 | `app/login/page.tsx` 작성 | email/password 입력, POST /api/auth/login, 성공 시 리다이렉트. 기존 패턴 참고 | frontend | write |
| 1-12 | `app/page.tsx` 작성 | 테넌트 선택 페이지 (superadmin) 또는 자동 리다이렉트 (일반 사용자) | frontend | write |
**의존성**: Phase 0 완료 후
**병렬 가능**: 백엔드(1-1~1-9)와 프론트엔드(1-10~1-12) 완전 병렬
**예상 소요**: 1.5일
**검증 기준**:
- `pytest tests/test_auth.py` 전체 통과
- 브라우저에서 로그인 → /me 응답 확인
- `/serve` 스킬로 서버 실행 가능
---
### Phase 2: 설비 + 부품 관리 (2일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| **Backend** | | | | |
| 2-1 | `models.py`에 EquipmentPart 모델 추가 | 위 스키마 정의 참조. Machine과 1:N 관계 설정. UNIQUE(tenant_id, machine_id, name) 제약 | backend | edit |
| 2-2 | `src/api/machines.py` — 설비 CRUD API | | backend | write |
| | `GET /api/{tenant_id}/machines` | 테넌트 전체 설비 목록. 응답에 parts_count 포함 | | |
| | `POST /api/{tenant_id}/machines` | 설비 등록 (name, equipment_code, model) | | |
| | `GET /api/{tenant_id}/machines/{id}` | 설비 상세 + 장착 부품 목록 포함 | | |
| | `PUT /api/{tenant_id}/machines/{id}` | 설비 수정 | | |
| | `DELETE /api/{tenant_id}/machines/{id}` | 설비 삭제 (부품 있으면 soft delete 또는 거부) | | |
| 2-3 | `src/api/equipment_parts.py` — 부품 CRUD API | | backend | write |
| | `GET /api/{tenant_id}/machines/{machine_id}/parts` | 설비의 전체 부품 목록 | | |
| | `POST /api/{tenant_id}/machines/{machine_id}/parts` | 부품 등록 (name, category, lifecycle_type, lifecycle_limit, alarm_threshold, installed_at). 동시에 PartCounter 레코드도 자동 생성 (current_value=0) | | |
| | `GET /api/{tenant_id}/parts/{id}` | 부품 상세 (카운터 포함) | | |
| | `PUT /api/{tenant_id}/parts/{id}` | 부품 정보 수정 | | |
| | `DELETE /api/{tenant_id}/parts/{id}` | 부품 삭제 (비활성화) | | |
| 2-4 | `main.py`에 machines, equipment_parts 라우터 등록 | router include | backend | edit |
| 2-5 | Alembic 마이그레이션 생성 | `alembic revision --autogenerate -m "add_equipment_part"` | backend | bash |
| 2-6 | `tests/test_machines.py` | 설비 CRUD 5개 엔드포인트 테스트, 테넌트 격리 확인 | backend | write |
| 2-7 | `tests/test_equipment_parts.py` | 부품 CRUD 테스트, 부품 등록 시 PartCounter 자동 생성 확인, 중복 이름 거부 확인 | backend | write |
| **Frontend** | | | | |
| 2-8 | `lib/types.ts` 작성 | Machine, EquipmentPart, PartCounter 타입 정의 | frontend | write |
| 2-9 | `lib/hooks.ts` 작성 | `useData<T>()` 기본 훅 (기존 패턴 유지: SWR, 30s refresh, 2s dedup). `useMachines(tenantId)`, `useMachine(tenantId, machineId)`, `useEquipmentParts(tenantId, machineId)` 도메인 훅 | frontend | write |
| 2-10 | `components/TopNav.tsx` 작성 | 새 프로젝트용 네비게이션 바. 메뉴: 대시보드, 설비, 검사 템플릿, 검사 실행, 알람. 사용자 정보 + 로그아웃. 알람 뱃지 자리(Phase 6에서 연결) | frontend | write |
| 2-11 | `app/[tenant]/layout.tsx` 작성 | TopNav + AuthGuard 래핑, TenantProvider 설정 | frontend | write |
| 2-12 | `app/[tenant]/page.tsx` — 대시보드 | 설비 목록 카드 뷰 (설비명, 코드, 부품 수, 상태 요약). 클릭 시 설비 상세로 이동 | frontend | write |
| 2-13 | `components/MachineList.tsx` 작성 | 설비 목록 컴포넌트 (검색, 필터). 기존보다 간소화: 건강점수/등급 제거, 부품 수 + 알람 여부만 표시 | frontend | write |
| 2-14 | `app/[tenant]/machines/[id]/page.tsx` — 설비 상세 | 설비 기본 정보 + 부품 탭 (등록/수정/삭제). 부품별: 이름, 카테고리, 수명유형, 한계값, 장착일 표시. 부품 추가 모달 | frontend | write |
**의존성**: Phase 1 완료 후
**병렬 가능**: 백엔드(2-1~2-7)와 프론트엔드(2-8~2-14) 병렬. 프론트엔드는 타입 목업으로 먼저 개발 가능
**예상 소요**: 2일
**검증 기준**:
- `pytest tests/test_machines.py tests/test_equipment_parts.py` 전체 통과
- 브라우저에서 설비 등록 → 부품 추가 → 목록 확인 플로우
---
### Phase 3: 검사 템플릿 (기준정보) (2~3일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| **Backend** | | | | |
| 3-1 | `models.py`에 InspectionTemplate + InspectionTemplateItem 추가 | 위 스키마 참조. Template ↔ Item 1:N cascade. Template.version 관리 로직 | backend | edit |
| 3-2 | `src/api/templates.py` — 템플릿 CRUD API | | backend | write |
| | `GET /api/{tenant_id}/templates` | 목록 (쿼리: subject_type, schedule_type, machine_id 필터). 각 템플릿의 items_count 포함 | | |
| | `POST /api/{tenant_id}/templates` | 생성 (name, subject_type, machine_id, schedule_type, items:[...]). items 배열을 함께 받아 일괄 생성 | | |
| | `GET /api/{tenant_id}/templates/{id}` | 상세 (items 목록 sort_order 순 포함) | | |
| | `PUT /api/{tenant_id}/templates/{id}` | 수정 (name, schedule_type 등). version 자동 증가. 이미 이 템플릿으로 진행된 세션이 있으면 경고 메시지 반환 | | |
| | `DELETE /api/{tenant_id}/templates/{id}` | 비활성화 (is_active=false). 물리 삭제 아님 | | |
| | `POST /api/{tenant_id}/templates/{id}/items` | 항목 추가 (name, category, data_type, unit, spec_min, spec_max, select_options, equipment_part_id, is_required). sort_order 자동 계산 (현재 max + 1) | | |
| | `PUT /api/{tenant_id}/templates/{id}/items/{item_id}` | 항목 수정 | | |
| | `DELETE /api/{tenant_id}/templates/{id}/items/{item_id}` | 항목 삭제 + 나머지 sort_order 재정렬 | | |
| | `PUT /api/{tenant_id}/templates/{id}/items/reorder` | 항목 순서 변경. body: {item_ids: [id1, id2, id3, ...]} 순서대로 sort_order 재할당 | | |
| 3-3 | `main.py`에 templates 라우터 등록 | | backend | edit |
| 3-4 | Alembic 마이그레이션 | `alembic revision --autogenerate -m "add_inspection_template"` | backend | bash |
| 3-5 | `tests/test_templates.py` | | backend | write |
| | 템플릿 생성 (items 포함) + 조회 확인 | | | |
| | 템플릿 수정 시 version 증가 확인 | | | |
| | 항목 추가/수정/삭제/순서변경 테스트 | | | |
| | subject_type 필터 테스트 | | | |
| | equipment_part_id 연결 테스트 | | | |
| | 비활성화(soft delete) 테스트 | | | |
| **Frontend** | | | | |
| 3-6 | `lib/types.ts`에 추가 | InspectionTemplate, InspectionTemplateItem 타입 정의 | frontend | edit |
| 3-7 | `lib/hooks.ts`에 추가 | `useTemplates(tenantId, filters?)`, `useTemplate(tenantId, templateId)` | frontend | edit |
| 3-8 | `app/[tenant]/templates/page.tsx` — 템플릿 목록 | 탭: 설비검사 / 품질검사 (subject_type 필터). 카드 또는 테이블 뷰: 템플릿명, 대상 설비, 주기, 항목수, 버전. "새 템플릿" 버튼 | frontend | write |
| 3-9 | `components/TemplateEditor.tsx` — 핵심 편집기 | | frontend | write |
| | 템플릿 기본정보 입력: 이름, 대상유형(설비/품질), 설비 선택(드롭다운), 검사주기 | | | |
| | 항목 목록: 드래그로 순서 변경 (또는 ↑↓ 버튼) | | | |
| | 항목 추가 폼: 항목명, 카테고리, 데이터유형 선택 → 유형별 조건부 UI | | | |
| | - numeric: 단위, 하한, 상한 입력 | | | |
| | - boolean: 추가 설정 없음 | | | |
| | - text: 추가 설정 없음 | | | |
| | - select: 선택지 목록 편집 (추가/삭제) | | | |
| | 부품 연결: equipment_part_id 선택 (해당 설비의 부품 드롭다운) | | | |
| | 필수 여부 토글 | | | |
| | 항목 삭제 (확인 다이얼로그) | | | |
| 3-10 | `app/[tenant]/templates/new/page.tsx` | TemplateEditor를 사용한 생성 페이지. 저장 시 POST /templates → 목록으로 리다이렉트 | frontend | write |
| 3-11 | `app/[tenant]/templates/[id]/page.tsx` | TemplateEditor를 사용한 편집 페이지. 기존 데이터 로드 → 수정 → PUT /templates/{id} | frontend | write |
**의존성**: Phase 2 완료 후 (Machine, EquipmentPart 필요)
**병렬 가능**: 백엔드와 프론트엔드 병렬. **Phase 5와도 병렬 가능** (서로 의존 없음)
**예상 소요**: 2~3일
**검증 기준**:
- `pytest tests/test_templates.py` 전체 통과
- 브라우저에서 템플릿 생성 → 항목 추가(각 데이터유형) → 순서변경 → 저장 → 재편집 플로우
---
### Phase 4: 검사 실행 (세션) (3일) — **핵심 UX, 최고 우선순위**
> **선배 개발자 경고**: "진행 중인 검사가 초기화되면 시스템 사용률 90% 실패"
> 이 Phase가 전체 프로젝트의 성패를 좌우함
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------- |
| **Backend** | | | | |
| 4-1 | `models.py`에 InspectionSession 추가 | 위 스키마 참조. template_snapshot JSON, partial_results JSON, status enum | backend | edit |
| 4-2 | `src/api/sessions.py` — 세션 API | | backend | write |
| | `POST /api/{tenant_id}/sessions` | 세션 시작. body: {template_id}. 로직: (1) 템플릿+항목 조회, (2) JSON 스냅샷 생성, (3) InspectionSession 생성(status='in_progress', template_snapshot=스냅샷, started_at=now). 응답: 세션 ID + 스냅샷 | | |
| | `GET /api/{tenant_id}/sessions` | 목록. 쿼리: status(in_progress/completed/cancelled), template_id, inspector_id. 페이지네이션. 정렬: 최신 순 | | |
| | `GET /api/{tenant_id}/sessions/{id}` | 세션 상세. template_snapshot + partial_results(또는 results) 포함 | | |
| | `PATCH /api/{tenant_id}/sessions/{id}/autosave` | **자동저장**. body: {partial_results: {item_id: {value: any, updated_at: str}}}. status가 'in_progress'일 때만 허용. 부분 업데이트(기존 partial_results에 merge) | | |
| | `POST /api/{tenant_id}/sessions/{id}/complete` | 세션 완료. 로직: (1) partial_results → results로 확정, (2) 필수 항목 미입력 체크, (3) 스펙 이탈 항목 체크 → 알람 생성(Phase 6), (4) status='completed', completed_at=now | | |
| | `POST /api/{tenant_id}/sessions/{id}/cancel` | 세션 취소. status='cancelled' | | |
| | `GET /api/{tenant_id}/sessions/in-progress` | 현재 사용자의 진행중 세션 목록. inspector_id=current_user, status='in_progress'. TopNav에서 진행중 세션 뱃지에 사용 | | |
| 4-3 | 템플릿 스냅샷 로직 | `create_template_snapshot(template_id, db)` 함수. 템플릿 기본정보 + 전체 항목(sort_order 순) JSON 직렬화. 스냅샷에 포함: template.name, template.version, template.schedule_type, items[{id, name, category, data_type, unit, spec_min, spec_max, select_options, equipment_part_id, is_required, sort_order}] | backend | write |
| 4-4 | `main.py`에 sessions 라우터 등록 | | backend | edit |
| 4-5 | Alembic 마이그레이션 | `alembic revision --autogenerate -m "add_inspection_session"` | backend | bash |
| 4-6 | `tests/test_sessions.py` | | backend | write |
| | 세션 시작 → template_snapshot 정확히 저장되는지 확인 | | | |
| | 자동저장(autosave) → partial_results 누적 확인 | | | |
| | 자동저장 후 다시 GET → partial_results 복원 확인 | | | |
| | 세션 완료 → results 확정, status 변경 확인 | | | |
| | 완료 시 필수 항목 미입력 → 에러 반환 확인 | | | |
| | 세션 취소 테스트 | | | |
| | in-progress 목록 → 현재 사용자 것만 반환 확인 | | | |
| | 이미 완료된 세션에 autosave 시도 → 에러 확인 | | | |
| **Frontend** | | | | |
| 4-7 | `lib/types.ts`에 추가 | InspectionSession, SessionStatus, PartialResult 타입 | frontend | edit |
| 4-8 | `lib/hooks.ts`에 추가 | `useSessions(tenantId, filters?)`, `useSession(tenantId, sessionId)`, `useInProgressSessions(tenantId)` | frontend | edit |
| 4-9 | `components/InspectionForm.tsx`**프로젝트 핵심 컴포넌트** | | frontend | write |
| | **렌더링**: template_snapshot 기반으로 폼 자동 생성. 항목별 입력 UI: | | | |
| | -`numeric`: 숫자 입력 + 단위 표시. spec_min/max 있으면 범위 표시. 범위 초과 시 빨간 테두리 + 경고 아이콘. warning_min/max 있으면 주황색 경고 | | | |
| | -`boolean`: 토글 스위치 (양호/불량 또는 OK/NG) | | | |
| | -`text`: 텍스트 입력 | | | |
| | -`select`: 드롭다운 (select_options 기반) | | | |
| | **자동저장**: 값 변경마다 2초 debounce → PATCH /sessions/{id}/autosave. SWR 낙관적 업데이트(mutate 호출). 저장 상태 표시("저장 중...", "저장됨 HH:MM:SS") | | | |
| | **이탈 방지**: `beforeunload` 이벤트 리스너. Next.js router 이벤트 후킹. 이탈 시도 시 "저장되지 않은 변경사항이 있습니다" 경고 | | | |
| | **진행률**: 입력 완료 항목 수 / 전체 항목 수 표시 (예: "8/12 완료") | | | |
| | **완료 버튼**: 필수 항목 미입력 시 비활성화 + 미입력 항목 하이라이트. 클릭 시 확인 다이얼로그 → POST /sessions/{id}/complete | | | |
| | **카테고리 그룹핑**: 항목을 category별로 그룹핑하여 섹션으로 표시 | | | |
| | **부품 연결 표시**: equipment_part_id가 있는 항목은 부품명 뱃지 표시 | | | |
| | **모바일 반응형**: 태블릿/모바일에서 터치 친화적 UI. 큰 입력 필드, 큰 버튼 | | | |
| 4-9a | ✨**3가지 검사 레이아웃** (inspection_mode별) | | frontend | write |
| | `ChecklistLayout.tsx` — inspection_mode='checklist': 큰 OK/NG 버튼, 스와이프 진행, 모바일 최적화 (스피폭스 순회검사용) | | | |
| | `MeasurementLayout.tsx` — inspection_mode='measurement': 상세 폼, 이미지/X-ray 업로드, 로트번호 입력 (엔키드 품질검사용) | | | |
| | `MonitoringLayout.tsx` — inspection_mode='monitoring': 수치 테이블, 인라인 트렌드 스파크라인 (알펫 라인 모니터링용) | | | |
| | →`next/dynamic`으로 동적 로드. 같은 InspectionForm 컴포넌트를 감싸는 레이아웃 | | | |
| 4-10 | `app/[tenant]/inspections/page.tsx` — 검사 목록 | | frontend | write |
| | 탭: "진행 중" / "완료됨" / "취소됨" (status 필터) | | | |
| | 진행 중 탭: 세션 카드(템플릿명, 검사자, 시작시간, 진행률). 클릭 시 검사 실행 화면으로 이동 → partial_results 자동 복원 | | | |
| | 완료 탭: 세션 카드(템플릿명, 검사자, 완료시간, 결과 요약). 클릭 시 결과 상세 보기 | | | |
| | "새 검사 시작" 버튼 → 템플릿 선택 모달 → POST /sessions → 검사 실행 화면으로 이동 | | | |
| 4-11 | `app/[tenant]/inspections/[id]/page.tsx` — 검사 실행 화면 | | frontend | write |
| | useSession()으로 데이터 로드 | | | |
| | status='in_progress'면 InspectionForm 렌더링 (편집 모드) | | | |
| | status='completed'면 결과 읽기 전용 표시 (스펙 이탈 항목 빨간 표시) | | | |
| | status='cancelled'면 취소됨 안내 | | | |
| 4-12 | 진행중 세션 재진입 플로우 테스트 | 검사 목록에서 진행중 세션 클릭 → partial_results 자동 복원 → 이전에 입력한 값 그대로 표시 → 추가 입력 → 자동저장 → 완료 | frontend | /ui-test |
**의존성**: Phase 3 완료 후 (InspectionTemplate + Items 필요)
**병렬 가능**: 백엔드와 프론트엔드 병렬
**예상 소요**: 3일 (InspectionForm이 가장 복잡한 컴포넌트)
**UX 핵심 요구사항 체크리스트**:
- [ ] 화면 이탈 시 자동저장됨 (절대 초기화 안됨)
- [ ] 진행중 세션 재진입 시 이전 입력값 복원됨
- [ ] 스펙 초과 시 즉시 시각적 경고
- [ ] 필수 항목 미입력 시 완료 불가
- [ ] 모바일/태블릿 터치 친화적
- [ ] 다중 세션 동시 진행 가능 (작업자가 여러 설비 순회)
---
### Phase 5: 부품 카운터 + 교체 이력 (2~3일)
> **Phase 3과 병렬 개발 가능** — 서로 의존성 없음
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| **Backend** | | | | |
| 5-1 | `models.py`에 PartCounter + PartReplacementLog 추가 | 위 스키마 참조. PartCounter.version으로 optimistic locking. PartCounter ↔ EquipmentPart 1:1 관계 | backend | edit |
| 5-2 | `src/services/counter_service.py` — 카운터 비즈니스 로직 | | backend | write |
| | `increment(db, equipment_part_id, delta, expected_version=None)` | | | |
| | → 카운터 조회, version 체크(expected_version이 주어지면), current_value += delta, lifecycle_pct 재계산, version += 1, last_updated_at = now | | | |
| | → lifecycle_pct >= alarm_threshold 이면 check_alarm_threshold() 호출 | | | |
| | → version 불일치 시 409 Conflict 반환 (optimistic locking) | | | |
| | `reset(db, equipment_part_id, user_id, reason=None, notes=None)` | | | |
| | → 현재 카운터 스냅샷 생성 (counter_at_replacement, lifecycle_pct_at_replacement) | | | |
| | → PartReplacementLog 레코드 생성 | | | |
| | → 카운터 리셋: current_value=0, lifecycle_pct=0, last_reset_at=now, version += 1 | | | |
| | → EquipmentPart.installed_at = now (새 부품 장착) | | | |
| | `recalculate_lifecycle_pct(counter, equipment_part)` | | | |
| | → lifecycle_pct = (current_value / equipment_part.lifecycle_limit) * 100 | | | |
| | → lifecycle_type='date'인 경우: (경과일수 / limit일수) * 100 | | | |
| | `check_alarm_threshold(db, counter, equipment_part)` | | | |
| | → lifecycle_pct >= alarm_threshold 이면 InspectionAlarm 생성 (Phase 6에서 연결) | | | |
| | → 이미 동일 부품에 대해 미확인 알람이 있으면 중복 생성하지 않음 | | | |
| 5-3 | `src/api/counters.py` — 카운터 API | | backend | write |
| | `GET /api/{tenant_id}/machines/{machine_id}/counters` | 설비의 전체 부품 카운터 현황. 응답: [{part_name, category, lifecycle_type, lifecycle_limit, current_value, lifecycle_pct, last_reset_at, alarm_threshold}] | | |
| | `GET /api/{tenant_id}/counters/critical` | 알람 임계값 이상인 카운터 목록 (대시보드 "교체 임박" 섹션용) | | |
| | `POST /api/{tenant_id}/parts/{part_id}/counter/increment` | 카운터 수동 증가. body: {delta, expected_version?}. 응답: 갱신된 카운터 | | |
| | `POST /api/{tenant_id}/parts/{part_id}/replace` | 부품 교체. body: {reason?, notes?}. 로직: counter_service.reset() 호출. 응답: 교체 이력 레코드 + 리셋된 카운터 | | |
| | `GET /api/{tenant_id}/parts/{part_id}/replacement-history` | 교체 이력 목록 (최신 순). 응답: [{replaced_at, replaced_by_name, counter_at_replacement, lifecycle_pct_at_replacement, reason}] | | |
| 5-4 | `main.py`에 counters 라우터 등록 | | backend | edit |
| 5-5 | Alembic 마이그레이션 | `alembic revision --autogenerate -m "add_part_counter_and_replacement_log"` | backend | bash |
| 5-6 | `tests/test_counters.py` | | backend | write |
| | 카운터 증가 → current_value, lifecycle_pct 정확히 계산 | | | |
| | 카운터 증가 → version 증가 확인 | | | |
| | optimistic locking: 잘못된 expected_version → 409 Conflict | | | |
| | 부품 교체(reset) → 카운터 0으로 리셋, 교체 이력 생성 확인 | | | |
| | 교체 이력 → counter_at_replacement 스냅샷 정확한지 확인 | | | |
| | 알람 임계값 도달 시 알람 생성 확인 (Phase 6 연결 후) | | | |
| | critical 카운터 목록 API 테스트 | | | |
| **Frontend** | | | | |
| 5-7 | `lib/types.ts`에 추가 | PartCounter (확장), PartReplacementLog, CounterSummary 타입 | frontend | edit |
| 5-8 | `lib/hooks.ts`에 추가 | `useCounters(tenantId, machineId)`, `useCriticalCounters(tenantId)`, `useReplacementHistory(tenantId, partId)` | frontend | edit |
| 5-9 | `components/PartLifecycleGauge.tsx` — 수명 진행률 게이지 | | frontend | write |
| | 원형 게이지 또는 수평 진행바 | | | |
| | 색상: 0~70% 녹색 (var(--md-success)), 70~90% 주황 (var(--md-warning)), 90~100% 빨강 (var(--md-error)) | | | |
| | 중앙 또는 우측에 현재값/한계값 표시 (예: "8,500 / 10,000") | | | |
| | 수명유형 아이콘 (hours→시계, count→카운터, date→달력) | | | |
| 5-10 | `components/CounterCard.tsx` — 카운터 현황 카드 | | frontend | write |
| | 부품명, 카테고리 뱃지 | | | |
| | PartLifecycleGauge 포함 | | | |
| | "교체" 버튼 (확인 다이얼로그 → POST /parts/{id}/replace) | | | |
| | 마지막 교체일 표시 | | | |
| 5-11 | `app/[tenant]/machines/[id]/page.tsx` 수정 — 카운터 탭 추가 | | frontend | edit |
| | 기존 부품 탭 옆에 "카운터" 탭 추가 | | | |
| | 카운터 탭: CounterCard 목록 (게이지 + 교체 버튼) | | | |
| | 교체 이력 탭: 교체 이력 타임라인 (날짜, 교체자, 교체 시점 카운터값, 사유) | | | |
| 5-12 | `app/[tenant]/page.tsx` 수정 — 대시보드에 "교체 임박 부품" 섹션 | | frontend | edit |
| | useCriticalCounters()로 알람 임계값 이상 카운터 목록 | | | |
| | 카드 형태로 표시: 설비명, 부품명, 수명%, 한계값 | | | |
| | 클릭 시 해당 설비 상세 카운터 탭으로 이동 | | | |
**의존성**: Phase 2 완료 후 (EquipmentPart 필요). **Phase 3과 병렬 가능!**
**병렬 가능**: 백엔드와 프론트엔드 병렬
**예상 소요**: 2~3일
**레이스 컨디션 대응 전략**:
> **Note**: PostgreSQL MVCC + 이 패턴으로 동시성 문제를 안전하게 처리함.
```sql
-- PostgreSQL 네이티브 쿼리
UPDATE part_counters
SET current_value = current_value + :delta,
lifecycle_pct = (current_value + :delta) / :lifecycle_limit * 100,
version = version + 1,
last_updated_at = now()
WHERE id = :counter_id AND version = :expected_version
RETURNING id, current_value, lifecycle_pct, version;
-- RETURNING 절로 업데이트 결과 즉시 반환 (PostgreSQL 전용 기능)
-- 결과가 없으면 → 409 Conflict 반환
```
---
### Phase 6: 알람 시스템 (2일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----- |
| **Backend** | | | | |
| 6-1 | `models.py`에 InspectionAlarm 추가 | 위 스키마 참조. 3가지 alarm_type, 2가지 severity | backend | edit |
| 6-2 | `src/services/alarm_service.py` — 알람 서비스 | | backend | write |
| | `create_alarm(db, tenant_id, alarm_type, severity, title, message, machine_id=None, equipment_part_id=None, session_id=None)` | 알람 생성. 동일 부품/세션에 미확인 중복 알람이 있으면 생성 안 함 | | |
| | `check_part_lifecycle_alarms(db, tenant_id)` | 전체 부품 카운터 순회 → lifecycle_pct >= alarm_threshold인 것 찾아 알람 생성. 배치용 (스케줄러 또는 API 호출) | | |
| | `check_overdue_inspections(db, tenant_id)` | schedule_type별 마지막 세션 완료 시간 체크 → 기한 초과 시 알람 생성. daily: 오늘 완료 세션 없으면, weekly: 이번 주 완료 세션 없으면 등 | | |
| | `create_spec_violation_alarm(db, session, item_name, value, spec_min, spec_max)` | 검사 완료 시 스펙 이탈 항목에 대해 호출. severity: spec 초과 정도에 따라 warning/critical | | |
| | `get_alarm_summary(db, tenant_id)` | 미확인 알람 타입별 개수. 응답: {part_lifecycle: 3, inspection_overdue: 1, spec_violation: 2, total: 6} | | |
| 6-3 | `src/api/alarms.py` — 알람 API | | backend | write |
| | `GET /api/{tenant_id}/alarms` | 알람 목록. 쿼리: alarm_type, severity, is_acknowledged, machine_id. 페이지네이션. 최신 순 | | |
| | `GET /api/{tenant_id}/alarms/summary` | 미확인 알람 개수 요약 (TopNav 뱃지용). 응답: {total: N, by_type: {...}} | | |
| | `POST /api/{tenant_id}/alarms/{id}/acknowledge` | 알람 확인 처리. acknowledged_by=current_user, acknowledged_at=now | | |
| | `POST /api/{tenant_id}/alarms/acknowledge-bulk` | 다중 알람 일괄 확인. body: {alarm_ids: [...]} | | |
| 6-4 | Phase 4 연결: 세션 완료 로직에 스펙 이탈 알람 추가 | `sessions.py`의 complete 엔드포인트에서 각 결과값을 template_snapshot의 spec_min/max와 비교. 이탈 항목마다 `alarm_service.create_spec_violation_alarm()` 호출 | backend | edit |
| 6-5 | Phase 5 연결: 카운터 서비스에 수명 알람 추가 | `counter_service.py``check_alarm_threshold()`에서 `alarm_service.create_alarm()` 호출. alarm_type='part_lifecycle', severity는 lifecycle_pct >= 100이면 'critical', 그 외 'warning' | backend | edit |
| 6-6 | `main.py`에 alarms 라우터 등록 | | backend | edit |
| 6-7 | Alembic 마이그레이션 | `alembic revision --autogenerate -m "add_inspection_alarm"` | backend | bash |
| 6-8 | `tests/test_alarms.py` | | backend | write |
| | 알람 수동 생성 + 조회 테스트 | | | |
| | 중복 알람 방지 테스트 (같은 부품에 미확인 알람 있으면 중복 생성 안 됨) | | | |
| | 알람 확인(acknowledge) 테스트 | | | |
| | 일괄 확인 테스트 | | | |
| | 알람 요약(summary) 테스트 | | | |
| | 필터(alarm_type, severity, is_acknowledged) 테스트 | | | |
| | 카운터 증가 → 임계값 도달 → 알람 자동 생성 E2E | | | |
| | 세션 완료 → 스펙 이탈 → 알람 자동 생성 E2E | | | |
| **Frontend** | | | | |
| 6-9 | `lib/types.ts`에 추가 | InspectionAlarm, AlarmType, AlarmSeverity, AlarmSummary 타입 | frontend | edit |
| 6-10 | `lib/hooks.ts`에 추가 | `useAlarms(tenantId, filters?)`, `useAlarmSummary(tenantId)` | frontend | edit |
| 6-11 | `components/AlarmBadge.tsx` — TopNav 알람 뱃지 | | frontend | write |
| | useAlarmSummary()로 미확인 알람 총 개수 조회 | | | |
| | 0이면 표시 안 함, 1 이상이면 빨간 원형 뱃지로 개수 표시 | | | |
| | 클릭 시 /alarms 페이지로 이동 | | | |
| | 30초 자동 갱신 (SWR refreshInterval) | | | |
| 6-12 | `app/[tenant]/alarms/page.tsx` — 알람 목록 | | frontend | write |
| | 필터 바: 알람 유형(부품수명/검사미실시/스펙이탈), 심각도(경고/긴급), 확인여부(미확인/확인) | | | |
| | 알람 카드 목록: 아이콘(유형별), 제목, 메시지, 생성시간, 설비명, 부품명 | | | |
| | 미확인 알람: "확인" 버튼 (POST /alarms/{id}/acknowledge) | | | |
| | 일괄 확인: 체크박스 선택 → "선택 확인" 버튼 | | | |
| | severity에 따른 색상: warning=주황, critical=빨강 | | | |
| 6-13 | TopNav에 AlarmBadge 통합 | TopNav.tsx에 AlarmBadge 컴포넌트 추가 | frontend | edit |
**의존성**: Phase 4(세션 완료 시 스펙 이탈) + Phase 5(카운터 임계값) 완료 후 6-4, 6-5 연결. 단, 6-1~6-3(알람 기본 CRUD)은 Phase 4/5와 병렬 가능
**병렬 가능**: 알람 모델/API 기본 구조는 Phase 3 이후 바로 시작 가능. 연결(6-4, 6-5)만 Phase 4/5 이후
**예상 소요**: 2일
---
### Phase 7: 통합 테스트 + 마무리 (1일)
| # | 작업 | 상세 | 카테고리 | 스킬 |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -------- | -------- |
| 7-1 | 전체 pytest 실행 + 실패 수정 | `pytest tests/ -v` 전체 통과 확인 | test | /test |
| 7-2 | E2E 플로우 테스트 (브라우저) | | test | /ui-test |
| | 플로우 1: 로그인 → 설비 등록 → 부품 등록 → 카운터 확인 | | | |
| | 플로우 2: 템플릿 생성 (항목 추가, 부품 연결) → 저장 → 재편집 | | | |
| | 플로우 3: 검사 시작 → 항목 입력 → 다른 페이지 이동 → 돌아옴 → 이전 입력 복원 → 완료 | | | |
| | 플로우 4: 카운터 증가 → 임계값 도달 → 알람 생성 → 알람 확인 | | | |
| | 플로우 5: 부품 교체 → 카운터 리셋 → 교체 이력 확인 | | | |
| 7-3 | API 문서 검증 | FastAPI 자동생성 /docs (Swagger) 확인. 모든 엔드포인트 정상 표시, 요청/응답 스키마 정확한지 | test | /ui-test |
| 7-4 | 모바일 반응형 확인 | 태블릿 뷰포트(768px)에서 검사 실행 화면 사용성 확인. 터치 타겟 충분한 크기, 스크롤 적절, 입력 편의성 | test | /ui-test |
| 7-5 | 시드 데이터 스크립트 | `scripts/seed.py` — 3개 테넌트 시드 데이터: | backend | write |
| | **스피폭스**: 설비 5대 (프레스 3, 건조 1, 세척 1), 부품 각 3~5개 (금형, 팁, 센서), 템플릿 3개 (일일 checklist, 주간 measurement, 월간), 샘플 카운터 값 | | | |
| | **엔키드**: 설비 3대 (Toyo 650T, Toshiba 350T, Toyo 200T), 부품 각 2~3개 (금형, 플런저팁, 슬리브), 템플릿 2개 (일일 checklist, 품질검사 measurement + X-ray select 항목) | | | |
| | **알펫**: 설비 4대 (전처리, 라미네이팅, 코팅, 리코일러), 부품 각 2~3개 (히터 소자, 화학약품, 롤), 템플릿 2개 (일일 monitoring, 주간 measurement) | | | |
| 7-6 | 정리 | 사용하지 않는 import 제거, 코드 포매팅, 주석 정리 | cleanup | edit |
**의존성**: Phase 6 완료 후
**예상 소요**: 1일
---
## 3. Task 의존성 그래프
```
Phase 0 (부트스트래핑, 0.5일)
Phase 1 (인증 + DB, 1.5일)
Phase 2 (설비 + 부품, 2일)
├──────────────────────────────┐
▼ ▼
Phase 3 (검사 템플릿, 2~3일) Phase 5 (카운터 + 교체, 2~3일)
│ │
▼ │ ← 이 두 Phase는 서로 의존 없음!
Phase 4 (검사 실행, 3일) │ 병렬 개발로 ~2일 단축 가능
│ │
├──────────────────────────────┘
Phase 6 (알람, 2일)
│ ← Phase 4의 세션완료 + Phase 5의 카운터 서비스에 알람 연결
Phase 7 (통합 테스트, 1일)
```
### 병렬 실행 최적화 요약
| 병렬 기회 | 설명 | 절약 시간 |
| ----------------------------- | ---------------------------------------------------------------- | ------------------- |
| **Phase 3 + Phase 5** | 검사 템플릿과 카운터 시스템은 서로 의존 없음 | ~2일 |
| **각 Phase 내 BE + FE** | 백엔드와 프론트엔드 항상 병렬 가능 (타입 목업 활용) | 각 Phase에서 ~0.5일 |
| **Phase 6 기본 CRUD** | 알람 모델/API 기본 구조는 Phase 2 이후 시작 가능 (연결만 나중에) | ~0.5일 |
---
## 4. 예상 일정 요약
| Phase | 내용 | 순차 시 | 병렬 시 | 누적 (병렬) |
| -------------- | ---------------------------------------------- | -------------- | ---------------------- | ------------------ |
| 0 | 프로젝트 부트스트래핑 | 0.5일 | 0.5일 | 0.5일 |
| 1 | 인증 + DB 기반 | 1.5일 | 1.5일 | 2일 |
| 2 | 설비 + 부품 관리 (+counter_source) | 2.5일 | 2.5일 | 4.5일 |
| 3 | 검사 템플릿 (+inspection_mode, warning, trend) | 3.5일 | 3.5일 (Phase 5와 병렬) | 8일 |
| 5 | 카운터 + 교체 이력 | 3일 | — (Phase 3과 동시) | — |
| 4 | 검사 실행 (+3개 레이아웃, lot_number) | 4일 | 4일 | 12일 |
| 6 | 알람 시스템 (+trend_warning) | 2.5일 | 2.5일 | 14.5일 |
| 7 | 통합 테스트 + 멀티테넌트 시드 | 1.5일 | 1.5일 | 16일 |
| **합계** | | **19일** | **16일** | **약 3.2주** |
> 기존 13일 → 16일 (+3일): 3개 고객사 멀티테넌트 지원을 위한 추가 작업
> 상세 변경 내역: 섹션 9 "3개 고객사 멀티테넌트 전략" 참조
---
## 5. 각 작업의 Agent 위임 추천
| 카테고리 | 추천 방식 | 사용 스킬/도구 | 비고 |
| ----------------------- | ----------------------------------- | ---------------------------- | ------------------------------------ |
| 프로젝트 셋업 (Phase 0) | 직접 실행 | bash + write | 디렉토리 생성, 초기화, npm init |
| DB 모델 작성 | 직접 실행 | write | SQLAlchemy 모델 정의 |
| API 라우터 작성 | 직접 실행 | write | FastAPI 엔드포인트, Pydantic 스키마 |
| 서비스 로직 | 직접 실행 | write | counter_service.py, alarm_service.py |
| 테스트 작성 + 실행 | 직접 작성 →`/test` 실행 | write + /test | pytest |
| Alembic 마이그레이션 | 직접 실행 | bash | alembic revision --autogenerate |
| 프론트엔드 페이지 | `/frontend-design` 스킬 | /frontend-design | 레이아웃 + Material Design 3 스타일 |
| InspectionForm (핵심) | `/frontend-design` + `/ui-test` | /frontend-design → /ui-test | 자동저장, debounce, 반응형, 이탈방지 |
| TemplateEditor | `/frontend-design` | /frontend-design | 드래그 순서변경, 조건부 UI |
| PartLifecycleGauge | `/frontend-design` | /frontend-design | 원형 게이지, 색상 전환 |
| 서버 실행 | `/serve` 스킬 | /serve | Podman 기반 |
| UI 검증 | `/ui-test` 스킬 | /ui-test | Playwright 브라우저 테스트 |
| 커밋 | `/commit` 스킬 | /commit | 각 Phase 완료 시 |
| 코드 리뷰 | `/review` 스킬 | /review | Phase 4, 6 완료 후 |
---
## 6. 기존 코드 참고 가이드
> ⚠️ 기존 factoryOps 프로젝트는 **참고만** 합니다. 코드를 복사하지 않고, 패턴과 로직만 발췌하여 새로 작성합니다.
### 참고할 패턴 (기존 코드에서 발췌할 것)
| 영역 | 참고 파일 (기존) | 발췌할 패턴 |
| ------------- | ------------------------------------ | ----------------------------------------------------------- |
| JWT 인증 | `src/auth/jwt_handler.py` | HS256 알고리즘, 24h 만료, 페이로드 구조 |
| 비밀번호 해싱 | `src/auth/password.py` | bcrypt 해싱 로직 |
| API 구조 | `src/auth/router.py` | FastAPI 라우터 패턴 (login, /me 등) |
| 테넌트 격리 | `src/tenant/manager.py` | tenant_id 기반 데이터 격리 패턴 |
| SWR 패턴 | `dashboard/lib/api.ts` | `fetcher` + `useData<T>()` 패턴 (30s refresh, 2s dedup) |
| URL 헬퍼 | `dashboard/lib/api.ts` | `getTenantUrl()` (`/api/{tenant_id}/...` 패턴) |
| CSS 변수 | `dashboard/app/globals.css` | Material Design 3 CSS 변수 시스템 |
| React Context | `dashboard/lib/auth-context.tsx` | AuthContext, TenantContext, ToastContext 패턴 |
| 어댑터 | `src/adapters/speedfox_adapter.py` | BaseAdapter 인터페이스, 데이터 변환 패턴 |
### 새로 작성할 때 주의사항
1. **PostgreSQL 전용**: `aiosqlite`, `sqlite3` 관련 코드 일체 불필요
2. **Async 우선**: SQLAlchemy async session, asyncpg 드라이버 사용
3. **간소화**: 기존의 Score/Action/Stats/Ontology 등 불필요한 모델 포함하지 않음
4. **main.py 목표 60~80줄**: 최소 구성 (app 생성, CORS, routers, init_db)
---
## 7. 기술 의존성 (requirements.txt / package.json)
### Backend (Python)
```
# Core
fastapi==0.115.0
uvicorn[standard]==0.30.0
python-multipart==0.0.9
# Database (PostgreSQL 전용, async)
sqlalchemy[asyncio]==2.0.36
asyncpg==0.30.0 # PostgreSQL async 드라이버
alembic==1.14.0
# Auth
PyJWT==2.9.0
bcrypt==4.2.0
# Testing
pytest==8.2.2
pytest-asyncio==0.24.0 # async 테스트 지원
httpx==0.27.0 # async TestClient
```
> ⚠️ psycopg2-binary 대신 **asyncpg** 사용 — FastAPI의 async 핸들러와 자연스럽게 결합.
> SQLite 관련 패키지(aiosqlite 등) 불필요.
### Frontend (Node.js)
```json
{
"dependencies": {
"next": "16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"swr": "2.3.8",
"clsx": "2.1.1"
},
"devDependencies": {
"typescript": "5.x",
"@types/react": "19.x",
"@types/node": "22.x"
}
}
```
---
## 8. API 엔드포인트 전체 목록
### 인증 (Phase 1)
```
POST /api/auth/login # 로그인
POST /api/auth/logout # 로그아웃
GET /api/auth/me # 현재 사용자 정보
POST /api/admin/users # 사용자 생성 (superadmin)
GET /api/admin/users # 사용자 목록 (superadmin)
```
### 설비 (Phase 2)
```
GET /api/{tenant_id}/machines # 설비 목록
POST /api/{tenant_id}/machines # 설비 등록
GET /api/{tenant_id}/machines/{id} # 설비 상세
PUT /api/{tenant_id}/machines/{id} # 설비 수정
DELETE /api/{tenant_id}/machines/{id} # 설비 삭제
```
### 설비 부품 (Phase 2)
```
GET /api/{tenant_id}/machines/{machine_id}/parts # 부품 목록
POST /api/{tenant_id}/machines/{machine_id}/parts # 부품 등록 (+카운터 자동생성)
GET /api/{tenant_id}/parts/{id} # 부품 상세
PUT /api/{tenant_id}/parts/{id} # 부품 수정
DELETE /api/{tenant_id}/parts/{id} # 부품 비활성화
```
### 검사 템플릿 (Phase 3)
```
GET /api/{tenant_id}/templates # 템플릿 목록 (subject_type 필터)
POST /api/{tenant_id}/templates # 템플릿 생성 (항목 포함)
GET /api/{tenant_id}/templates/{id} # 템플릿 상세 (항목 포함)
PUT /api/{tenant_id}/templates/{id} # 템플릿 수정 (버전 증가)
DELETE /api/{tenant_id}/templates/{id} # 템플릿 비활성화
POST /api/{tenant_id}/templates/{id}/items # 항목 추가
PUT /api/{tenant_id}/templates/{id}/items/{item_id} # 항목 수정
DELETE /api/{tenant_id}/templates/{id}/items/{item_id} # 항목 삭제
PUT /api/{tenant_id}/templates/{id}/items/reorder # 항목 순서 변경
```
### 검사 세션 (Phase 4)
```
POST /api/{tenant_id}/sessions # 세션 시작 (템플릿 스냅샷 저장)
GET /api/{tenant_id}/sessions # 세션 목록 (status 필터)
GET /api/{tenant_id}/sessions/in-progress # 현재 사용자 진행중 세션
GET /api/{tenant_id}/sessions/{id} # 세션 상세
PATCH /api/{tenant_id}/sessions/{id}/autosave # 자동저장 (partial_results)
POST /api/{tenant_id}/sessions/{id}/complete # 세션 완료
POST /api/{tenant_id}/sessions/{id}/cancel # 세션 취소
```
### 부품 카운터 (Phase 5)
```
GET /api/{tenant_id}/machines/{machine_id}/counters # 설비 카운터 현황
GET /api/{tenant_id}/counters/critical # 교체 임박 카운터 목록
POST /api/{tenant_id}/parts/{part_id}/counter/increment # 카운터 증가
POST /api/{tenant_id}/parts/{part_id}/replace # 부품 교체 (리셋+이력)
GET /api/{tenant_id}/parts/{part_id}/replacement-history # 교체 이력
```
### 알람 (Phase 6)
```
GET /api/{tenant_id}/alarms # 알람 목록 (type/severity/acknowledged 필터)
GET /api/{tenant_id}/alarms/summary # 미확인 알람 개수 요약
POST /api/{tenant_id}/alarms/{id}/acknowledge # 알람 확인
POST /api/{tenant_id}/alarms/acknowledge-bulk # 다중 알람 일괄 확인
```
**총 엔드포인트 수: 32개**
---
## 9. 3개 고객사 멀티테넌트 전략 (2026-02-10 추가)
> 상세 분석: `planning/integrated-analysis.md` 참조
### 9.1 업체별 설비검사/품질검사 로직 차이 → 해결 전략 요약
| 차이 영역 | 해결 메커니즘 | 코드 분기 여부 |
| -------------------------------------- | ------------------------------------------------------------ | :--------------: |
| 카운터 갱신 (PLC vs 시간 vs 날짜) | `EquipmentPart.lifecycle_type` + `counter_source` | ❌ TYPE 분기 |
| 품질 판정 (이진 vs 불량코드 vs 수치) | `InspectionTemplateItem.data_type` + `spec_min/max` | ❌ TYPE 분기 |
| 알람 유형 (임계값 vs 추세) | `InspectionAlarm.alarm_type` (4종) + 템플릿 설정 | ❌ TYPE 분기 |
| 검사 UX (빠른체크 vs 정밀 vs 모니터링) | `InspectionTemplate.inspection_mode` (3종) | ❌ TYPE 분기 |
| PLC 데이터 형식 | 어댑터 계층 (SpeedfoxAdapter / EnkiedAdapter / AlpetAdapter) | ✅ (설계상 분리) |
| 테넌트 정책 (동시세션수, 첨부파일 등) | `Tenant.workflow_config` JSON | ❌ 설정값 |
### 9.2 스키마 변경 요약 (기존 대비)
**추가된 컬럼 (7개)**:
1. `EquipmentPart.counter_source` VARCHAR(20) — 카운터 갱신 방식
2. `InspectionTemplate.inspection_mode` VARCHAR(20) — 검사 UX 모드
3. `InspectionTemplateItem.warning_min` FLOAT — 트렌드 경고 소프트 하한
4. `InspectionTemplateItem.warning_max` FLOAT — 트렌드 경고 소프트 상한
5. `InspectionTemplateItem.trend_window` INTEGER — 연속 N회 경고구간 체크
6. `InspectionSession.lot_number` VARCHAR(100) — 로트/시리얼 (IATF 추적성)
7. `InspectionAlarm.alarm_type``trend_warning` 값 추가 (기존 3종 → 4종)
**추가된 프론트엔드 레이아웃 (3개)**:
```
dashboard/app/[tenant]/inspections/[id]/_layouts/
ChecklistLayout.tsx # inspection_mode='checklist'
MeasurementLayout.tsx # inspection_mode='measurement'
MonitoringLayout.tsx # inspection_mode='monitoring'
```
**추가된 어댑터 (1개)**:
- `src/adapters/alpet_adapter.py` — 알펫 연속공정 PLC 데이터 변환
### 9.3 구현 순서 (기존 Phase에 통합)
| Phase | 기존 내용 | 추가 작업 | 추가 소요 |
| -------------- | ----------- | ---------------------------------------------------------------- | -------------- |
| Phase 2 | 설비 + 부품 | `counter_source` 컬럼 추가, `auto_time` 계산 로직 | +0.5일 |
| Phase 3 | 검사 템플릿 | `inspection_mode` 컬럼 + `warning_min/max/trend_window` 추가 | +0.5일 |
| Phase 4 | 검사 세션 | `lot_number` 컬럼, 3개 레이아웃 (ChecklistLayout 등) | +1일 |
| Phase 6 | 알람 | `trend_warning` 알람 유형 + 트렌드 평가 로직 | +0.5일 |
| Phase 7 | 통합 테스트 | 엔키드/알펫 시드 데이터, 멀티테넌트 E2E 테스트 | +0.5일 |
| **합계** | | | **+3일** |
**수정된 일정**: 기존 13일 → **16일 (약 3.2주)**
### 9.4 주의사항
1. **알펫 설비 계층**: 알펫의 Machine은 개별 기계가 아닌 **라인 구간** (전처리존, 라미네이팅 스테이션 등). Machine 모델로 표현 가능하나, 이름 규칙으로 구분 (예: "ALPET-LINE1-PRETREAT", "ALPET-LINE1-LAMI")
2. **엔키드 로트 추적**: IATF 16949은 검사 결과 → 특정 로트/시리얼 연결 필수. `lot_number` 필드로 대응
3. **스피폭스 대규모 알람 성능**: 304대 × 부품 → 알람 평가는 **검사 제출/카운터 갱신 시점에만** (PLC 원시 데이터 수신 시에는 평가 안 함)
---
## 10. 미결 질문 (구현 전 확인 필요)
1. **병렬 개발 전략**: Phase 3(검사 템플릿)과 Phase 5(카운터)를 병렬로 진행할지, 순차로 할지?
- 병렬: ~2일 단축 (16일)
- 순차: 더 안전하지만 더 오래 걸림 (18.5일)
2. **품질검사 범위**: 이번 구현에서 `subject_type='product'`도 실제 UI/로직을 만들지?
- 추천: equipment만 우선 구현. 모델에 subject_type 필드만 두고, product 관련 UI/로직은 다음 Phase에서 확장
- 엔키드가 product 검사 필요하므로 Phase 1+ 이후 확장 예정
- 둘 다 하면 2~3일 추가
3. **검사 미실시(overdue) 알람의 기준**: 스케줄러(cron)로 주기적 체크할지, API 호출 시 체크할지?
- 옵션 A: 별도 스케줄러 (APScheduler 등) — 정확하지만 인프라 복잡도 증가
- 옵션 B: 대시보드 로딩 시 체크 — 단순하지만 대시보드 접속 안 하면 알람 안 생김
- 옵션 C: 알람 목록 API 호출 시 체크 — 절충안
- 추천: Phase 1에서는 옵션 C (on_access), 향후 스케줄러로 확장
4. **알펫 PLC 태그 리스트**: AlpetAdapter 구현 전 알펫에서 PLC 태그 사양서 수집 필요
5. **엔키드 X-ray 이미지 저장소**: 첨부파일 스토리지 설계 (S3? 로컬? 용량 예측 필요)
6. **연속공정 세션 단위**: 알펫의 검사 세션 = 코일 1롤? 시간 구간? 교대 1회?