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

137 KiB
Raw Blame History

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. 프로젝트 셋업 절차

디렉토리 전환

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개) — 새로 작성

-- 사용자
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개) — 새로 작성

-- 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-tailwindnpm 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-11-9)와 프론트엔드(1-101-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-12-7)와 프론트엔드(2-82-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
원형 게이지 또는 수평 진행바
색상: 070% 녹색 (var(--md-success)), 7090% 주황 (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 + 이 패턴으로 동시성 문제를 안전하게 처리함.

-- 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.pycheck_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)

{
  "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_typetrend_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회?