feat: PIPE 분석 기능 개선 및 자재 확인 페이지 UX 향상
- 자재 확인 페이지에 뒤로가기 버튼 추가 - 상세 목록 탭에 PIPE 분석 섹션 추가 - 재질-외경-스케줄-제작방식별로 그룹화 - 동일 속성 파이프들의 길이 합산 표시 - 총 파이프 길이 및 규격 종류 수 요약 - 파일 삭제 기능 수정 (외래키 제약 조건 해결) - MaterialsPage에서 전체 자재 목록 표시 (limit 10000) - 길이 단위 변환 로직 수정 (mm 단위 유지) - 파싱 로직에 디버그 출력 추가 TODO: MAIN_NOM/RED_NOM 별도 저장을 위한 스키마 개선 필요
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user