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>
1049 lines
137 KiB
Markdown
1049 lines
137 KiB
Markdown
# 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회?
|