도먼 추가 정보 생성 기능 구현

This commit is contained in:
2025-07-23 13:37:33 +09:00
parent 1a2d57a306
commit 8a5f3128ad
7 changed files with 611 additions and 112 deletions

View File

@@ -29,33 +29,65 @@ ValueWithCoords = types.Schema(
required=["value", "x", "y"]
)
# 범용 표의 한 행을 위한 스키마
GenericTableRow = types.Schema(
type=types.Type.ARRAY,
items=ValueWithCoords,
description="표의 한 행. 각 셀의 값과 좌표를 포함합니다."
)
# 페이지에서 추출된 범용 표를 위한 스키마
GenericTable = types.Schema(
type=types.Type.OBJECT,
properties={
"table_title": types.Schema(type=types.Type.STRING, description="추출된 표의 내용을 설명하는 제목 (예: '범례', 'IP 정보', '개정 이력')."),
"table_data": types.Schema(
type=types.Type.ARRAY,
items=GenericTableRow,
description="표의 데이터. 각 내부 리스트가 하나의 행을 나타냅니다."
)
},
description="도면에서 발견된 구조화된 정보 블록이나 표."
)
# 모든 필드가 ValueWithCoords를 사용하도록 스키마 업데이트
SCHEMA_EXPRESSWAY = types.Schema(
type=types.Type.OBJECT,
properties={
"도면명": ValueWithCoords,
"도면명_line0": ValueWithCoords,
"도면명_line1": ValueWithCoords,
"도면명_line2": ValueWithCoords,
"편철번호": ValueWithCoords,
"도면번호": ValueWithCoords,
"Main Title": ValueWithCoords,
"Sub Title": ValueWithCoords,
"수평축척": ValueWithCoords,
"수직축척": ValueWithCoords,
"Main_Title": ValueWithCoords,
"Sub_Title": ValueWithCoords,
"수평_도면_축척": ValueWithCoords,
"수직_도면_축척": ValueWithCoords,
"적용표준버전": ValueWithCoords,
"사업명": ValueWithCoords,
"사업명_top": ValueWithCoords,
"사업명_bot": ValueWithCoords,
"시설_공구": ValueWithCoords,
"설계공구_Station": ValueWithCoords,
"시공공구_Station": ValueWithCoords,
"설계공구_공구명": ValueWithCoords,
"설계공구_범위": ValueWithCoords,
"시공공구_공구명": ValueWithCoords,
"시공공구_범위": ValueWithCoords,
"건설분야": ValueWithCoords,
"건설단계": ValueWithCoords,
"설계사": ValueWithCoords,
"시공사": ValueWithCoords,
"노선이정": ValueWithCoords,
"정번호": ValueWithCoords,
"정날짜": ValueWithCoords,
"정내용": ValueWithCoords,
"작성자": ValueWithCoords,
"검토자": ValueWithCoords,
"확인자": ValueWithCoords
"정번호_1": ValueWithCoords,
"정날짜_1": ValueWithCoords,
"정내용_1": ValueWithCoords,
"작성자_1": ValueWithCoords,
"검토자_1": ValueWithCoords,
"확인자_1": ValueWithCoords,
"additional_tables": types.Schema(
type=types.Type.ARRAY,
items=GenericTable,
description="도면에서 발견된 추가적인 표나 정보 블록 목록."
)
},
)
@@ -74,12 +106,17 @@ SCHEMA_TRANSPORTATION = types.Schema(
"시설_공구": ValueWithCoords,
"건설분야": ValueWithCoords,
"건설단계": ValueWithCoords,
"정차수": ValueWithCoords,
"정일자": ValueWithCoords,
"정차수": ValueWithCoords,
"정일자": ValueWithCoords,
"과업책임자": ValueWithCoords,
"분야별책임자": ValueWithCoords,
"설계자": ValueWithCoords,
"위치정보": ValueWithCoords
"위치정보": ValueWithCoords,
"additional_tables": types.Schema(
type=types.Type.ARRAY,
items=GenericTable,
description="도면에서 발견된 추가적인 표나 정보 블록 목록."
)
},
)
@@ -139,9 +176,17 @@ class GeminiAnalyzer:
"\n\n--- 추출된 텍스트와 좌표 정보 ---\n" +
text_context +
"\n\n--- 지시사항 ---\n"
"위 텍스트와 좌표 정보를 바탕으로, 이미지의 내용을 분석하여 JSON 스키마를 채워주세요."
"각 필드에 해당하는 텍스트를 찾고, 해당 텍스트의 'value'와 시작 'x', 'y' 좌표를 JSON에 기입하세요."
"해당하는 값이 없으면 빈 문자열을 사용하세요."
"1. 위 텍스트와 좌표 정보를 바탕으로, 이미지의 내용을 분석하여 JSON 스키마의 기본 필드를 채워주세요.\n"
"2. **(중요)** 도면 내에 표나 사각형으로 구분된 정보 블록이 있다면, `additional_tables` 필드에 추가해주세요. 예를 들어 'IP 정보 표', '범례(Legend)', '구조물 설명', '개정 이력' 등이 해당됩니다.\n"
" - 각 표/블록에 대해 `table_title`에 적절한 제목을 붙여주세요.\n"
" - `table_data`에는 표의 모든 행을 추출하여 리스트 형태로 넣어주세요. 각 행은 셀들의 리스트입니다.\n"
"3. 각 필드에 해당하는 텍스트를 찾고, 해당 텍스트의 'value'와 시작 'x', 'y' 좌표를 JSON에 기입하세요.\n"
"4. 해당하는 값이 없으면 빈 문자열이나 빈 리스트를 사용하세요.\n"
"\n--- 필드 설명 ---\n"
"- `{ }_Title`: 중앙 상단의 비교적 큰 폰트입니다.\n"
"- `사업명_top`에 해당하는 텍스트 아랫줄은 '시설_공구' 항목입니다.\n"
"- `도면명_line{n}`: 도면명에 해당하는 여러 줄의 텍스트를 위에서부터 0, 1, 2 순서로 기입합니다. 만약 두 줄이라면 line0은 비워두고 line1, line2를 채웁니다.\n"
"- `설계공구`/`시공공구`: '공구명''범위'로 나뉘어 기입될 수 있습니다. (예: '설계공구 | 제2-1공구 | 12780.00-15860.00' -> `설계공구_공구명`: '제2-1공구', `설계공구_범위`: '12780.00-15860.00')\n"
)
contents = [
@@ -178,6 +223,49 @@ class GeminiAnalyzer:
result = response.text
# JSON 응답을 파싱하여 다시 직렬화 (일관된 포맷팅)
parsed_json = json.loads(result)
# 디버깅: Gemini 응답 내용 로깅
logger.info(f"=== Gemini 응답 디버깅 ===")
logger.info(f"조직 유형: {organization_type}")
logger.info(f"응답 필드 수: {len(parsed_json) if isinstance(parsed_json, dict) else 'N/A'}")
if isinstance(parsed_json, dict):
# 새로운 필드들이 응답에 포함되었는지 확인
new_fields = ["설계공구_Station_col1", "설계공구_Station_col2", "시공공구_Station_col1", "시공공구_Station_col2"]
old_fields = ["설계공구_Station", "시공공구_Station"]
logger.info("=== 새 필드 확인 ===")
for field in new_fields:
if field in parsed_json:
field_data = parsed_json[field]
if isinstance(field_data, dict) and field_data.get('value'):
logger.info(f"{field}: '{field_data.get('value')}' at ({field_data.get('x', 'N/A')}, {field_data.get('y', 'N/A')})")
else:
logger.info(f"⚠️ {field}: 빈 값 또는 잘못된 형식 - {field_data}")
else:
logger.info(f"{field}: 응답에 없음")
logger.info("=== 기존 필드 확인 ===")
for field in old_fields:
if field in parsed_json:
field_data = parsed_json[field]
if isinstance(field_data, dict) and field_data.get('value'):
logger.info(f"⚠️ {field}: '{field_data.get('value')}' (기존 필드가 여전히 존재)")
else:
logger.info(f"⚠️ {field}: 빈 값 - {field_data}")
else:
logger.info(f"{field}: 응답에 없음 (예상됨)")
logger.info("=== 전체 응답 필드 목록 ===")
for key in parsed_json.keys():
value = parsed_json[key]
if isinstance(value, dict) and 'value' in value:
logger.info(f"필드: {key} = '{value.get('value', '')}' at ({value.get('x', 'N/A')}, {value.get('y', 'N/A')})")
else:
logger.info(f"필드: {key} = {type(value).__name__}")
logger.info("=== 디버깅 끝 ===")
pretty_result = json.dumps(parsed_json, ensure_ascii=False, indent=2)
logger.info(f"분석 완료: {len(pretty_result)} 문자")
return pretty_result