diff --git a/backend/src/schemas/simulation_input.py b/backend/src/schemas/simulation_input.py index d66ac3a2..897ff42f 100644 --- a/backend/src/schemas/simulation_input.py +++ b/backend/src/schemas/simulation_input.py @@ -141,3 +141,36 @@ def _warn_unknown_business_type(cls, v: str) -> str: sorted(_BUSINESS_TYPE_ALLOWED), ) return v + + @field_validator("target_district", "target_districts", mode="before") + @classmethod + def _validate_dong_code_format(cls, v): + """target_district / target_districts 입력 검증. + + 사용자가 dong_code 형식 (8자리 숫자) 으로 입력 시 행정동 형식 강제 검증. + 한글 동명 (예: '서교동') 입력은 통과 — dong_resolver 가 후속 매핑. + 법정동 10자리 코드 입력 시 reject (잘못된 컬럼 적재 차단). + """ + if v is None or v == "": + return v + # list 입력 처리 + if isinstance(v, list): + return [cls._check_single(d) for d in v] + return cls._check_single(v) + + @staticmethod + def _check_single(value: str) -> str: + """단일 동 입력값 검증 — 숫자 형식이면 8자리 강제, 한글이면 통과.""" + if not value or not isinstance(value, str): + return value + s = value.strip() + # 숫자 입력 (dong_code 직접 입력 케이스) — 8자리 강제 + if s.isdigit(): + if len(s) != 8: + raise ValueError( + f"dong_code 형식 오류: {value!r} (행정동 8자리 숫자 기대, " + "10자리 법정동 또는 잘못된 형식이면 한글 동명 또는 8자리 행정동 코드 사용)" + ) + return s + # 한글 동명 — dong_resolver 가 후속 매핑 (validator 통과) + return s diff --git a/backend/src/services/dong_resolver.py b/backend/src/services/dong_resolver.py index 5596006f..c0e57d37 100644 --- a/backend/src/services/dong_resolver.py +++ b/backend/src/services/dong_resolver.py @@ -123,6 +123,46 @@ def resolve_dong_code_or_default( return resolve_dong_code(dong_name, db_url=db_url, default=fallback) or fallback +def validate_dong_code(code: str | None, *, strict: bool = True) -> str | None: + """행정동 dong_code 8자리 숫자 형식 검증. + + Args: + code: 검증 대상 코드. None / "" 은 None 반환. + strict: True 면 형식 위반 시 ValueError raise. False 면 None 반환 (silent skip). + + Returns: + 검증 통과 시 trim 된 8자리 코드. 실패 시 None (strict=False) 또는 ValueError. + + Raises: + ValueError (strict=True): 8자리 숫자 아니면 raise. + + 예외: + - 마포 행정동 코드: '11440***' (8자, 숫자) — 통과 + - 법정동 코드 (10자): 거부 (잘못된 컬럼 적재 차단) + - 빈 값 / None: None 반환 + + 사용처: + - 새 ETL/사용자 입력 검증 — varchar(10/15/text) 컬럼이라 길이 통과해도 + 행정동 컬럼 적재 시 SoT 가정 위반. + - SimulationInput Pydantic validator + - ORM 적재 직전 sanity check + """ + if code is None: + return None + s = str(code).strip() + if not s: + return None + if len(s) == 8 and s.isdigit(): + return s + if strict: + raise ValueError( + f"invalid dong_code format: {code!r} (행정동 8자리 숫자 기대, " + "법정동 10자리 또는 잘못된 형식이면 별 컬럼/테이블 사용)" + ) + logger.warning(f"[validate_dong_code] 잘못된 dong_code 형식: {code!r} — None 반환 (silent)") + return None + + def resolve_dong_name(dong_code: str, db_url: str | None = None) -> str | None: """동코드 → 동이름 변환. diff --git a/docs/retrospective/2026-05-05.md b/docs/retrospective/2026-05-05.md index 23b3d105..9999cf88 100644 --- a/docs/retrospective/2026-05-05.md +++ b/docs/retrospective/2026-05-05.md @@ -534,3 +534,18 @@ ``` --- + +## 12:19:50 세션 완료 + +### 변경 파일 +- backend/src/agents/legal/categories.py +- docs/retrospective/2026-05-05.md + +### diff 요약 +``` + backend/src/agents/legal/categories.py | 18 +++++++++--------- + docs/retrospective/2026-05-05.md | 9 +++++++++ + 2 files changed, 18 insertions(+), 9 deletions(-) +``` + +---