schoolbridge / backend /app /services /header_split.py
tsKim
feat: schoolbridge spaces deploy (extract-text endpoint added)
7f105c8
"""슬롯 카드 헤더 분해 — todo.text → (헤더, 값) 페어.
강사님 처방 "슬롯 위주 가공" 대응:
슬롯 카드 = 헤더(라벨) 굵게 + 값 행. 본 모듈이 헤더 추출 책임.
윤정님 split_sentences가 이미 헤더 단위로 todo를 분리해서 반환하므로
보통 todo.text 시작에 헤더 키워드가 옴. 또한 윤정님이 한글 프로 기호 +
표 구분자(`|`)를 정제하기로 합의됨 — 매칭 시 구분자는 옵셔널 처리.
매칭 실패 시 (None, 원문)을 반환해 호출부가 fallback("기타") 헤더로 처리.
"""
from __future__ import annotations
import re
# 가정통신문 표준 헤더 키워드.
# 윤정님 split_sentences 분리 룰의 키워드 + 갈산초/서대구초 케이스 보강.
# 긴 표현이 짧은 것에 흡수되지 않게 정렬은 길이 내림차순 (예: "기타 안내사항" 우선, "기타" 후순위).
HEADER_KEYWORDS: list[str] = [
# 운영 계열
"운영시간", "운영방법", "운영날짜", "운영기간", "운영장소",
# 신청 계열
"신청방법", "신청기간", "신청경로", "신청대상", "신청자격",
# 접수/제출
"접수기간", "접수방법", "접수처",
"제출방법", "제출기한", "제출처",
# 일정/장소
"일시", "기간", "장소", "위치", "주소",
# 대상/자격
"대상", "자격", "참가대상",
# 준비물/비용
"준비물", "지참물", "준비사항",
"비용", "회비", "참가비", "수강료", "급식비",
# 안내/유의
"기타 안내사항", "기타안내사항", "안내사항",
"유의사항", "참고사항", "주의사항",
# 문의
"문의", "연락처", "문의처",
# 기타 (가장 짧음, 마지막)
"기타",
]
_HEADER_KEYWORDS_SORTED = sorted(HEADER_KEYWORDS, key=len, reverse=True)
# 줄 시작 + 헤더 키워드 + (선택적 `|`/`:`/`:` 구분자) + 값
# `|`는 윤정님이 정제하기 전엔 살아있을 수 있어 옵셔널 처리.
_HEADER_RE = re.compile(
r"^\s*(?P<header>" + "|".join(re.escape(k) for k in _HEADER_KEYWORDS_SORTED) + r")"
r"\s*[|::]?\s*"
r"(?P<value>.+)$",
re.DOTALL,
)
def split_header_value(text: str) -> tuple[str | None, str]:
"""todo.text → (헤더, 값) 페어.
매칭 실패 시 (None, text.strip()) 반환 — 호출부에서 "기타" 같은 fallback 헤더 처리.
"""
if not text or not text.strip():
return None, ""
m = _HEADER_RE.match(text.strip())
if m:
return m.group("header"), m.group("value").strip()
return None, text.strip()