"""원문 텍스트 → 업종 스키마 fact 추출 프롬프트·응답 스키마.""" from common.category_schema import get_schema from common.enums import PlaceCategory # Gemini responseSchema(OpenAPI subset). RESPONSE_SCHEMA = { "type": "object", "properties": { "facts": { "type": "array", "items": { "type": "object", "properties": { "key": {"type": "string", "description": "아래 필드 목록의 key 중 하나. 목록에 없는 key 는 절대 쓰지 않는다"}, "value": {"type": "string", "description": "원문 표기 그대로. bool 필드는 'true' 또는 'false'"}, "unit_name": {"type": "string", "description": "scope=unit 필드일 때 어느 객실·메뉴·프로그램인지. place 스코프면 빈 문자열"}, "evidence": {"type": "string", "description": "이 값의 근거가 된 원문 문장을 **글자 그대로** 복사. 요약·수정 금지"}, }, "required": ["key", "value", "evidence"], }, } }, "required": ["facts"], } _RULES = """규칙 1. 아래 [필드 목록]에 있는 key 만 쓴다. 목록에 없는 key 는 버린다. 2. value 는 원문 표기를 **그대로** 옮긴다. 단위 환산·반올림·요약·번역을 하지 않는다. - "오후 3시" 를 "15:00" 으로 바꾸지 않는다. 원문이 "오후 3시" 면 "오후 3시" 다. 3. evidence 에는 그 값이 적힌 **원문 문장을 글자 그대로 복사**한다. - 원문에 없는 문장을 쓰면 그 항목은 버려진다. - 여러 문장에 걸쳐 있으면 값이 실제로 적힌 한 문장만 고른다. 4. 원문에 없는 것은 만들지 않는다. 모르면 그 key 를 빼면 된다. 빈 값으로 채우지 않는다. - "보통 펜션은 3시 체크인" 같은 일반 상식으로 채우지 않는다. 5. type=bool 필드는 value 를 "true" 또는 "false" 로만 쓴다. - 원문에 "반려동물 동반 불가" 라고 있으면 pet_allowed = "false" 다. 부정 표현을 놓치지 않는다. - 원문에 언급이 아예 없으면 그 key 를 **쓰지 않는다**. "없다"고 단정하지 않는다. 6. type=number 필드는 숫자만 쓴다(단위·쉼표 없이). 원문이 "최대 4인" 이면 "4" 다. 7. scope=unit 필드는 unit_name 을 반드시 채운다. 같은 객실·메뉴의 항목은 unit_name 을 똑같이 쓴다. - unit_name 은 원문에 적힌 이름 그대로. "A동", "커플룸", "김치찌개". 8. 홍보 문구·후기·다른 업소 이야기는 근거로 쓰지 않는다. - "최고의 전망", "인생 맛집" 같은 표현은 사실이 아니다. 9. 같은 key 가 원문에서 여러 번 다르게 적혀 있으면, 가장 구체적이고 최신인 한 건만 고른다. """ def _field_lines(category: PlaceCategory) -> str: """업종 스키마를 그대로 프롬프트에 편다.""" schema = get_schema(category) lines = [] for spec in schema.fields.values(): # allow_llm 필드(소개문 등)는 목록에 넣지 않는다 — LLM 이 **쓰는** 칸이지 원문에서 옮겨 담는 사실이 아니다. if spec.allow_llm: continue bits = [f"- {spec.key} ({spec.label})", f"type={spec.type}", f"scope={spec.scope}"] if spec.unit: bits.append(f"단위={spec.unit}") if spec.critical: # 틀리면 예약 클레임이 나는 항목. bits.append("※중요-확실할 때만") lines.append(" · ".join(bits)) return "\n".join(lines) def build_prompt(place_name: str, category: PlaceCategory, source_text: str, *, max_chars: int = 30000) -> str: """추출 프롬프트.""" schema = get_schema(category) text = (source_text or "").strip()[:max_chars] return f"""너는 사업장 정보를 **원문에서 찾아 옮겨 적는** 추출기다. 글을 쓰는 것이 아니다. [사업장] {place_name} (업종: {schema.label}) {_RULES} [필드 목록] {_field_lines(category)} [원문] \"\"\" {text} \"\"\" 원문에서 찾은 항목만 facts 배열에 담아라. 찾은 게 없으면 빈 배열을 반환한다."""