o2o-site-AEO/solution/backend/services/place_category.py
Mina Choi 11d30bb3d1 [chore] solution,admin,ontology: 코드 주석을 한 줄로 — 히스토리 주석 삭제
여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다.

- ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제
- 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다
- eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다

파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이).
site·frontend·admin tsc, site vitest 105 passed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:19 +09:00

76 lines
3.5 KiB
Python

"""외부 장소 DB 의 분류 → 우리 업종(PlaceCategory)."""
from typing import Optional
from common.enums import PlaceCategory
# 카카오 category_group_code.
_BY_GROUP_CODE = {
"AD5": PlaceCategory.LODGING,
"CE7": PlaceCategory.CAFE,
"FD6": PlaceCategory.RESTAURANT,
}
_HOSPITAL_GROUP_CODE = "HP8"
# 분류 문자열 키워드.
_KEYWORDS: list[tuple[PlaceCategory, tuple[str, ...]]] = [
# "피부"·"성형" 이 아니라 "피부과"·"성형외과" 다.
(PlaceCategory.CLINIC, ("피부과", "성형외과")),
(PlaceCategory.LODGING, ("숙박", "펜션", "호텔", "모텔", "리조트", "게스트하우스", "민박")),
(PlaceCategory.CAFE, ("카페", "커피", "베이커리", "제과", "디저트")),
(PlaceCategory.RESTAURANT, ("음식점", "한식", "일식", "중식", "양식", "분식",
"주점", "술집", "치킨", "횟집", "뷔페", "패스트푸드")),
]
def guess_category(
category_name: Optional[str], group_code: Optional[str] = None
) -> Optional[PlaceCategory]:
"""분류 문자열(+ 카카오 그룹코드)로 업종을 추정한다."""
text = (category_name or "").replace(" ", "")
code = (group_code or "").strip().upper()
if code == _HOSPITAL_GROUP_CODE:
# 병원이라는 것까지만 안다.
return PlaceCategory.CLINIC if _match(text) is PlaceCategory.CLINIC else None
if code in _BY_GROUP_CODE:
by_code = _BY_GROUP_CODE[code]
# 그룹코드를 문자열로 덮는 경우가 하나 있다: 카카오가 베이커리·브런치 가게를 FD6(음식점)으로 주면서 분류 문자열엔 "카페"를 다는 일이 있다.
if by_code is PlaceCategory.RESTAURANT and _match(text) is PlaceCategory.CAFE:
return PlaceCategory.CAFE
return by_code
return _match(text)
def _match(text: str) -> Optional[PlaceCategory]:
for category, words in _KEYWORDS:
if any(word in text for word in words):
return category
return None
# ── 음식 중분류(TourAPI lclsSystm2) 추정 ───────────────────────────────── 주변 맛집에서 **같은 중분류(경쟁 업소)를 빼는** 기준이다.
_FOOD_CLASS_KEYWORDS: list[tuple[str, tuple[str, ...]]] = [
("FD05", ("카페", "커피", "찻집", "디저트", "음료", "주스", "빙수", "케이크", "브런치")),
("FD04", ("주점", "술집", "호프", "맥주", "이자카야", "포차", "와인바", "칵테일", "펍")),
("FD03", ("제과", "베이커리", "빵", "도넛", "피자", "햄버거", "샌드위치", "치킨", "분식", "김밥",
"떡볶이", "간식", "토스트", "패스트푸드")),
("FD02", ("중식", "중국", "일식", "일본", "초밥", "라멘", "양식", "서양", "이탈리", "파스타", "스테이크",
"프렌치", "프랑스", "멕시", "아시아", "베트남", "태국", "인도", "퓨전")),
("FD01", ("한식", "한정식", "백반", "국밥", "찌개", "국수", "냉면", "삼겹", "갈비", "곱창", "족발", "보쌈",
"해장국", "설렁탕", "감자탕", "칼국수", "횟집", "회")),
]
def guess_food_class(category_name: Optional[str]) -> Optional[str]:
"""외부 분류 문자열 → TourAPI 음식 중분류 코드(FD01~FD05)."""
text = (category_name or "").replace(" ", "")
if not text:
return None
for code, words in _FOOD_CLASS_KEYWORDS:
if any(word in text for word in words):
return code
return None