# πŸ—οΈ TK-MP-Project Rules & Context ## πŸ“‹ **ν”„λ‘œμ νŠΈ κ°œμš”** - **λͺ©μ **: λ°°κ΄€ 자재 BOM 관리 및 리비전 비ꡐ μ‹œμŠ€ν…œ - **μ£Όμš” κΈ°λŠ₯**: 파일 μ—…λ‘œλ“œ, 자재 λΆ„λ₯˜, 리비전 비ꡐ, ꡬ맀 관리, μ—‘μ…€ 내보내기 ## πŸ› οΈ **기술 μŠ€νƒ** ``` Frontend: React.js + Material-UI + Vite + React Router DOM + Nginx Backend: FastAPI + SQLAlchemy + Python + Uvicorn Database: PostgreSQL (운영 및 개발) μΊμ‹œ: Redis 관리도ꡬ: pgAdmin4 μ»¨ν…Œμ΄λ„ˆ: Docker + Docker Compose 기타: Axios, XLSX (SheetJS), file-saver ``` ## πŸ“ **ν”„λ‘œμ νŠΈ ꡬ쑰** ``` TK-MP-Project/ β”œβ”€β”€ frontend/src/ β”‚ β”œβ”€β”€ pages/ # νŽ˜μ΄μ§€ μ»΄ν¬λ„ŒνŠΈ β”‚ β”œβ”€β”€ components/ # μž¬μ‚¬μš© μ»΄ν¬λ„ŒνŠΈ β”‚ β”œβ”€β”€ utils/ # μœ ν‹Έλ¦¬ν‹° (μ—‘μ…€ λ“±) β”‚ └── api.js # API 톡신 β”œβ”€β”€ backend/app/ β”‚ β”œβ”€β”€ routers/ # API λΌμš°ν„° β”‚ β”œβ”€β”€ services/ # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 (λΆ„λ₯˜κΈ° λ“±) β”‚ β”œβ”€β”€ models.py # DB λͺ¨λΈ β”‚ └── main.py # FastAPI μ•± └── database/ # DB μŠ€ν‚€λ§ˆ/μ‹œλ“œ ``` ## 🐳 **Docker μ‹€ν–‰ ν™˜κ²½** ### μ»¨ν…Œμ΄λ„ˆ ꡬ성 - **tk-mp-frontend**: React + Nginx (포트: 3000) - **tk-mp-backend**: FastAPI + Uvicorn (포트: 8000) - **tk-mp-postgres**: PostgreSQL (포트: 5432) - **tk-mp-redis**: Redis (포트: 6379) - **tk-mp-pgadmin**: pgAdmin4 (포트: 5050) ### μ‹€ν–‰ 방법 ```bash # 개발 ν™˜κ²½ ./scripts/dev.sh # λ˜λŠ” docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d # ν”„λ‘œλ•μ…˜ ν™˜κ²½ ./scripts/prod.sh # λ˜λŠ” docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d # κΈ°λ³Έ ν™˜κ²½ docker-compose up -d ``` ### μ€‘μš”ν•œ μ„€μ • 사항 - **API URL μ„€μ •**: - κ°œλ°œν™˜κ²½: `http://localhost:8000` (직접 μ ‘κ·Ό) - ν”„λ‘œλ•μ…˜: `/api` (nginx ν”„λ‘μ‹œλ₯Ό ν†΅ν•œ μƒλŒ€κ²½λ‘œ) - **λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²°**: `postgres:5432` (μ»¨ν…Œμ΄λ„ˆλͺ… μ‚¬μš©, localhost μ•„λ‹˜!) - **ν™˜κ²½λ³€μˆ˜**: `VITE_API_URL`둜 API URL μ˜€λ²„λΌμ΄λ“œ κ°€λŠ₯ ## πŸ—„οΈ **핡심 λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ** ```sql -- 핡심 ν…Œμ΄λΈ”λ“€ jobs (job_no, job_name, client_name, ...) files (id, job_no, revision, original_filename, ...) materials (id, file_id, original_description, classified_category, quantity, ...) pipe_details (material_id, length_mm, ...) -- 기타: fitting_details, flange_details, bolt_details, gasket_details ``` ## πŸ”§ **μ€‘μš”ν•œ μ½”λ”© μ»¨λ²€μ…˜ & νŒ¨ν„΄** ### **1. 자재 λΆ„λ₯˜ μ‹œμŠ€ν…œ** ```python # 항상 이 μˆœμ„œλ‘œ λΆ„λ₯˜κΈ° 호좜 classification_result = classify_pipe("", description, main_nom, length_value) # κ²°κ³Ό: {"category": "PIPE", "confidence": 0.95, ...} ``` ### **2. νŒŒμ΄ν”„ 길이 처리 κ·œμΉ™** ```javascript // ❌ μ ˆλŒ€ ν•˜μ§€ 말 것: 평균 길이 계산/ν‘œμ‹œ // βœ… 항상 ν•  것: 총 길이 κΈ°μ€€ 계산 const totalLength = quantity * unitLength; // 총 길이 = μˆ˜λŸ‰ Γ— λ‹¨μœ„κΈΈμ΄ ``` ### **3. 자재 ν•΄μ‹± κ·œμΉ™** ```python # 자재 κ³ μœ μ„± νŒλ‹¨: description + size + material_grade material_hash = hashlib.md5(f"{description}|{size_spec}|{material_grade}".encode()).hexdigest() ``` ### **4. 리비전 비ꡐ 둜직** ```python # 이전 리비전 μžλ™ 탐지: 숫자 기반 비ꡐ current_rev_num = int(current_revision.replace("Rev.", "")) # Rev.0 β†’ Rev.1 β†’ Rev.2 μˆœμ„œ ``` ## πŸ› **자주 λ°œμƒν•˜λŠ” 이슈 & 해결법** ### **1. νŒŒμ΄ν”„ 길이 ν•©μ‚° 문제** ```python # ❌ 잘λͺ»λœ SQL: GROUP BY에 pd.length_mm 포함 # βœ… μ˜¬λ°”λ₯Έ 방법: Pythonμ—μ„œ 같은 νŒŒμ΄ν”„λ“€ ν•©μΉ˜κΈ° if material_hash in materials_dict: existing["quantity"] += float(new_quantity) existing["total_length"] += new_quantity * unit_length ``` ### **2. ν”„λ‘ νŠΈμ—”λ“œ λ³€μˆ˜ μ΄ˆκΈ°ν™”** ```javascript // ❌ μ‚¬μš© 전에 μ„ μ–Έν•˜μ§€ μ•ŠμŒ const summaryData = [..., consolidatedMaterials.length, ...]; const consolidatedMaterials = consolidateMaterials(materials); // 뒀에 μ„ μ–Έ // βœ… μ‚¬μš© 전에 λ¨Όμ € μ„ μ–Έ const consolidatedMaterials = consolidateMaterials(materials); const summaryData = [..., consolidatedMaterials.length, ...]; ``` ### **3. API 응닡 처리** ```javascript // βœ… 항상 Axios 응닡 ꡬ쑰 확인 setComparisonResult(result.data || result); // response.data μš°μ„  ``` ## 🎯 **UI/UX κ°€μ΄λ“œλΌμΈ** ### **1. 자재 ν‘œμ‹œ κ·œμΉ™** - **νŒŒμ΄ν”„**: "총 길이: 4,561mm" (ν‰κ· λ‹¨μœ„ ν‘œμ‹œ κΈˆμ§€) - **기타 자재**: "μˆ˜λŸ‰: 24 EA" - **변경사항**: "이전: 2,781mm β†’ ν˜„μž¬: 4,561mm / λ³€ν™”: +1,780mm" ### **2. λ²„νŠΌ 넀이밍** - "BOM λͺ©λ‘μœΌλ‘œ" (λ’€λ‘œκ°€κΈ°) - "μ—‘μ…€ 내보내기" - "상세 비ꡐ 보기" ### **3. νŽ˜μ΄μ§€ λ„€λΉ„κ²Œμ΄μ…˜** ```javascript // BOM κ΄€λ ¨ νŽ˜μ΄μ§€λ“€μ€ job_no κΈ°μ€€μœΌλ‘œ 이동 navigate(`/bom-status?job_no=${jobNo}`); navigate(`/material-comparison?job_no=${jobNo}&revision=${revision}`); ``` ## πŸ”„ **개발 μ›Œν¬ν”Œλ‘œμš°** ### **1. μ„œλ²„ μ‹€ν–‰ λͺ…λ Ήμ–΄** ```bash # λ°±μ—”λ“œ μ‹€ν–‰ (터미널 1번) - TK-MP-Project λ£¨νŠΈμ—μ„œ source venv/bin/activate # κ°€μƒν™˜κ²½ ν™œμ„±ν™” (venvλŠ” λ£¨νŠΈμ— 있음) cd backend python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 # ν”„λ‘ νŠΈμ—”λ“œ μ‹€ν–‰ (터미널 2번) - TK-MP-Project λ£¨νŠΈμ—μ„œ cd frontend npm run dev # npm start μ•„λ‹˜! ``` **접속 μ£Όμ†Œ:** - λ°±μ—”λ“œ API: http://localhost:8000 - API λ¬Έμ„œ: http://localhost:8000/docs - ν”„λ‘ νŠΈμ—”λ“œ: http://localhost:5173 ### **2. λ°±μ—”λ“œ λ³€κ²½ μ‹œ** ```bash # 항상 κ°€μƒν™˜κ²½μ—μ„œ μ‹€ν–‰ (μ‚¬μš©μž μ„ ν˜Έμ‚¬ν•­) cd backend python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` ### **3. λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ λ³€κ²½ μ‹œ** ```sql -- scripts/ 폴더에 λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ SQL 파일 생성 -- 번호 μˆœμ„œ: 01_, 02_, 03_... ``` ### **4. 컀밋 λ©”μ‹œμ§€** ``` ν•œκ΅­μ–΄λ‘œ μž‘μ„± (μ‚¬μš©μž μ„ ν˜Έμ‚¬ν•­) 예: "νŒŒμ΄ν”„ 길이 계산 및 μ—‘μ…€ 내보내기 버그 μˆ˜μ •" ``` ## ⚠️ **μ ˆλŒ€ ν•˜μ§€ 말아야 ν•  것듀** 1. **νŒŒμ΄ν”„ "ν‰κ· λ‹¨μœ„" ν‘œμ‹œ** - μ‚¬μš©μžκ°€ ν˜Όλž€μŠ€λŸ¬μ›Œν•¨ 2. **ν•˜λ“œμ½”λ”©λœ 길이 κ°’** - μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€ κ°’ μ‚¬μš© 3. **μ˜μ–΄ 컀밋 λ©”μ‹œμ§€** - μ‚¬μš©μžκ°€ ν•œκ΅­μ–΄ μ„ ν˜Έ 4. **SQLμ—μ„œ κ³Όλ„ν•œ GROUP BY** - 같은 자재 뢄리됨 5. **λΉ„μœ¨ 기반 길이 계산** - μ‹€μ œ 총길이 μ‚¬μš©ν•΄μ•Ό 함 ## πŸ’° **ꡬ맀 μˆ˜λŸ‰ 계산 κ·œμΉ™** ### **1. νŒŒμ΄ν”„ (PIPE)** ```javascript // 6,000mm λ‹¨μœ„ 판맀 + μ ˆλ‹¨μ—¬μœ λΆ„ 2mm/쑰각 const cutLength = originalLength + 2; // μ ˆλ‹¨ μ—¬μœ λΆ„ const pipeCount = Math.ceil(cutLength / 6000); // 올림 처리 ``` ### **2. ν”ΌνŒ…/계기/밸브 (FITTING/INSTRUMENT/VALVE)** ```javascript // BOM μˆ˜λŸ‰ κ·ΈλŒ€λ‘œ const purchaseQuantity = bomQuantity; ``` ### **3. 볼트/λ„ˆνŠΈ (BOLT)** ```javascript // +5% ν›„ 4의 배수둜 올림 const withMargin = bomQuantity * 1.05; const purchaseQuantity = Math.ceil(withMargin / 4) * 4; // 예: 150 β†’ 157.5 β†’ 160 SETS ``` ### **4. κ°€μŠ€μΌ“ (GASKET)** ```javascript // 5의 배수둜 올림 const purchaseQuantity = Math.ceil(bomQuantity / 5) * 5; // 예: 7 β†’ 10 EA ``` ## 🎯 **ν˜„μž¬ μ§„ν–‰ 상황** - βœ… 자재 μ—…λ‘œλ“œ 및 λΆ„λ₯˜ μ‹œμŠ€ν…œ - βœ… 리비전 비ꡐ κΈ°λŠ₯ - βœ… νŒŒμ΄ν”„ 길이 ν•©μ‚° 둜직 μˆ˜μ • - βœ… μ—‘μ…€ 내보내기 κΈ°λŠ₯ - 🚧 ꡬ맀 μˆ˜λŸ‰ 계산 μ‹œμŠ€ν…œ (μ§„ν–‰ 쀑) ## πŸ“š **μΆ”κ°€ 참고사항** - μ‚¬μš©μžλŠ” κ°€μƒν™˜κ²½μ—μ„œ Python 싀행을 μ„ ν˜Έ - λ°±μ—”λ“œ μ„œλ²„λŠ” μžλ™ μž¬μ‹œμž‘λ˜λ―€λ‘œ μˆ˜λ™ μž¬μ‹œμž‘ λΆˆν•„μš” - μž‘μ—… μƒνƒœλŠ” 'in-progress'와 'complete'λ₯Ό λͺ…ν™•νžˆ ν‘œμ‹œ --- ## 🚨 **Docker μ‹€ν–‰ κ΄€λ ¨ νŠΈλŸ¬λΈ”μŠˆνŒ…** ### ν•΄κ²°λœ μ£Όμš” λ¬Έμ œλ“€ (2025.08.01) 1. **ν”„λ‘ νŠΈμ—”λ“œ API μ—°κ²° 였λ₯˜** - **문제**: λΉŒλ“œλœ ν”„λ‘ νŠΈμ—”λ“œκ°€ 10080 포트둜 API μš”μ²­ - **원인**: ν™˜κ²½λ³€μˆ˜ μ„€μ • λˆ„λ½μœΌλ‘œ ν•˜λ“œμ½”λ”©λœ 포트 μ‚¬μš© - **ν•΄κ²°**: ```bash # Dockerfileμ—μ„œ λΉŒλ“œ μ‹œ ν™˜κ²½λ³€μˆ˜ μ£Όμž… ARG VITE_API_URL=http://localhost:8000 ENV VITE_API_URL=$VITE_API_URL # docker-compose.ymlμ—μ„œ ν™˜κ²½λ³€μˆ˜ μ„€μ • environment: - VITE_API_URL=${VITE_API_URL:-/api} ``` 2. **λ°±μ—”λ“œ λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° μ‹€νŒ¨** - **문제**: `psycopg2.OperationalError` - localhost:5432 μ—°κ²° κ±°λΆ€ - **원인**: λ°±μ—”λ“œκ°€ localhost둜 DB μ ‘κ·Ό μ‹œλ„ (Docker μ»¨ν…Œμ΄λ„ˆ λ‚΄μ—μ„œλŠ” λΆˆκ°€) - **ν•΄κ²°**: ```python # backend/app/database.py μˆ˜μ • DATABASE_URL = os.getenv( "DATABASE_URL", "postgresql://tkmp_user:tkmp_password_2025@postgres:5432/tk_mp_bom" # localhost β†’ postgres ) ``` 3. **ν™˜κ²½λ³„ μ„€μ • 관리 κ°œμ„ ** - **문제**: 개발/ν”„λ‘œλ•μ…˜ ν™˜κ²½ ꡬ뢄 없이 ν•˜λ“œμ½”λ”© - **ν•΄κ²°**: docker-compose 파일 뢄리 ```bash docker-compose.yml # κΈ°λ³Έ μ„€μ • docker-compose.dev.yml # 개발 ν™˜κ²½ μ˜€λ²„λΌμ΄λ“œ docker-compose.prod.yml # ν”„λ‘œλ•μ…˜ ν™˜κ²½ μ˜€λ²„λΌμ΄λ“œ ``` ### μ‹€ν–‰ μ „ 체크리슀트 - [ ] Docker 및 Docker Compose μ„€μΉ˜ 확인 - [ ] λͺ¨λ“  μ»¨ν…Œμ΄λ„ˆ 정상 μ‹€ν–‰ 확인: `docker-compose ps` - [ ] λ°±μ—”λ“œ API λ¬Έμ„œ μ ‘κ·Ό κ°€λŠ₯: http://localhost:8000/docs - [ ] ν”„λ‘ νŠΈμ—”λ“œ λ‘œλ”© 확인: http://localhost:3000 - [ ] λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 확인: pgAdmin (http://localhost:5050) **λ§ˆμ§€λ§‰ μ—…λ°μ΄νŠΈ**: 2025-08-01 (Docker ν™˜κ²½ ꡬ성 μ™„λ£Œ)