요약
factlog/literal_types.py 의 네 파서가 모두 \d 로 숫자를 인식한다. 파이썬의 \d 는
유니코드 Nd 범주 전체에 매치하므로 전각 숫자가 조용히 파싱된다. 스칼라는 ASCII
표기와 같은 값이 나오는데 KB 가 저장하는 객체 문자열은 다르다. 그래서 같은 값이
문맥에 따라 하나로도, 둘로도 취급된다.
재현 (upstream/main c6d359d, Python 3.12 로 실측)
from factlog.literal_types import (
parse_date, parse_number_scaled, parse_ordinal, parse_amount, DEFAULT_AMOUNT_UNITS,
)
parse_date('date(2020,1)') # 20200101
parse_date('date(2020,1)') # 20200101 <- 전각인데 통과, 같은 스칼라
parse_number_scaled('number(123)') # 123000
parse_ordinal('ordinal(3)') # 3
parse_amount('100억', DEFAULT_AMOUNT_UNITS) # 10000000000
근거
factlog/literal_types.py:44-78 의 모든 수치 그룹이 \d 로 적혀 있다.
_DATE_RE = re.compile(r"^(\d{4})[.\-/](\d{1,2})(?:[.\-/](\d{1,2}))?$")
_DATE_COMPOUND_RE = re.compile(r"^date\(\s*(\d{4})(?:\s*,\s*(\d{1,2})...")
_NUMBER_RE = re.compile(r"^-?\d[\d,]*(?:\.\d+)?$")
_ORDINAL_KO_RE = re.compile(r"^제?(\d+)\s*(?:호|위|번|차|등|째)$")
_AMOUNT_RE = re.compile(r"^(?P<num>-?\d[\d,]*(?:\.\d+)?) ?(?P<unit>\D+)$")
모듈 docstring 이 규정한 파서 계약은 "returns the canonical scalar, or None … never
guesses" 인데, 여기서는 정규화 없이 값을 받아들이면서 저장 문자열은 원문 그대로 둔다.
int() 가 전각을 받아주기 때문에 뒤따르는 변환도 예외 없이 통과한다.
코드베이스는 NFC 로 정규화하는데, NFC 는 전각 숫자를 접지 않는다(NFKC 만 접는다).
따라서 상류 어디에서도 두 표기가 합쳐지지 않는다.
영향
한 값이 세 갈래로 갈린다.
- typed 관계 아래 — 그룹핑 키가 스칼라이므로 두 표기가 같은 값으로 병합된다.
단일값 모순 검출·비교 규칙은 둘을 하나로 본다.
- 객체 매치 질의 — 질의 상수는 저장 문자열과 대조되므로, ASCII 로 물으면 전각 행이
조용히 미스한다. 검증된 부정(engine says no)과 구분되지 않는다.
- typed 선언이 없는 관계 — 파서를 아예 타지 않으므로 두 표기가 별개 엔티티로
남는다.
즉 "같은 값인가" 에 대한 답이 코드 경로마다 다르다. 심각도 중간 — 크래시는 없고
CJK 소스·PDF 변환·IME 전각 모드에서 유입되는 사고성 입력이 주된 경로다.
제안
두 방향 중 하나를 정책으로 선택해야 한다(둘 다 하면 안 된다).
- (A) 거부 — 수치 그룹을
[0-9] 로 좁혀 전각 값이 파싱되지 않게 한다. 값은 "does not
parse" 경로로 떨어져 typed projection 경고와 entity_audit 의 malformed 보고에 드러난다.
저장 문자열을 고쳐 쓰지 않으므로 데이터를 재작성하지 않는다. 다만 (3) 의 엔티티
분리는 그대로 남는다.
- (B) 접기 —
tools/merge_candidates.py 의 normalize_rows 에서 dedup 키 계산 전에
전각을 ASCII 로 접는다(canonical_amount 가 이미 같은 자리에서 하는 일). 세 증상이
한꺼번에 사라지지만 저장된 사실을 재작성하는 결정이다.
이 저장소가 --category 사전검증·silent-zero 가드에서 보인 규율(조용한 폴드보다 명시적
거부)을 따르면 (A) 가 일관된다. 어느 쪽이든 선택 자체가 문서화되어야 한다.
수용 기준
요약
factlog/literal_types.py의 네 파서가 모두\d로 숫자를 인식한다. 파이썬의\d는유니코드
Nd범주 전체에 매치하므로 전각 숫자가 조용히 파싱된다. 스칼라는 ASCII표기와 같은 값이 나오는데 KB 가 저장하는 객체 문자열은 다르다. 그래서 같은 값이
문맥에 따라 하나로도, 둘로도 취급된다.
재현 (upstream/main c6d359d, Python 3.12 로 실측)
근거
factlog/literal_types.py:44-78의 모든 수치 그룹이\d로 적혀 있다.모듈 docstring 이 규정한 파서 계약은 "returns the canonical scalar, or None … never
guesses" 인데, 여기서는 정규화 없이 값을 받아들이면서 저장 문자열은 원문 그대로 둔다.
int()가 전각을 받아주기 때문에 뒤따르는 변환도 예외 없이 통과한다.코드베이스는 NFC 로 정규화하는데, NFC 는 전각 숫자를 접지 않는다(NFKC 만 접는다).
따라서 상류 어디에서도 두 표기가 합쳐지지 않는다.
영향
한 값이 세 갈래로 갈린다.
단일값 모순 검출·비교 규칙은 둘을 하나로 본다.
조용히 미스한다. 검증된 부정(engine says no)과 구분되지 않는다.
남는다.
즉 "같은 값인가" 에 대한 답이 코드 경로마다 다르다. 심각도 중간 — 크래시는 없고
CJK 소스·PDF 변환·IME 전각 모드에서 유입되는 사고성 입력이 주된 경로다.
제안
두 방향 중 하나를 정책으로 선택해야 한다(둘 다 하면 안 된다).
[0-9]로 좁혀 전각 값이 파싱되지 않게 한다. 값은 "does notparse" 경로로 떨어져 typed projection 경고와
entity_audit의 malformed 보고에 드러난다.저장 문자열을 고쳐 쓰지 않으므로 데이터를 재작성하지 않는다. 다만 (3) 의 엔티티
분리는 그대로 남는다.
tools/merge_candidates.py의normalize_rows에서 dedup 키 계산 전에전각을 ASCII 로 접는다(
canonical_amount가 이미 같은 자리에서 하는 일). 세 증상이한꺼번에 사라지지만 저장된 사실을 재작성하는 결정이다.
이 저장소가
--category사전검증·silent-zero 가드에서 보인 규율(조용한 폴드보다 명시적거부)을 따르면 (A) 가 일관된다. 어느 쪽이든 선택 자체가 문서화되어야 한다.
수용 기준