feat: PIPE 분석 기능 개선 및 자재 확인 페이지 UX 향상

- 자재 확인 페이지에 뒤로가기 버튼 추가
- 상세 목록 탭에 PIPE 분석 섹션 추가
  - 재질-외경-스케줄-제작방식별로 그룹화
  - 동일 속성 파이프들의 길이 합산 표시
  - 총 파이프 길이 및 규격 종류 수 요약
- 파일 삭제 기능 수정 (외래키 제약 조건 해결)
- MaterialsPage에서 전체 자재 목록 표시 (limit 10000)
- 길이 단위 변환 로직 수정 (mm 단위 유지)
- 파싱 로직에 디버그 출력 추가

TODO: MAIN_NOM/RED_NOM 별도 저장을 위한 스키마 개선 필요
This commit is contained in:
Hyungi Ahn
2025-07-17 15:55:40 +09:00
parent 5f7a6f0b3a
commit 82f057a0c9
8 changed files with 1433 additions and 156 deletions

View File

@@ -497,6 +497,180 @@ async def upload_file(
except Exception as e:
print(f"VALVE 상세정보 저장 실패: {e}")
elif category == 'FLANGE' and confidence >= 0.5:
try:
flange_info = classification_result
flange_insert_query = text("""
INSERT INTO flange_details (
material_id, file_id, flange_type, facing_type,
pressure_rating, material_standard, material_grade,
size_inches, classification_confidence, additional_info
)
VALUES (
(SELECT id FROM materials WHERE file_id = :file_id AND original_description = :description AND row_number = :row_number),
:file_id, :flange_type, :facing_type,
:pressure_rating, :material_standard, :material_grade,
:size_inches, :classification_confidence, :additional_info
)
""")
db.execute(flange_insert_query, {
"file_id": file_id,
"description": material_data["original_description"],
"row_number": material_data["row_number"],
"flange_type": flange_info.get('flange_type', {}).get('type', ''),
"facing_type": flange_info.get('face_finish', {}).get('finish', ''),
"pressure_rating": flange_info.get('pressure_rating', {}).get('rating', ''),
"material_standard": flange_info.get('material', {}).get('standard', ''),
"material_grade": flange_info.get('material', {}).get('grade', ''),
"size_inches": material_data.get('size_spec', ''),
"classification_confidence": confidence,
"additional_info": json.dumps(flange_info, ensure_ascii=False)
})
print(f"FLANGE 상세정보 저장 완료: {material_data['original_description']}")
except Exception as e:
print(f"FLANGE 상세정보 저장 실패: {e}")
elif category == 'BOLT' and confidence >= 0.5:
try:
bolt_info = classification_result
bolt_insert_query = text("""
INSERT INTO bolt_details (
material_id, file_id, bolt_type, thread_type,
diameter, length, material_standard, material_grade,
coating_type, includes_nut, includes_washer,
classification_confidence, additional_info
)
VALUES (
(SELECT id FROM materials WHERE file_id = :file_id AND original_description = :description AND row_number = :row_number),
:file_id, :bolt_type, :thread_type,
:diameter, :length, :material_standard, :material_grade,
:coating_type, :includes_nut, :includes_washer,
:classification_confidence, :additional_info
)
""")
# BOLT 분류기 결과 구조에 맞게 데이터 추출
bolt_details = bolt_info.get('bolt_details', {})
material_info = bolt_info.get('material', {})
db.execute(bolt_insert_query, {
"file_id": file_id,
"description": material_data["original_description"],
"row_number": material_data["row_number"],
"bolt_type": bolt_details.get('type', ''),
"thread_type": bolt_details.get('thread_type', ''),
"diameter": bolt_details.get('diameter', ''),
"length": bolt_details.get('length', ''),
"material_standard": material_info.get('standard', ''),
"material_grade": material_info.get('grade', ''),
"coating_type": material_info.get('coating', ''),
"includes_nut": bolt_details.get('includes_nut', False),
"includes_washer": bolt_details.get('includes_washer', False),
"classification_confidence": confidence,
"additional_info": json.dumps(bolt_info, ensure_ascii=False)
})
print(f"BOLT 상세정보 저장 완료: {material_data['original_description']}")
except Exception as e:
print(f"BOLT 상세정보 저장 실패: {e}")
elif category == 'GASKET' and confidence >= 0.5:
try:
gasket_info = classification_result
gasket_insert_query = text("""
INSERT INTO gasket_details (
material_id, file_id, gasket_type, gasket_subtype,
material_type, size_inches, pressure_rating,
thickness, temperature_range, fire_safe,
classification_confidence, additional_info
)
VALUES (
(SELECT id FROM materials WHERE file_id = :file_id AND original_description = :description AND row_number = :row_number),
:file_id, :gasket_type, :gasket_subtype,
:material_type, :size_inches, :pressure_rating,
:thickness, :temperature_range, :fire_safe,
:classification_confidence, :additional_info
)
""")
# GASKET 분류기 결과 구조에 맞게 데이터 추출
gasket_type_info = gasket_info.get('gasket_type', {})
material_info = gasket_info.get('material', {})
db.execute(gasket_insert_query, {
"file_id": file_id,
"description": material_data["original_description"],
"row_number": material_data["row_number"],
"gasket_type": gasket_type_info.get('type', ''),
"gasket_subtype": gasket_type_info.get('subtype', ''),
"material_type": material_info.get('type', ''),
"size_inches": material_data.get('size_spec', ''),
"pressure_rating": gasket_info.get('pressure_rating', ''),
"thickness": gasket_info.get('thickness', ''),
"temperature_range": material_info.get('temperature_range', ''),
"fire_safe": gasket_info.get('fire_safe', False),
"classification_confidence": confidence,
"additional_info": json.dumps(gasket_info, ensure_ascii=False)
})
print(f"GASKET 상세정보 저장 완료: {material_data['original_description']}")
except Exception as e:
print(f"GASKET 상세정보 저장 실패: {e}")
elif category == 'INSTRUMENT' and confidence >= 0.5:
try:
inst_info = classification_result
inst_insert_query = text("""
INSERT INTO instrument_details (
material_id, file_id, instrument_type, instrument_subtype,
measurement_type, measurement_range, accuracy,
connection_type, connection_size, body_material,
classification_confidence, additional_info
)
VALUES (
(SELECT id FROM materials WHERE file_id = :file_id AND original_description = :description AND row_number = :row_number),
:file_id, :instrument_type, :instrument_subtype,
:measurement_type, :measurement_range, :accuracy,
:connection_type, :connection_size, :body_material,
:classification_confidence, :additional_info
)
""")
# INSTRUMENT 분류기 결과 구조에 맞게 데이터 추출
inst_type_info = inst_info.get('instrument_type', {})
measurement_info = inst_info.get('measurement', {})
connection_info = inst_info.get('connection', {})
db.execute(inst_insert_query, {
"file_id": file_id,
"description": material_data["original_description"],
"row_number": material_data["row_number"],
"instrument_type": inst_type_info.get('type', ''),
"instrument_subtype": inst_type_info.get('subtype', ''),
"measurement_type": measurement_info.get('type', ''),
"measurement_range": measurement_info.get('range', ''),
"accuracy": measurement_info.get('accuracy', ''),
"connection_type": connection_info.get('type', ''),
"connection_size": connection_info.get('size', ''),
"body_material": inst_info.get('material', ''),
"classification_confidence": confidence,
"additional_info": json.dumps(inst_info, ensure_ascii=False)
})
print(f"INSTRUMENT 상세정보 저장 완료: {material_data['original_description']}")
except Exception as e:
print(f"INSTRUMENT 상세정보 저장 실패: {e}")
materials_inserted += 1
db.commit()

View File

@@ -87,7 +87,7 @@ async def get_files(
"original_filename": f.original_filename,
"name": f.original_filename,
"job_no": f.job_no, # job_no 사용
"bom_name": f.original_filename, # 파일명을 BOM 이름으로 사용
"bom_name": f.bom_name or f.original_filename, # 실제 bom_name 값 사용, 없으면 파일명
"bom_type": f.file_type or "unknown", # file_type을 BOM 종류로 사용
"status": "active" if f.is_active else "inactive", # is_active 상태
"file_size": f.file_size,
@@ -118,7 +118,26 @@ async def delete_file(
if not file:
return {"error": "파일을 찾을 수 없습니다"}
# 관련 자재 데이터 삭제
# 먼저 상세 테이블의 데이터 삭제 (외래 키 제약 조건 때문)
# 각 자재 타입별 상세 테이블 데이터 삭제
detail_tables = [
'pipe_details', 'fitting_details', 'valve_details',
'flange_details', 'bolt_details', 'gasket_details',
'instrument_details'
]
# 해당 파일의 materials ID 조회
material_ids_query = text("SELECT id FROM materials WHERE file_id = :file_id")
material_ids_result = db.execute(material_ids_query, {"file_id": file_id})
material_ids = [row[0] for row in material_ids_result]
if material_ids:
# 각 상세 테이블에서 관련 데이터 삭제
for table in detail_tables:
delete_detail_query = text(f"DELETE FROM {table} WHERE material_id = ANY(:material_ids)")
db.execute(delete_detail_query, {"material_ids": material_ids})
# materials 테이블 데이터 삭제
materials_query = text("DELETE FROM materials WHERE file_id = :file_id")
db.execute(materials_query, {"file_id": file_id})
@@ -267,17 +286,17 @@ async def upload_file(
file_type = "excel" if file.filename.endswith(('.xls', '.xlsx')) else "csv" if file.filename.endswith('.csv') else "unknown"
# BOM 종류별 자동 리비전 관리
if bom_type and not parent_bom_id:
# 같은 job_no의 같은 파일명에 대한 최신 리비전 조회
if bom_name and not parent_bom_id:
# 같은 job_no의 같은 BOM 이름에 대한 최신 리비전 조회
latest_revision_query = text("""
SELECT revision FROM files
WHERE job_no = :job_no AND original_filename = :filename
WHERE job_no = :job_no AND bom_name = :bom_name
ORDER BY revision DESC LIMIT 1
""")
result = db.execute(latest_revision_query, {
"job_no": job_no,
"filename": file.filename
"bom_name": bom_name
})
latest_file = result.fetchone()
@@ -300,10 +319,10 @@ async def upload_file(
insert_query = text("""
INSERT INTO files (
job_no, filename, original_filename, file_path,
file_size, upload_date, revision, file_type, uploaded_by
file_size, upload_date, revision, file_type, uploaded_by, bom_name
) VALUES (
:job_no, :filename, :original_filename, :file_path,
:file_size, NOW(), :revision, :file_type, :uploaded_by
:file_size, NOW(), :revision, :file_type, :uploaded_by, :bom_name
) RETURNING id
""")
@@ -315,7 +334,8 @@ async def upload_file(
"file_size": file_size,
"revision": revision,
"file_type": file_type,
"uploaded_by": "system"
"uploaded_by": "system",
"bom_name": bom_name
})
file_id = result.fetchone()[0]
@@ -372,9 +392,10 @@ async def upload_file(
:quantity, :unit, :drawing_name, :area_code, :line_no,
:classification_confidence, :is_verified, NOW()
)
RETURNING id
""")
db.execute(insert_material_query, {
result = db.execute(insert_material_query, {
"file_id": file_id,
"line_number": material.get("line_number", 0),
"original_description": material.get("original_description", ""),
@@ -391,6 +412,191 @@ async def upload_file(
"classification_confidence": material.get("classification_confidence", 0.0),
"is_verified": False
})
# 저장된 material의 ID 가져오기
material_id = result.fetchone()[0]
# 카테고리별 상세 정보 저장
category = material.get("classified_category", "")
if category == "PIPE" and "pipe_details" in material:
pipe_details = material["pipe_details"]
pipe_insert_query = text("""
INSERT INTO pipe_details (
material_id, file_id, nominal_size, schedule,
material_standard, material_grade, material_type,
manufacturing_method, length_mm
) VALUES (
:material_id, :file_id, :nominal_size, :schedule,
:material_standard, :material_grade, :material_type,
:manufacturing_method, :length_mm
)
""")
db.execute(pipe_insert_query, {
"material_id": material_id,
"file_id": file_id,
"nominal_size": material.get("size_spec", ""),
"schedule": pipe_details.get("schedule", material.get("schedule", "")),
"material_standard": pipe_details.get("material_spec", material.get("material_grade", "")),
"material_grade": material.get("material_grade", ""),
"material_type": material.get("material_grade", "").split("-")[0] if material.get("material_grade", "") else "",
"manufacturing_method": pipe_details.get("manufacturing_method", ""),
"length_mm": material.get("length", 0.0) if material.get("length", 0.0) else 0.0 # 이미 mm 단위임
})
elif category == "FITTING" and "fitting_details" in material:
fitting_details = material["fitting_details"]
fitting_insert_query = text("""
INSERT INTO fitting_details (
material_id, file_id, fitting_type, fitting_subtype,
connection_method, pressure_rating, material_standard,
material_grade, main_size, reduced_size
) VALUES (
:material_id, :file_id, :fitting_type, :fitting_subtype,
:connection_method, :pressure_rating, :material_standard,
:material_grade, :main_size, :reduced_size
)
""")
db.execute(fitting_insert_query, {
"material_id": material_id,
"file_id": file_id,
"fitting_type": fitting_details.get("fitting_type", ""),
"fitting_subtype": fitting_details.get("fitting_subtype", ""),
"connection_method": fitting_details.get("connection_method", ""),
"pressure_rating": fitting_details.get("pressure_rating", ""),
"material_standard": fitting_details.get("material_standard", material.get("material_grade", "")),
"material_grade": fitting_details.get("material_grade", material.get("material_grade", "")),
"main_size": material.get("size_spec", ""),
"reduced_size": fitting_details.get("reduced_size", "")
})
elif category == "VALVE" and "valve_details" in material:
valve_details = material["valve_details"]
valve_insert_query = text("""
INSERT INTO valve_details (
material_id, file_id, valve_type, valve_subtype,
actuator_type, connection_method, pressure_rating,
body_material, size_inches
) VALUES (
:material_id, :file_id, :valve_type, :valve_subtype,
:actuator_type, :connection_method, :pressure_rating,
:body_material, :size_inches
)
""")
db.execute(valve_insert_query, {
"material_id": material_id,
"file_id": file_id,
"valve_type": valve_details.get("valve_type", ""),
"valve_subtype": valve_details.get("valve_subtype", ""),
"actuator_type": valve_details.get("actuator_type", "MANUAL"),
"connection_method": valve_details.get("connection_method", ""),
"pressure_rating": valve_details.get("pressure_rating", ""),
"body_material": material.get("material_grade", ""),
"size_inches": material.get("size_spec", "")
})
elif category == "FLANGE" and "flange_details" in material:
flange_details = material["flange_details"]
flange_insert_query = text("""
INSERT INTO flange_details (
material_id, file_id, flange_type, flange_subtype,
facing_type, pressure_rating, material_standard,
material_grade, size_inches
) VALUES (
:material_id, :file_id, :flange_type, :flange_subtype,
:facing_type, :pressure_rating, :material_standard,
:material_grade, :size_inches
)
""")
db.execute(flange_insert_query, {
"material_id": material_id,
"file_id": file_id,
"flange_type": flange_details.get("flange_type", ""),
"flange_subtype": flange_details.get("flange_subtype", ""),
"facing_type": flange_details.get("facing_type", ""),
"pressure_rating": flange_details.get("pressure_rating", ""),
"material_standard": material.get("material_grade", ""),
"material_grade": material.get("material_grade", ""),
"size_inches": material.get("size_spec", "")
})
elif category == "BOLT" and "bolt_details" in material:
bolt_details = material["bolt_details"]
bolt_insert_query = text("""
INSERT INTO bolt_details (
material_id, file_id, bolt_type, bolt_subtype,
thread_standard, diameter, length, thread_pitch,
material_standard, material_grade, coating
) VALUES (
:material_id, :file_id, :bolt_type, :bolt_subtype,
:thread_standard, :diameter, :length, :thread_pitch,
:material_standard, :material_grade, :coating
)
""")
db.execute(bolt_insert_query, {
"material_id": material_id,
"file_id": file_id,
"bolt_type": bolt_details.get("bolt_type", ""),
"bolt_subtype": bolt_details.get("bolt_subtype", ""),
"thread_standard": bolt_details.get("thread_standard", ""),
"diameter": material.get("size_spec", ""),
"length": bolt_details.get("length", ""),
"thread_pitch": bolt_details.get("thread_pitch", ""),
"material_standard": material.get("material_grade", ""),
"material_grade": material.get("material_grade", ""),
"coating": bolt_details.get("coating", "")
})
elif category == "GASKET" and "gasket_details" in material:
gasket_details = material["gasket_details"]
gasket_insert_query = text("""
INSERT INTO gasket_details (
material_id, file_id, gasket_type, gasket_material,
flange_size, pressure_rating, temperature_range,
thickness, inner_diameter, outer_diameter
) VALUES (
:material_id, :file_id, :gasket_type, :gasket_material,
:flange_size, :pressure_rating, :temperature_range,
:thickness, :inner_diameter, :outer_diameter
)
""")
db.execute(gasket_insert_query, {
"material_id": material_id,
"file_id": file_id,
"gasket_type": gasket_details.get("gasket_type", ""),
"gasket_material": gasket_details.get("gasket_material", ""),
"flange_size": material.get("size_spec", ""),
"pressure_rating": gasket_details.get("pressure_rating", ""),
"temperature_range": gasket_details.get("temperature_range", ""),
"thickness": gasket_details.get("thickness", ""),
"inner_diameter": gasket_details.get("inner_diameter", ""),
"outer_diameter": gasket_details.get("outer_diameter", "")
})
elif category == "INSTRUMENT" and "instrument_details" in material:
instrument_details = material["instrument_details"]
instrument_insert_query = text("""
INSERT INTO instrument_details (
material_id, file_id, instrument_type, measurement_type,
measurement_range, output_signal, connection_size,
process_connection, accuracy_class
) VALUES (
:material_id, :file_id, :instrument_type, :measurement_type,
:measurement_range, :output_signal, :connection_size,
:process_connection, :accuracy_class
)
""")
db.execute(instrument_insert_query, {
"material_id": material_id,
"file_id": file_id,
"instrument_type": instrument_details.get("instrument_type", ""),
"measurement_type": instrument_details.get("measurement_type", ""),
"measurement_range": instrument_details.get("measurement_range", ""),
"output_signal": instrument_details.get("output_signal", ""),
"connection_size": material.get("size_spec", ""),
"process_connection": instrument_details.get("process_connection", ""),
"accuracy_class": instrument_details.get("accuracy_class", "")
})
db.commit()
@@ -442,6 +648,7 @@ def parse_file(file_path: str) -> List[Dict]:
'quantity': ['QTY', 'Quantity', 'quantity', 'QTY.', 'Qty', 'qty', 'AMOUNT', 'Amount', 'amount'],
'length': ['LENGTH', 'Length', 'length', 'LG', 'Lg', 'lg', 'LENGTH_MM', 'Length_mm', 'length_mm'],
'unit': ['UNIT', 'Unit', 'unit', 'UOM', 'Uom', 'uom'],
'size': ['SIZE', 'Size', 'size', 'NOM_SIZE', 'Nom_Size', 'nom_size', 'MAIN_NOM', 'Main_Nom', 'main_nom'],
'drawing': ['DRAWING', 'Drawing', 'drawing', 'DWG', 'Dwg', 'dwg'],
'area': ['AREA', 'Area', 'area', 'AREA_CODE', 'Area_Code', 'area_code'],
'line': ['LINE', 'Line', 'line', 'LINE_NO', 'Line_No', 'line_no', 'PIPELINE', 'Pipeline', 'pipeline']
@@ -470,6 +677,7 @@ def parse_file(file_path: str) -> List[Dict]:
length_raw = row.get(found_columns.get('length', 0), 0)
length = float(length_raw) if length_raw is not None else 0.0
unit = str(row.get(found_columns.get('unit', 'EA'), 'EA') or 'EA')
size = str(row.get(found_columns.get('size', ''), '') or '')
drawing = str(row.get(found_columns.get('drawing', ''), '') or '')
area = str(row.get(found_columns.get('area', ''), '') or '')
line = str(row.get(found_columns.get('line', ''), '') or '')
@@ -480,6 +688,7 @@ def parse_file(file_path: str) -> List[Dict]:
"quantity": quantity,
"length": length,
"unit": unit,
"size_spec": size,
"drawing_name": drawing,
"area_code": area,
"line_no": line
@@ -590,6 +799,92 @@ def classify_material_item(material: Dict) -> Dict:
"length": length # 길이 정보 추가
}
# 카테고리별 상세 정보 추가
category = classification_result.get("category", "")
if category == "PIPE":
# PIPE 상세 정보 추출
final_result["pipe_details"] = {
"size_inches": size_spec,
"schedule": classification_result.get("schedule", {}).get("schedule", ""),
"material_spec": classification_result.get("material", {}).get("standard", ""),
"manufacturing_method": classification_result.get("manufacturing", {}).get("method", ""),
"length_mm": length * 1000 if length else 0, # meter to mm
"outer_diameter_mm": 0.0, # 추후 계산
"wall_thickness_mm": 0.0, # 추후 계산
"weight_per_meter_kg": 0.0 # 추후 계산
}
elif category == "FITTING":
# FITTING 상세 정보 추출
final_result["fitting_details"] = {
"fitting_type": classification_result.get("fitting_type", {}).get("type", ""),
"fitting_subtype": classification_result.get("fitting_type", {}).get("subtype", ""),
"connection_method": classification_result.get("connection_method", {}).get("method", ""),
"pressure_rating": classification_result.get("pressure_rating", {}).get("rating", ""),
"material_standard": classification_result.get("material", {}).get("standard", ""),
"material_grade": classification_result.get("material", {}).get("grade", ""),
"main_size": size_spec,
"reduced_size": ""
}
elif category == "VALVE":
# VALVE 상세 정보 추출
final_result["valve_details"] = {
"valve_type": classification_result.get("valve_type", {}).get("type", ""),
"valve_subtype": classification_result.get("valve_type", {}).get("subtype", ""),
"actuator_type": classification_result.get("actuation", {}).get("method", "MANUAL"),
"connection_method": classification_result.get("connection_method", {}).get("method", ""),
"pressure_rating": classification_result.get("pressure_rating", {}).get("rating", ""),
"body_material": classification_result.get("material", {}).get("grade", ""),
"size_inches": size_spec
}
elif category == "FLANGE":
# FLANGE 상세 정보 추출
final_result["flange_details"] = {
"flange_type": classification_result.get("flange_type", {}).get("type", ""),
"flange_subtype": classification_result.get("flange_type", {}).get("subtype", ""),
"facing_type": classification_result.get("face_finish", {}).get("finish", ""),
"pressure_rating": classification_result.get("pressure_rating", {}).get("rating", ""),
"material_standard": classification_result.get("material", {}).get("standard", ""),
"material_grade": classification_result.get("material", {}).get("grade", ""),
"size_inches": size_spec
}
elif category == "BOLT":
# BOLT 상세 정보 추출
final_result["bolt_details"] = {
"bolt_type": classification_result.get("fastener_type", {}).get("type", ""),
"bolt_subtype": classification_result.get("fastener_type", {}).get("subtype", ""),
"thread_standard": classification_result.get("thread_specification", {}).get("standard", ""),
"diameter": classification_result.get("dimensions", {}).get("diameter", size_spec),
"length": classification_result.get("dimensions", {}).get("length", ""),
"thread_pitch": classification_result.get("thread_specification", {}).get("pitch", ""),
"material_standard": classification_result.get("material", {}).get("standard", ""),
"material_grade": classification_result.get("material", {}).get("grade", ""),
"coating": ""
}
elif category == "GASKET":
# GASKET 상세 정보 추출
final_result["gasket_details"] = {
"gasket_type": classification_result.get("gasket_type", {}).get("type", ""),
"gasket_material": classification_result.get("gasket_material", {}).get("material", ""),
"flange_size": size_spec,
"pressure_rating": classification_result.get("pressure_rating", {}).get("rating", ""),
"temperature_range": classification_result.get("gasket_material", {}).get("temperature_range", ""),
"thickness": classification_result.get("size_info", {}).get("thickness", ""),
"inner_diameter": classification_result.get("size_info", {}).get("inner_diameter", ""),
"outer_diameter": classification_result.get("size_info", {}).get("outer_diameter", "")
}
elif category == "INSTRUMENT":
# INSTRUMENT 상세 정보 추출
final_result["instrument_details"] = {
"instrument_type": classification_result.get("instrument_type", {}).get("type", ""),
"measurement_type": "",
"measurement_range": classification_result.get("measurement_info", {}).get("range", ""),
"output_signal": classification_result.get("measurement_info", {}).get("signal_type", ""),
"connection_size": size_spec,
"process_connection": "",
"accuracy_class": ""
}
return final_result
@app.get("/health")

View File

@@ -67,7 +67,10 @@ def generate_unique_filename(original_filename: str) -> str:
def parse_dataframe(df):
df = df.dropna(how='all')
# 원본 컬럼명 출력
print(f"원본 컬럼들: {list(df.columns)}")
df.columns = df.columns.str.strip().str.lower()
print(f"소문자 변환 후: {list(df.columns)}")
column_mapping = {
'description': ['description', 'item', 'material', '품명', '자재명'],
@@ -87,6 +90,8 @@ def parse_dataframe(df):
mapped_columns[standard_col] = possible_name
break
print(f"찾은 컬럼 매핑: {mapped_columns}")
materials = []
for index, row in df.iterrows():
description = str(row.get(mapped_columns.get('description', ''), ''))
@@ -269,6 +274,13 @@ async def upload_file(
RETURNING id
""")
# 첫 번째 자재에 대해서만 디버그 출력
if materials_inserted == 0:
print(f"첫 번째 자재 저장:")
print(f" size_spec: '{material_data['size_spec']}'")
print(f" original_description: {material_data['original_description']}")
print(f" category: {classification_result.get('category', 'UNKNOWN')}")
material_result = db.execute(material_insert_query, {
"file_id": file_id,
"original_description": material_data["original_description"],
@@ -291,20 +303,19 @@ async def upload_file(
if classification_result.get("category") == "PIPE":
print("PIPE 상세 정보 저장 시작")
# 길이 정보 추출
length_mm = None
if "length_info" in classification_result:
length_mm = classification_result["length_info"].get("length_mm")
# 길이 정보 추출 - material_data에서 직접 가져옴
length_mm = material_data.get("length", 0.0) if material_data.get("length") else None
# material_id도 함께 저장하도록 수정
pipe_detail_insert_query = text("""
INSERT INTO pipe_details (
file_id, material_standard, material_grade, material_type,
material_id, file_id, material_standard, material_grade, material_type,
manufacturing_method, end_preparation, schedule, wall_thickness,
nominal_size, length_mm, material_confidence, manufacturing_confidence,
end_prep_confidence, schedule_confidence
)
VALUES (
:file_id, :material_standard, :material_grade, :material_type,
:material_id, :file_id, :material_standard, :material_grade, :material_type,
:manufacturing_method, :end_preparation, :schedule, :wall_thickness,
:nominal_size, :length_mm, :material_confidence, :manufacturing_confidence,
:end_prep_confidence, :schedule_confidence
@@ -319,6 +330,7 @@ async def upload_file(
size_info = classification_result.get("size_info", {})
db.execute(pipe_detail_insert_query, {
"material_id": material_id,
"file_id": file_id,
"material_standard": material_info.get("standard"),
"material_grade": material_info.get("grade"),
@@ -327,7 +339,7 @@ async def upload_file(
"end_preparation": end_prep_info.get("type"),
"schedule": schedule_info.get("schedule"),
"wall_thickness": schedule_info.get("wall_thickness"),
"nominal_size": size_info.get("nominal_size"),
"nominal_size": material_data.get("size_spec", ""), # material_data에서 직접 가져옴
"length_mm": length_mm,
"material_confidence": material_info.get("confidence", 0.0),
"manufacturing_confidence": manufacturing_info.get("confidence", 0.0),
@@ -447,6 +459,7 @@ async def get_materials(
SELECT m.id, m.file_id, m.original_description, m.quantity, m.unit,
m.size_spec, m.material_grade, m.line_number, m.row_number,
m.created_at, m.classified_category, m.classification_confidence,
m.classification_details,
f.original_filename, f.project_id, f.job_no, f.revision,
p.official_project_code, p.project_name
FROM materials m
@@ -552,33 +565,85 @@ async def get_materials(
count_result = db.execute(text(count_query), count_params)
total_count = count_result.fetchone()[0]
# 각 자재의 상세 정보도 가져오기
material_list = []
for m in materials:
material_dict = {
"id": m.id,
"file_id": m.file_id,
"filename": m.original_filename,
"project_id": m.project_id,
"project_code": m.official_project_code,
"project_name": m.project_name,
"original_description": m.original_description,
"quantity": float(m.quantity) if m.quantity else 0,
"unit": m.unit,
"size_spec": m.size_spec,
"material_grade": m.material_grade,
"line_number": m.line_number,
"row_number": m.row_number,
"classified_category": m.classified_category,
"classification_confidence": float(m.classification_confidence) if m.classification_confidence else 0.0,
"classification_details": m.classification_details,
"created_at": m.created_at
}
# 카테고리별 상세 정보 추가
if m.classified_category == 'PIPE':
pipe_query = text("SELECT * FROM pipe_details WHERE material_id = :material_id")
pipe_result = db.execute(pipe_query, {"material_id": m.id})
pipe_detail = pipe_result.fetchone()
if pipe_detail:
material_dict['pipe_details'] = {
"nominal_size": pipe_detail.nominal_size,
"schedule": pipe_detail.schedule,
"material_standard": pipe_detail.material_standard,
"material_grade": pipe_detail.material_grade,
"material_type": pipe_detail.material_type,
"manufacturing_method": pipe_detail.manufacturing_method,
"end_preparation": pipe_detail.end_preparation,
"wall_thickness": pipe_detail.wall_thickness,
"length_mm": float(pipe_detail.length_mm) if pipe_detail.length_mm else None
}
elif m.classified_category == 'FITTING':
fitting_query = text("SELECT * FROM fitting_details WHERE material_id = :material_id")
fitting_result = db.execute(fitting_query, {"material_id": m.id})
fitting_detail = fitting_result.fetchone()
if fitting_detail:
material_dict['fitting_details'] = {
"fitting_type": fitting_detail.fitting_type,
"fitting_subtype": fitting_detail.fitting_subtype,
"connection_method": fitting_detail.connection_method,
"pressure_rating": fitting_detail.pressure_rating,
"material_standard": fitting_detail.material_standard,
"material_grade": fitting_detail.material_grade,
"main_size": fitting_detail.main_size,
"reduced_size": fitting_detail.reduced_size
}
elif m.classified_category == 'VALVE':
valve_query = text("SELECT * FROM valve_details WHERE material_id = :material_id")
valve_result = db.execute(valve_query, {"material_id": m.id})
valve_detail = valve_result.fetchone()
if valve_detail:
material_dict['valve_details'] = {
"valve_type": valve_detail.valve_type,
"valve_subtype": valve_detail.valve_subtype,
"actuator_type": valve_detail.actuator_type,
"connection_method": valve_detail.connection_method,
"pressure_rating": valve_detail.pressure_rating,
"body_material": valve_detail.body_material,
"size_inches": valve_detail.size_inches
}
material_list.append(material_dict)
return {
"success": True,
"total_count": total_count,
"returned_count": len(materials),
"skip": skip,
"limit": limit,
"materials": [
{
"id": m.id,
"file_id": m.file_id,
"filename": m.original_filename,
"project_id": m.project_id,
"project_code": m.official_project_code,
"project_name": m.project_name,
"original_description": m.original_description,
"quantity": float(m.quantity) if m.quantity else 0,
"unit": m.unit,
"size_spec": m.size_spec,
"material_grade": m.material_grade,
"line_number": m.line_number,
"row_number": m.row_number,
"classified_category": m.classified_category,
"classification_confidence": float(m.classification_confidence) if m.classification_confidence else 0.0,
"created_at": m.created_at
}
for m in materials
]
"materials": material_list
}
except Exception as e:
@@ -863,6 +928,116 @@ async def get_pipe_details(
except Exception as e:
raise HTTPException(status_code=500, detail=f"PIPE 상세 정보 조회 실패: {str(e)}")
@router.get("/fitting-details")
async def get_fitting_details(
file_id: Optional[int] = None,
job_no: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
FITTING 상세 정보 조회
"""
try:
query = """
SELECT fd.*, f.original_filename, f.job_no, f.revision,
m.original_description, m.quantity, m.unit
FROM fitting_details fd
LEFT JOIN files f ON fd.file_id = f.id
LEFT JOIN materials m ON fd.material_id = m.id
WHERE 1=1
"""
params = {}
if file_id:
query += " AND fd.file_id = :file_id"
params["file_id"] = file_id
if job_no:
query += " AND f.job_no = :job_no"
params["job_no"] = job_no
query += " ORDER BY fd.created_at DESC"
result = db.execute(text(query), params)
fitting_details = result.fetchall()
return [
{
"id": fd.id,
"file_id": fd.file_id,
"fitting_type": fd.fitting_type,
"fitting_subtype": fd.fitting_subtype,
"connection_method": fd.connection_method,
"pressure_rating": fd.pressure_rating,
"material_standard": fd.material_standard,
"material_grade": fd.material_grade,
"main_size": fd.main_size,
"reduced_size": fd.reduced_size,
"classification_confidence": fd.classification_confidence,
"original_description": fd.original_description,
"quantity": fd.quantity
}
for fd in fitting_details
]
except Exception as e:
raise HTTPException(status_code=500, detail=f"FITTING 상세 정보 조회 실패: {str(e)}")
@router.get("/valve-details")
async def get_valve_details(
file_id: Optional[int] = None,
job_no: Optional[str] = None,
db: Session = Depends(get_db)
):
"""
VALVE 상세 정보 조회
"""
try:
query = """
SELECT vd.*, f.original_filename, f.job_no, f.revision,
m.original_description, m.quantity, m.unit
FROM valve_details vd
LEFT JOIN files f ON vd.file_id = f.id
LEFT JOIN materials m ON vd.material_id = m.id
WHERE 1=1
"""
params = {}
if file_id:
query += " AND vd.file_id = :file_id"
params["file_id"] = file_id
if job_no:
query += " AND f.job_no = :job_no"
params["job_no"] = job_no
query += " ORDER BY vd.created_at DESC"
result = db.execute(text(query), params)
valve_details = result.fetchall()
return [
{
"id": vd.id,
"file_id": vd.file_id,
"valve_type": vd.valve_type,
"valve_subtype": vd.valve_subtype,
"actuator_type": vd.actuator_type,
"connection_method": vd.connection_method,
"pressure_rating": vd.pressure_rating,
"body_material": vd.body_material,
"size_inches": vd.size_inches,
"fire_safe": vd.fire_safe,
"classification_confidence": vd.classification_confidence,
"original_description": vd.original_description,
"quantity": vd.quantity
}
for vd in valve_details
]
except Exception as e:
raise HTTPException(status_code=500, detail=f"VALVE 상세 정보 조회 실패: {str(e)}")
@router.get("/user-requirements")
async def get_user_requirements(
file_id: Optional[int] = None,