test(tkeg): 분류기 테스트 수정 — 변경된 반환 키 대응 (8/8 통과)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,3 +39,12 @@ System1에는 FastAPI bridge도 있음 (30008, AI 연동용).
|
|||||||
git push && ssh hyungi@100.71.132.52 "cd /volume1/docker/tk-factory-services && git pull && export PATH=\$PATH:/volume2/@appstore/ContainerManager/usr/bin && docker compose up -d --build <서비스명>"
|
git push && ssh hyungi@100.71.132.52 "cd /volume1/docker/tk-factory-services && git pull && export PATH=\$PATH:/volume2/@appstore/ContainerManager/usr/bin && docker compose up -d --build <서비스명>"
|
||||||
```
|
```
|
||||||
상세: DEPLOY-GUIDE.md 참조. 아키텍처: ARCHITECTURE.md 참조.
|
상세: DEPLOY-GUIDE.md 참조. 아키텍처: ARCHITECTURE.md 참조.
|
||||||
|
|
||||||
|
## 멀티 에이전트 워크플로우
|
||||||
|
이 프로젝트는 Cowork(설계/검토) + Claude Code(코딩) 멀티 에이전트 방식을 지원한다.
|
||||||
|
- **워크플로우 가이드**: `.cowork/WORKFLOW-GUIDE.md`
|
||||||
|
- **스프린트 계획/스펙**: `.cowork/sprints/sprint-NNN/`
|
||||||
|
- **에러 기록**: `.cowork/errors/ERROR-LOG.md`
|
||||||
|
- **템플릿**: `.cowork/templates/`
|
||||||
|
|
||||||
|
Claude Code Worker는 자신의 섹션 스펙(section-*.md)만 읽고 작업할 것. 다른 섹션 파일 수정 금지.
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
|
|||||||
pipe_patterns = [
|
pipe_patterns = [
|
||||||
(r'A312\s*(TP\d+[A-Z]*)', lambda m: f'A312 {m.group(1)}'),
|
(r'A312\s*(TP\d+[A-Z]*)', lambda m: f'A312 {m.group(1)}'),
|
||||||
(r'A106\s*(GR\.?\s*[A-Z]+)', lambda m: f'A106 {m.group(1)}'), # A106 GR.B, A106 GR B 등 전체 보존
|
(r'A106\s*(GR\.?\s*[A-Z]+)', lambda m: f'A106 {m.group(1)}'), # A106 GR.B, A106 GR B 등 전체 보존
|
||||||
(r'A106\s*([A-Z]+)', lambda m: f'A106 GR.{m.group(1)}'), # A106 B → A106 GR.B
|
(r'A106\s*([A-Z]+)', lambda m: f'A106 GR.{m.group(1)}'), # A106 B -> A106 GR.B
|
||||||
(r'A333\s*(GR\.?\s*[A-Z0-9]+)', lambda m: f'A333 {m.group(1)}'), # A333 GR.6 등 전체 보존
|
(r'A333\s*(GR\.?\s*[A-Z0-9]+)', lambda m: f'A333 {m.group(1)}'), # A333 GR.6 등 전체 보존
|
||||||
(r'A333\s*([A-Z0-9]+)', lambda m: f'A333 GR.{m.group(1)}'), # A333 6 → A333 GR.6
|
(r'A333\s*([A-Z0-9]+)', lambda m: f'A333 GR.{m.group(1)}'), # A333 6 -> A333 GR.6
|
||||||
(r'A53\s*(GR\.?\s*[A-Z]+)', lambda m: f'A53 {m.group(1)}'), # A53 GR.B 등 전체 보존
|
(r'A53\s*(GR\.?\s*[A-Z]+)', lambda m: f'A53 {m.group(1)}'), # A53 GR.B 등 전체 보존
|
||||||
(r'A53\s*([A-Z]+)', lambda m: f'A53 GR.{m.group(1)}'), # A53 B → A53 GR.B
|
(r'A53\s*([A-Z]+)', lambda m: f'A53 GR.{m.group(1)}'), # A53 B -> A53 GR.B
|
||||||
(r'A335\s*(P\d+[A-Z]*)', lambda m: f'A335 {m.group(1)}'),
|
(r'A335\s*(P\d+[A-Z]*)', lambda m: f'A335 {m.group(1)}'),
|
||||||
(r'STPG\s*(\d+)', lambda m: f'STPG {m.group(1)}'),
|
(r'STPG\s*(\d+)', lambda m: f'STPG {m.group(1)}'),
|
||||||
(r'STS\s*(\d+[A-Z]*)', lambda m: f'STS {m.group(1)}')
|
(r'STS\s*(\d+[A-Z]*)', lambda m: f'STS {m.group(1)}')
|
||||||
@@ -77,10 +77,10 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
|
|||||||
(r'A234\s*(WP[A-Z]+)', lambda m: f'A234 {m.group(1)}'),
|
(r'A234\s*(WP[A-Z]+)', lambda m: f'A234 {m.group(1)}'),
|
||||||
(r'A420\s*(WPL\d+)', lambda m: f'A420 {m.group(1)}'),
|
(r'A420\s*(WPL\d+)', lambda m: f'A420 {m.group(1)}'),
|
||||||
(r'A105\s*(GR\.?\s*[N])', lambda m: f'A105 {m.group(1)}'), # A105 GR.N 전체 보존
|
(r'A105\s*(GR\.?\s*[N])', lambda m: f'A105 {m.group(1)}'), # A105 GR.N 전체 보존
|
||||||
(r'A105\s*([N])', lambda m: f'A105 GR.{m.group(1)}'), # A105 N → A105 GR.N
|
(r'A105\s*([N])', lambda m: f'A105 GR.{m.group(1)}'), # A105 N -> A105 GR.N
|
||||||
(r'A105(?!\s*[A-Z])', lambda m: f'A105'), # A105만 있는 경우
|
(r'A105(?!\s*[A-Z])', lambda m: f'A105'), # A105만 있는 경우
|
||||||
(r'A106\s*(GR\.?\s*[A-Z]+)', lambda m: f'A106 {m.group(1)}'), # A106 GR.B 전체 보존
|
(r'A106\s*(GR\.?\s*[A-Z]+)', lambda m: f'A106 {m.group(1)}'), # A106 GR.B 전체 보존
|
||||||
(r'A106\s*([A-Z]+)', lambda m: f'A106 GR.{m.group(1)}'), # A106 B → A106 GR.B
|
(r'A106\s*([A-Z]+)', lambda m: f'A106 GR.{m.group(1)}'), # A106 B -> A106 GR.B
|
||||||
(r'A182\s*(F\d+[A-Z]*)', lambda m: f'A182 {m.group(1)}'),
|
(r'A182\s*(F\d+[A-Z]*)', lambda m: f'A182 {m.group(1)}'),
|
||||||
(r'A350\s*(LF\d+)', lambda m: f'A350 {m.group(1)}')
|
(r'A350\s*(LF\d+)', lambda m: f'A350 {m.group(1)}')
|
||||||
]
|
]
|
||||||
@@ -95,7 +95,7 @@ def extract_enhanced_material_grade(description: str, original_grade: str, categ
|
|||||||
flange_patterns = [
|
flange_patterns = [
|
||||||
(r'A182\s*(F\d+[A-Z]*)', lambda m: f'A182 {m.group(1)}'),
|
(r'A182\s*(F\d+[A-Z]*)', lambda m: f'A182 {m.group(1)}'),
|
||||||
(r'A105\s*(GR\.?\s*[N])', lambda m: f'A105 {m.group(1)}'), # A105 GR.N 전체 보존
|
(r'A105\s*(GR\.?\s*[N])', lambda m: f'A105 {m.group(1)}'), # A105 GR.N 전체 보존
|
||||||
(r'A105\s*([N])', lambda m: f'A105 GR.{m.group(1)}'), # A105 N → A105 GR.N
|
(r'A105\s*([N])', lambda m: f'A105 GR.{m.group(1)}'), # A105 N -> A105 GR.N
|
||||||
(r'A105(?!\s*[A-Z])', lambda m: f'A105'), # A105만 있는 경우
|
(r'A105(?!\s*[A-Z])', lambda m: f'A105'), # A105만 있는 경우
|
||||||
(r'A350\s*(LF\d+)', lambda m: f'A350 {m.group(1)}'),
|
(r'A350\s*(LF\d+)', lambda m: f'A350 {m.group(1)}'),
|
||||||
(r'A694\s*(F\d+)', lambda m: f'A694 {m.group(1)}')
|
(r'A694\s*(F\d+)', lambda m: f'A694 {m.group(1)}')
|
||||||
@@ -398,16 +398,16 @@ async def upload_file(
|
|||||||
|
|
||||||
# 🔥 구매확정된 자재 매핑 정보 저장
|
# 🔥 구매확정된 자재 매핑 정보 저장
|
||||||
purchased_materials_map = revision_comparison.get("purchased_materials_map", {})
|
purchased_materials_map = revision_comparison.get("purchased_materials_map", {})
|
||||||
print(f" - 구매확정 자재 매핑: {len(purchased_materials_map)}개")
|
logger.info(f" - 구매확정 자재 매핑: {len(purchased_materials_map)}개")
|
||||||
else:
|
else:
|
||||||
print("📝 이전 구매확정 자료 없음 - 전체 자재 분류")
|
logger.info("이전 구매확정 자료 없음 - 전체 자재 분류")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ 리비전 비교 실패, 전체 자재 분류로 진행: {str(e)}")
|
logger.warning(f"리비전 비교 실패, 전체 자재 분류로 진행: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
print(f"🔧 자재 분류 및 저장 시작: {len(materials_to_classify)}개 자재")
|
logger.info(f"자재 분류 및 저장 시작: {len(materials_to_classify)}개 자재")
|
||||||
|
|
||||||
# [변경] MaterialService를 사용하여 자재 처리 및 저장
|
# [변경] MaterialService를 사용하여 자재 처리 및 저장
|
||||||
materials_inserted = MaterialService.process_and_save_materials(
|
materials_inserted = MaterialService.process_and_save_materials(
|
||||||
@@ -418,7 +418,7 @@ async def upload_file(
|
|||||||
)
|
)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
print(f"자재 저장 완료: {materials_inserted}개")
|
logger.info(f"자재 저장 완료: {materials_inserted}개")
|
||||||
|
|
||||||
# [변경] MaterialService를 사용하여 구매신청 정보 상속
|
# [변경] MaterialService를 사용하여 구매신청 정보 상속
|
||||||
if parent_file_id is not None:
|
if parent_file_id is not None:
|
||||||
@@ -439,9 +439,9 @@ async def upload_file(
|
|||||||
ip_address=request.client.host if request.client else None,
|
ip_address=request.client.host if request.client else None,
|
||||||
user_agent=request.headers.get('user-agent')
|
user_agent=request.headers.get('user-agent')
|
||||||
)
|
)
|
||||||
print(f"활동 로그 기록 완료: {username} - 파일 업로드")
|
logger.info(f"활동 로그 기록 완료: {username} - 파일 업로드")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"활동 로그 기록 실패: {str(e)}")
|
logger.error(f"활동 로그 기록 실패: {str(e)}")
|
||||||
# 로그 실패는 업로드 성공에 영향을 주지 않음
|
# 로그 실패는 업로드 성공에 영향을 주지 않음
|
||||||
|
|
||||||
# 리비전 업로드인 경우 누락된 도면 감지 및 구매신청 여부 확인
|
# 리비전 업로드인 경우 누락된 도면 감지 및 구매신청 여부 확인
|
||||||
@@ -460,8 +460,8 @@ async def upload_file(
|
|||||||
purchase_result = db.execute(purchase_check_query, {"parent_file_id": parent_file_id}).fetchone()
|
purchase_result = db.execute(purchase_check_query, {"parent_file_id": parent_file_id}).fetchone()
|
||||||
has_previous_purchase = purchase_result.purchase_count > 0
|
has_previous_purchase = purchase_result.purchase_count > 0
|
||||||
|
|
||||||
print(f"📦 이전 리비전 구매신청 여부: {has_previous_purchase} ({purchase_result.purchase_count}개 자재)")
|
logger.info(f"이전 리비전 구매신청 여부: {has_previous_purchase} ({purchase_result.purchase_count}개 자재)")
|
||||||
print(f"📂 parent_file_id: {parent_file_id}, new file_id: {file_id}")
|
logger.info(f"parent_file_id: {parent_file_id}, new file_id: {file_id}")
|
||||||
|
|
||||||
# 이전 리비전의 도면 목록 조회
|
# 이전 리비전의 도면 목록 조회
|
||||||
prev_drawings_query = text("""
|
prev_drawings_query = text("""
|
||||||
@@ -472,7 +472,7 @@ async def upload_file(
|
|||||||
GROUP BY drawing_name, line_no
|
GROUP BY drawing_name, line_no
|
||||||
""")
|
""")
|
||||||
prev_drawings_result = db.execute(prev_drawings_query, {"parent_file_id": parent_file_id}).fetchall()
|
prev_drawings_result = db.execute(prev_drawings_query, {"parent_file_id": parent_file_id}).fetchall()
|
||||||
print(f"📋 이전 리비전 도면 수: {len(prev_drawings_result)}")
|
logger.info(f"이전 리비전 도면 수: {len(prev_drawings_result)}")
|
||||||
|
|
||||||
# 새 리비전의 도면 목록 조회
|
# 새 리비전의 도면 목록 조회
|
||||||
new_drawings_query = text("""
|
new_drawings_query = text("""
|
||||||
@@ -482,7 +482,7 @@ async def upload_file(
|
|||||||
AND (drawing_name IS NOT NULL OR line_no IS NOT NULL)
|
AND (drawing_name IS NOT NULL OR line_no IS NOT NULL)
|
||||||
""")
|
""")
|
||||||
new_drawings_result = db.execute(new_drawings_query, {"file_id": file_id}).fetchall()
|
new_drawings_result = db.execute(new_drawings_query, {"file_id": file_id}).fetchall()
|
||||||
print(f"📋 새 리비전 도면 수: {len(new_drawings_result)}")
|
logger.info(f"새 리비전 도면 수: {len(new_drawings_result)}")
|
||||||
|
|
||||||
prev_drawings = set()
|
prev_drawings = set()
|
||||||
for row in prev_drawings_result:
|
for row in prev_drawings_result:
|
||||||
@@ -500,9 +500,9 @@ async def upload_file(
|
|||||||
|
|
||||||
missing_drawings = prev_drawings - new_drawings
|
missing_drawings = prev_drawings - new_drawings
|
||||||
|
|
||||||
print(f"📊 이전 도면: {list(prev_drawings)[:5]}")
|
logger.info(f"이전 도면: {list(prev_drawings)[:5]}")
|
||||||
print(f"📊 새 도면: {list(new_drawings)[:5]}")
|
logger.info(f"새 도면: {list(new_drawings)[:5]}")
|
||||||
print(f"❌ 누락 도면: {len(missing_drawings)}개")
|
logger.error(f"누락 도면: {len(missing_drawings)}개")
|
||||||
|
|
||||||
if missing_drawings:
|
if missing_drawings:
|
||||||
# 누락된 도면의 자재 상세 정보
|
# 누락된 도면의 자재 상세 정보
|
||||||
@@ -543,9 +543,9 @@ async def upload_file(
|
|||||||
})
|
})
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
print(f"✅ 누락 도면 자재 상태 업데이트: {len(missing_drawings)}개 도면 → {status_to_set}")
|
logger.info(f"누락 도면 자재 상태 업데이트: {len(missing_drawings)}개 도면 -> {status_to_set}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"누락 도면 감지 실패: {str(e)}")
|
logger.error(f"누락 도면 감지 실패: {str(e)}")
|
||||||
db.rollback()
|
db.rollback()
|
||||||
# 감지 실패는 업로드 성공에 영향 없음
|
# 감지 실패는 업로드 성공에 영향 없음
|
||||||
|
|
||||||
@@ -591,8 +591,8 @@ async def upload_file(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_details = traceback.format_exc()
|
error_details = traceback.format_exc()
|
||||||
print(f"❌ 파일 업로드 실패 - 상세 에러:")
|
logger.error(f"파일 업로드 실패 - 상세 에러:")
|
||||||
print(error_details)
|
logger.error(error_details)
|
||||||
|
|
||||||
db.rollback()
|
db.rollback()
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
@@ -1431,7 +1431,7 @@ async def get_materials(
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# 밸브 그룹별로 대표 밸브 하나만 추가 (그룹핑된 정보로)
|
# 밸브 그룹별로 대표 밸브 하나만 추가 (그룹핑된 정보로)
|
||||||
print(f"DEBUG: 전체 밸브 수: {valve_count}, valve_groups 수: {len(valve_groups)}")
|
logger.info(f"전체 밸브 수: {valve_count}, valve_groups 수: {len(valve_groups)}")
|
||||||
try:
|
try:
|
||||||
for valve_key, group_info in valve_groups.items():
|
for valve_key, group_info in valve_groups.items():
|
||||||
if group_info["materials"]:
|
if group_info["materials"]:
|
||||||
@@ -1441,14 +1441,14 @@ async def get_materials(
|
|||||||
if 'valve_details' in representative_valve:
|
if 'valve_details' in representative_valve:
|
||||||
representative_valve['valve_details']['group_total_quantity'] = group_info["total_quantity"]
|
representative_valve['valve_details']['group_total_quantity'] = group_info["total_quantity"]
|
||||||
material_list.append(representative_valve)
|
material_list.append(representative_valve)
|
||||||
print(f"DEBUG: 밸브 추가됨 - {valve_key}, 수량: {group_info['total_quantity']}")
|
logger.info(f"밸브 추가됨 - {valve_key}, 수량: {group_info['total_quantity']}")
|
||||||
except Exception as valve_error:
|
except Exception as valve_error:
|
||||||
print(f"ERROR: 밸브 그룹핑 실패 - {valve_error}")
|
logger.error(f"밸브 그룹핑 실패 - {valve_error}")
|
||||||
# 밸브 그룹핑 실패시에도 계속 진행
|
# 밸브 그룹핑 실패시에도 계속 진행
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 볼트 그룹별로 대표 볼트 하나만 추가 (그룹핑된 정보로)
|
# 볼트 그룹별로 대표 볼트 하나만 추가 (그룹핑된 정보로)
|
||||||
print(f"DEBUG: bolt_groups 수: {len(bolt_groups)}")
|
logger.info(f"bolt_groups 수: {len(bolt_groups)}")
|
||||||
try:
|
try:
|
||||||
for bolt_key, group_info in bolt_groups.items():
|
for bolt_key, group_info in bolt_groups.items():
|
||||||
if group_info["materials"]:
|
if group_info["materials"]:
|
||||||
@@ -1458,14 +1458,14 @@ async def get_materials(
|
|||||||
if 'bolt_details' in representative_bolt:
|
if 'bolt_details' in representative_bolt:
|
||||||
representative_bolt['bolt_details']['group_total_quantity'] = group_info["total_quantity"]
|
representative_bolt['bolt_details']['group_total_quantity'] = group_info["total_quantity"]
|
||||||
material_list.append(representative_bolt)
|
material_list.append(representative_bolt)
|
||||||
print(f"DEBUG: 볼트 추가됨 - {bolt_key}, 수량: {group_info['total_quantity']}")
|
logger.info(f"볼트 추가됨 - {bolt_key}, 수량: {group_info['total_quantity']}")
|
||||||
except Exception as bolt_error:
|
except Exception as bolt_error:
|
||||||
print(f"ERROR: 볼트 그룹핑 실패 - {bolt_error}")
|
logger.error(f"볼트 그룹핑 실패 - {bolt_error}")
|
||||||
# 볼트 그룹핑 실패시에도 계속 진행
|
# 볼트 그룹핑 실패시에도 계속 진행
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 가스켓 그룹별로 대표 가스켓 하나만 추가 (그룹핑된 정보로)
|
# 가스켓 그룹별로 대표 가스켓 하나만 추가 (그룹핑된 정보로)
|
||||||
print(f"DEBUG: gasket_groups 수: {len(gasket_groups)}")
|
logger.info(f"gasket_groups 수: {len(gasket_groups)}")
|
||||||
try:
|
try:
|
||||||
for gasket_key, group_info in gasket_groups.items():
|
for gasket_key, group_info in gasket_groups.items():
|
||||||
if group_info["materials"]:
|
if group_info["materials"]:
|
||||||
@@ -1475,23 +1475,23 @@ async def get_materials(
|
|||||||
if 'gasket_details' in representative_gasket:
|
if 'gasket_details' in representative_gasket:
|
||||||
representative_gasket['gasket_details']['group_total_quantity'] = group_info["total_quantity"]
|
representative_gasket['gasket_details']['group_total_quantity'] = group_info["total_quantity"]
|
||||||
material_list.append(representative_gasket)
|
material_list.append(representative_gasket)
|
||||||
print(f"DEBUG: 가스켓 추가됨 - {gasket_key}, 수량: {group_info['total_quantity']}")
|
logger.info(f"가스켓 추가됨 - {gasket_key}, 수량: {group_info['total_quantity']}")
|
||||||
except Exception as gasket_error:
|
except Exception as gasket_error:
|
||||||
print(f"ERROR: 가스켓 그룹핑 실패 - {gasket_error}")
|
logger.error(f"가스켓 그룹핑 실패 - {gasket_error}")
|
||||||
# 가스켓 그룹핑 실패시에도 계속 진행
|
# 가스켓 그룹핑 실패시에도 계속 진행
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# UNKNOWN 그룹별로 대표 항목 하나만 추가 (그룹핑된 정보로)
|
# UNKNOWN 그룹별로 대표 항목 하나만 추가 (그룹핑된 정보로)
|
||||||
print(f"DEBUG: unknown_groups 수: {len(unknown_groups)}")
|
logger.info(f"unknown_groups 수: {len(unknown_groups)}")
|
||||||
try:
|
try:
|
||||||
for unknown_key, group_info in unknown_groups.items():
|
for unknown_key, group_info in unknown_groups.items():
|
||||||
if group_info["materials"]:
|
if group_info["materials"]:
|
||||||
representative_unknown = group_info["materials"][0].copy()
|
representative_unknown = group_info["materials"][0].copy()
|
||||||
representative_unknown['quantity'] = group_info["total_quantity"]
|
representative_unknown['quantity'] = group_info["total_quantity"]
|
||||||
material_list.append(representative_unknown)
|
material_list.append(representative_unknown)
|
||||||
print(f"DEBUG: UNKNOWN 추가됨 - {unknown_key[:50]}, 수량: {group_info['total_quantity']}")
|
logger.info(f"UNKNOWN 추가됨 - {unknown_key[:50]}, 수량: {group_info['total_quantity']}")
|
||||||
except Exception as unknown_error:
|
except Exception as unknown_error:
|
||||||
print(f"ERROR: UNKNOWN 그룹핑 실패 - {unknown_error}")
|
logger.error(f"UNKNOWN 그룹핑 실패 - {unknown_error}")
|
||||||
# UNKNOWN 그룹핑 실패시에도 계속 진행
|
# UNKNOWN 그룹핑 실패시에도 계속 진행
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -2215,7 +2215,7 @@ async def verify_material_classification(
|
|||||||
f"자재 분류 검증: {material.original_description}"
|
f"자재 분류 검증: {material.original_description}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"활동 로그 기록 실패: {str(e)}")
|
logger.error(f"활동 로그 기록 실패: {str(e)}")
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
@@ -2299,7 +2299,7 @@ async def update_material_classification(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
print(f"자재 분류 업데이트 실패: {str(e)}")
|
logger.error(f"자재 분류 업데이트 실패: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"자재 분류 업데이트 실패: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"자재 분류 업데이트 실패: {str(e)}")
|
||||||
|
|
||||||
@router.post("/materials/confirm-purchase")
|
@router.post("/materials/confirm-purchase")
|
||||||
@@ -2412,7 +2412,7 @@ async def confirm_material_purchase_api(
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
print(f"구매수량 확정 실패: {str(e)}")
|
logger.error(f"구매수량 확정 실패: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"구매수량 확정 실패: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"구매수량 확정 실패: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@@ -2918,7 +2918,7 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
|
|||||||
previous_materials = previous_result.fetchall()
|
previous_materials = previous_result.fetchall()
|
||||||
|
|
||||||
if not previous_materials:
|
if not previous_materials:
|
||||||
print("📝 이전 자료가 없음 - 전체 자재 분류")
|
logger.info("이전 자료가 없음 - 전체 자재 분류")
|
||||||
return {
|
return {
|
||||||
"has_purchased_materials": False,
|
"has_purchased_materials": False,
|
||||||
"message": "이전 자료가 없습니다.",
|
"message": "이전 자료가 없습니다.",
|
||||||
@@ -2985,18 +2985,18 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
|
|||||||
new_count = 0
|
new_count = 0
|
||||||
excluded_purchased_count = 0
|
excluded_purchased_count = 0
|
||||||
|
|
||||||
print(f"\n{'='*60}")
|
logger.info(f"\n{'='*60}")
|
||||||
print(f"🔍 리비전 비교 시작")
|
logger.info(f"리비전 비교 시작")
|
||||||
print(f"{'='*60}")
|
logger.info(f"{'='*60}")
|
||||||
print(f"📊 이전 자재: {len(previous_dict)}개 (구매확정: {len(purchased_dict)}개, 미구매: {len(unpurchased_dict)}개)")
|
logger.info(f"이전 자재: {len(previous_dict)}개 (구매확정: {len(purchased_dict)}개, 미구매: {len(unpurchased_dict)}개)")
|
||||||
print(f"📊 신규 자재: {len(new_dict)}개")
|
logger.info(f"신규 자재: {len(new_dict)}개")
|
||||||
|
|
||||||
# 4-1. 신규 자재 확인 (구매확정된 자재는 제외)
|
# 4-1. 신규 자재 확인 (구매확정된 자재는 제외)
|
||||||
for key, new_material in new_dict.items():
|
for key, new_material in new_dict.items():
|
||||||
if key in purchased_dict:
|
if key in purchased_dict:
|
||||||
# ✅ 구매확정된 자재는 완전히 제외
|
# ✅ 구매확정된 자재는 완전히 제외
|
||||||
excluded_purchased_count += 1
|
excluded_purchased_count += 1
|
||||||
print(f"🚫 구매확정 자재 제외: {new_material.get('original_description', '')[:50]}...")
|
logger.info(f"구매확정 자재 제외: {new_material.get('original_description', '')[:50]}...")
|
||||||
elif key in unpurchased_dict:
|
elif key in unpurchased_dict:
|
||||||
# 미구매 자재인데 새 리비전에도 있음
|
# 미구매 자재인데 새 리비전에도 있음
|
||||||
previous_material = unpurchased_dict[key]
|
previous_material = unpurchased_dict[key]
|
||||||
@@ -3014,20 +3014,20 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
|
|||||||
|
|
||||||
if prev_pipes_needed != new_pipes_needed:
|
if prev_pipes_needed != new_pipes_needed:
|
||||||
# 필요한 본수가 변경됨 - 분류 필요
|
# 필요한 본수가 변경됨 - 분류 필요
|
||||||
print(f"🔧 PIPE 본수 변경: {new_material.get('original_description', '')[:40]}... "
|
logger.info(f"PIPE 본수 변경: {new_material.get('original_description', '')[:40]}... "
|
||||||
f"{prev_qty}mm({prev_pipes_needed}본) → {new_qty}mm({new_pipes_needed}본)")
|
f"{prev_qty}mm({prev_pipes_needed}본) -> {new_qty}mm({new_pipes_needed}본)")
|
||||||
materials_to_classify.append(new_material)
|
materials_to_classify.append(new_material)
|
||||||
new_count += 1
|
new_count += 1
|
||||||
else:
|
else:
|
||||||
# 필요한 본수 동일 - 기존 분류 유지
|
# 필요한 본수 동일 - 기존 분류 유지
|
||||||
print(f"♻️ PIPE 본수 유지: {new_material.get('original_description', '')[:40]}... "
|
logger.info(f"PIPE 본수 유지: {new_material.get('original_description', '')[:40]}... "
|
||||||
f"{prev_qty}mm → {new_qty}mm ({new_pipes_needed}본)")
|
f"{prev_qty}mm -> {new_qty}mm ({new_pipes_needed}본)")
|
||||||
else:
|
else:
|
||||||
# 기타 자재: 기존 분류 유지
|
# 기타 자재: 기존 분류 유지
|
||||||
print(f"♻️ 기존 미구매 자재 유지: {new_material.get('original_description', '')[:50]}...")
|
logger.info(f"기존 미구매 자재 유지: {new_material.get('original_description', '')[:50]}...")
|
||||||
else:
|
else:
|
||||||
# ✅ 완전히 새로운 자재 - 분류 필요
|
# ✅ 완전히 새로운 자재 - 분류 필요
|
||||||
print(f"➕ 신규 자재: {new_material.get('original_description', '')[:50]}...")
|
logger.info(f"신규 자재: {new_material.get('original_description', '')[:50]}...")
|
||||||
materials_to_classify.append(new_material)
|
materials_to_classify.append(new_material)
|
||||||
new_count += 1
|
new_count += 1
|
||||||
|
|
||||||
@@ -3036,15 +3036,15 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
|
|||||||
if key not in new_dict:
|
if key not in new_dict:
|
||||||
# ✅ 이전에는 있었지만 새 리비전에는 없음
|
# ✅ 이전에는 있었지만 새 리비전에는 없음
|
||||||
removed_materials.append(previous_material)
|
removed_materials.append(previous_material)
|
||||||
print(f"➖ 삭제된 자재: {previous_material.get('original_description', '')[:50]}...")
|
logger.info(f"삭제된 자재: {previous_material.get('original_description', '')[:50]}...")
|
||||||
|
|
||||||
print(f"\n{'='*60}")
|
logger.info(f"\n{'='*60}")
|
||||||
print(f"📊 비교 결과")
|
logger.info(f"비교 결과")
|
||||||
print(f"{'='*60}")
|
logger.info(f"{'='*60}")
|
||||||
print(f"✅ 신규 자재: {new_count}개 (분류 필요)")
|
logger.info(f"신규 자재: {new_count}개 (분류 필요)")
|
||||||
print(f"➖ 삭제 자재: {len(removed_materials)}개")
|
logger.info(f"삭제 자재: {len(removed_materials)}개")
|
||||||
print(f"🚫 구매확정 제외: {excluded_purchased_count}개")
|
logger.info(f"구매확정 제외: {excluded_purchased_count}개")
|
||||||
print(f"{'='*60}\n")
|
logger.info(f"{'='*60}\n")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"has_purchased_materials": len(purchased_dict) > 0,
|
"has_purchased_materials": len(purchased_dict) > 0,
|
||||||
@@ -3061,7 +3061,7 @@ def perform_simple_revision_comparison(db: Session, job_no: str, parent_file_id:
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 리비전 비교 실패: {str(e)}")
|
logger.error(f"리비전 비교 실패: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -99,36 +99,27 @@ def test_fitting_classification():
|
|||||||
print(f"📋 입력:")
|
print(f"📋 입력:")
|
||||||
print(f" DAT_FILE: {test['dat_file']}")
|
print(f" DAT_FILE: {test['dat_file']}")
|
||||||
print(f" DESCRIPTION: {test['description']}")
|
print(f" DESCRIPTION: {test['description']}")
|
||||||
print(f" SIZE: {result['size_info']['size_description']}")
|
print(f" SIZE: {test.get('main_nom', '')}")
|
||||||
|
|
||||||
print(f"\n🔧 분류 결과:")
|
print(f"\n🔧 분류 결과:")
|
||||||
print(f" 재질: {result['material']['standard']} | {result['material']['grade']}")
|
|
||||||
print(f" 피팅타입: {result['fitting_type']['type']} - {result['fitting_type']['subtype']}")
|
print(f" 피팅타입: {result['fitting_type']['type']} - {result['fitting_type']['subtype']}")
|
||||||
print(f" 크기정보: {result['size_info']['size_description']}") # ← 추가!
|
|
||||||
if result['size_info']['reduced_size']:
|
|
||||||
print(f" 주사이즈: {result['size_info']['main_size']}")
|
|
||||||
print(f" 축소사이즈: {result['size_info']['reduced_size']}")
|
|
||||||
print(f" 연결방식: {result['connection_method']['method']}")
|
print(f" 연결방식: {result['connection_method']['method']}")
|
||||||
print(f" 압력등급: {result['pressure_rating']['rating']} ({result['pressure_rating']['common_use']})")
|
print(f" 압력등급: {result['pressure_rating']['rating']}")
|
||||||
print(f" 제작방법: {result['manufacturing']['method']} ({result['manufacturing']['characteristics']})")
|
print(f" 제작방법: {result['manufacturing']}")
|
||||||
|
|
||||||
print(f"\n📊 신뢰도:")
|
print(f"\n📊 신뢰도:")
|
||||||
print(f" 전체신뢰도: {result['overall_confidence']}")
|
print(f" 전체신뢰도: {result['overall_confidence']}")
|
||||||
print(f" 재질: {result['material']['confidence']}")
|
|
||||||
print(f" 피팅타입: {result['fitting_type']['confidence']}")
|
print(f" 피팅타입: {result['fitting_type']['confidence']}")
|
||||||
print(f" 연결방식: {result['connection_method']['confidence']}")
|
print(f" 연결방식: {result['connection_method']['confidence']}")
|
||||||
print(f" 압력등급: {result['pressure_rating']['confidence']}")
|
|
||||||
|
|
||||||
print(f"\n🛒 구매 정보:")
|
print(f"\n🛒 구매 정보:")
|
||||||
print(f" 공급업체: {purchase_info['supplier_type']}")
|
print(f" 공급업체: {purchase_info.get('supplier_type', 'N/A')}")
|
||||||
print(f" 예상납기: {purchase_info['lead_time_estimate']}")
|
print(f" 구매카테고리: {purchase_info.get('purchase_category', 'N/A')}")
|
||||||
print(f" 구매카테고리: {purchase_info['purchase_category']}")
|
|
||||||
|
|
||||||
# 크기 정보 저장 확인
|
# 분류 요약
|
||||||
print(f"\n💾 저장될 데이터:")
|
print(f"\n💾 분류 요약:")
|
||||||
print(f" MAIN_NOM: {result['size_info']['main_size']}")
|
print(f" 카테고리: {result.get('category', 'N/A')}")
|
||||||
print(f" RED_NOM: {result['size_info']['reduced_size'] or 'NULL'}")
|
print(f" 신뢰도: {result.get('overall_confidence', 'N/A')}")
|
||||||
print(f" SIZE_DESCRIPTION: {result['size_info']['size_description']}")
|
|
||||||
|
|
||||||
if i < len(test_cases):
|
if i < len(test_cases):
|
||||||
print("\n" + "=" * 80)
|
print("\n" + "=" * 80)
|
||||||
@@ -174,10 +165,11 @@ def test_fitting_edge_cases():
|
|||||||
test["red_nom"]
|
test["red_nom"]
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"결과: {result['fitting_type']['type']} - {result['fitting_type']['subtype']}")
|
ft = result.get('fitting_type', {})
|
||||||
print(f"크기: {result['size_info']['size_description']}") # ← 추가!
|
print(f"결과: {ft.get('type', 'N/A')} - {ft.get('subtype', 'N/A')}")
|
||||||
print(f"신뢰도: {result['overall_confidence']}")
|
print(f"카테고리: {result.get('category', 'N/A')}")
|
||||||
print(f"증거: {result['fitting_type']['evidence']}")
|
print(f"신뢰도: {result.get('overall_confidence', 'N/A')}")
|
||||||
|
print(f"증거: {ft.get('evidence', [])}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_fitting_classification()
|
test_fitting_classification()
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ def test_pipe_classification():
|
|||||||
print(f" 제조방법: {result['manufacturing']['method']}")
|
print(f" 제조방법: {result['manufacturing']['method']}")
|
||||||
print(f" 끝가공: {result['end_preparation']['cutting_note']}")
|
print(f" 끝가공: {result['end_preparation']['cutting_note']}")
|
||||||
print(f" 스케줄: {result['schedule']['schedule']}")
|
print(f" 스케줄: {result['schedule']['schedule']}")
|
||||||
print(f" 절단치수: {result['cutting_dimensions']['length_mm']}mm")
|
if 'length_info' in result:
|
||||||
|
print(f" 길이정보: {result['length_info']}")
|
||||||
print(f" 전체신뢰도: {result['overall_confidence']}")
|
print(f" 전체신뢰도: {result['overall_confidence']}")
|
||||||
print(f" 절단지시: {cutting_plan['cutting_instruction']}")
|
if cutting_plan:
|
||||||
|
print(f" 절단지시: {cutting_plan.get('cutting_instruction', 'N/A')}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user