Skip to content

fix(data-quality): alias 중복으로 예측 산출이 전량 중단되던 문제 — 완전동일행만 흡수, 정체성 충돌은 유지 - #68

Open
choigod1023 wants to merge 1 commit into
devfrom
fix/alias-duplicate-resolution
Open

fix(data-quality): alias 중복으로 예측 산출이 전량 중단되던 문제 — 완전동일행만 흡수, 정체성 충돌은 유지#68
choigod1023 wants to merge 1 commit into
devfrom
fix/alias-duplicate-resolution

Conversation

@choigod1023

Copy link
Copy Markdown
Contributor

Description

attach_standardization_metadata 가 alias 중복 하나에도 예외를 던져 stock_predictions.csv 자체가 만들어지지 않았습니다. 수정 후 예측이 완주했습니다.

#66 리뷰에서 지적하신 "완전 동일행만 제거하고 semantic conflict 는 quarantine" 원칙을 그대로 적용했습니다. 제가 앞서 standardized_history.py 에 넣으려던 일괄 drop_duplicates 는 철회했습니다 — 지적이 맞았습니다.

두 파일의 성격이 다릅니다

전수 실측 결과입니다.

파일 중복 완전 동일행 진짜 정체성 충돌
item_alias_candidates_v0.3.parquet 51키 / 102행 43행 0건
item_alias_to_product_v1.parquet 51키 / 102행 0행 49키

이 PR 은 앞쪽만 다룹니다. 뒤쪽은 representative_item_id 자체가 다른 서로 다른 제품이라 흡수하면 하나가 사라집니다. 손대지 않았습니다.

앞쪽 8건 전수 확인

의미 충돌 8키를 전부 열어봤고, 예외 없이 "분류행 1개 + unresolved/UNCLASSIFIED 1개" 였습니다.

K4934::USE0000039   [분류] family_candidate / MED_SUPPLY  / DISPOSABLE_SYRINGE
                    [빈값] unresolved       / UNCLASSIFIED / -
M3545::USE0000020   [분류] family_candidate / LAB_REAGENT / BLOOD_GLUCOSE_TEST_STRIP
                    [빈값] unresolved       / UNCLASSIFIED / -
L;435::USE0000181   [분류] family_candidate / MED_SUPPLY  / MEDICAL_MASK
                    [빈값] unresolved       / UNCLASSIFIED / -
... 8/8 동일 패턴, 서로 다른 분류가 맞붙는 경우 0건

unresolved/UNCLASSIFIED 행은 품목에 대해 아무것도 주장하지 않습니다. 분류행을 남겨도 유실되는 정체성이 없습니다.

조치

두 가지만 완화하고 나머지는 그대로 막습니다.

  1. 읽어들이는 컬럼 전체가 동일한 행은 중복이므로 제거
  2. 분류행 1개 + placeholder 인 키는 분류행을 남김
    • placeholder 만 있는 키는 하나를 남겨 키가 통째로 사라지지 않게 합니다
  3. 서로 다른 분류가 둘 이상이면 여전히 예외 — 상류에서 고쳐야 합니다

3번을 남긴 것이 핵심입니다. 진짜 충돌은 계속 시끄럽게 실패합니다.

작성 중 2번의 함정을 테스트가 잡았습니다. 처음 구현은 충돌 키의 placeholder 를 전부 지워서 placeholder 만 있는 키가 통째로 사라졌습니다. 테스트를 먼저 쓰지 않았으면 조용히 넘어갔을 결함입니다.

테스트

tests/test_data_quality.py 신규 6건입니다.

  • 중복 없는 입력은 그대로 통과
  • 완전 동일행 축약
  • 분류행이 placeholder 를 이김
  • 서로 다른 분류 2개는 예외
  • placeholder 만 있는 키는 1행으로 남고 사라지지 않음
  • 컬럼 보존
Ran 6 tests in 0.013s
OK

실행 결과

outputs/stock_predictions.csv            163,229 행
outputs/stock_backtest_predictions.csv   605,437 행
outputs/stock_predictions_by_subtype.csv  35,217 행
outputs/stock_evaluation_report.csv

To Reviewer

  1. 완화 조건이 충분히 좁은지normalization_status == "unresolved" 그리고 item_group_id_candidate == "UNCLASSIFIED" 두 조건을 모두 만족할 때만 placeholder 로 봅니다. 더 좁혀야 한다면 알려주세요.
  2. 상류 수정 여부 — 근본적으로는 item_normalization 이 같은 local_item_key 를 두 번 만들지 않는 게 맞습니다. 이 PR 은 소비자 쪽 방어이고, 상류 수정은 별도 이슈로 빼는 게 좋을지 판단 부탁드립니다.
  3. item_alias_to_product_v1.parquet 의 49키 정체성 충돌은 이 PR 범위 밖입니다. quarantine 설계가 필요하면 별도로 올리겠습니다.

Type

  • 새로운 기능
  • 버그 수정
  • 리팩토링
  • 문서
  • 의존성 추가/수정

PR Checklist

  • Commit Message Convention을 준수했습니다.
  • Code Convention을 준수했습니다.
  • 변경한 기능이 잘 동작하는지 테스트했습니다. — 단위 테스트 6건 통과 + 실데이터 예측 완주 확인

`attach_standardization_metadata` 는 `item_alias_candidates_v0.3.parquet` 에
local_item_key 가 하나라도 겹치면 예외를 던져 `stock_predictions.csv` 자체가
만들어지지 않았다.

실측하면 505,720행 중 중복은 51키 102행(0.02%)이고, 성격이 둘로 갈린다.

- 읽어들이는 3개 컬럼이 완전히 같은 단순 중복 43행
- 남는 의미 충돌 8키 16행 — 전부 "분류행 1 + unresolved/UNCLASSIFIED 1"

8건 전수를 확인했고 서로 다른 분류가 맞붙는 경우는 0건이었다.
unresolved 행은 정체성을 담고 있지 않으므로, 분류행을 남겨도
제품 정체성이 유실되지 않는다.

따라서 두 가지만 완화한다.

1. 읽는 컬럼 전체가 동일한 행은 중복이므로 제거
2. 분류행 1개 + placeholder 인 키는 분류행을 남긴다
   (placeholder 만 있는 키는 하나를 남겨 키가 사라지지 않게 한다)

서로 다른 분류가 둘 이상인 키는 진짜 정체성 충돌이므로 **여전히 예외**를
내고 상류에서 고쳐야 한다.

`item_alias_to_product_v1.parquet` 쪽은 성격이 다르다. 중복 51키 중
49키가 representative_item_id 자체가 다르고 완전 동일행은 0행이라,
같은 방식으로 흡수하면 실제로 제품 하나가 사라진다. 그쪽은 손대지 않았다.

수정 후 예측이 완주해 stock_predictions.csv 163,229행,
stock_backtest_predictions.csv 605,437행이 산출됐다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sehyeon03

Copy link
Copy Markdown
Contributor

2026-08-10 신규 PR 검토

결론: 완전 동일행만 축약하고 실제 충돌은 막는 방향은 맞지만, 현재 placeholder 판정은 의미값을 임의로 선택할 수 있어 병합 전 수정이 필요합니다. 관련 테스트 6개와 통합 브랜치 전체 244개 테스트는 통과했습니다.

Blocking 1: item_family_id_candidate를 가진 행도 placeholder로 처리합니다

placeholder 조건은 normalization_status == unresolveditem_group_id_candidate == UNCLASSIFIED만 확인합니다. 따라서 family가 서로 다른 두 행도 placeholder-only로 간주하고 첫 행을 남깁니다. 실제로 입력 순서만 뒤집으면 결과가 바뀝니다.

[FAMILY_X, FAMILY_Y] -> FAMILY_X
[FAMILY_Y, FAMILY_X] -> FAMILY_Y

새 테스트 test_unresolved_only_duplicates_collapse_to_one 자체도 family=SOMETHING인 행을 “아무 정보가 없는 placeholder”라고 간주하고 있어 이 오류를 고정하고 있습니다. 소비자가 실제로 붙이는 세 컬럼 중 family가 비어 있지 않으므로 이는 무정보 행이 아닙니다.

Blocking 2: 제품 정체성 충돌을 확인할 컬럼을 읽지 않습니다

item_alias_candidates는 원래 (기관, 물품코드, 정리된 물품명) 단위인데 이 함수는 local_item_key와 status/group/family만 읽고 이름·대표품목·규격을 버립니다. 같은 local code가 시기별 다른 제품에 재사용된 경우, 두 행의 group/family가 우연히 같으면 drop_duplicates()로 제품 충돌을 보지 못합니다. item_alias_to_product_v1에서 이미 서로 다른 대표품목 49키가 관측됐으므로 “제품 정체성을 선택하지 않는다”는 현재 docstring은 보장되지 않습니다.

수정 권장

  1. placeholder는 모든 소비 semantic field가 blank/sentinel일 때만 인정
  2. prediction output에 이미 있는 item_name을 정규화해 (local_item_key, raw/cleaned name)으로 exact alias join하거나, 승인된 temporal representative mapping 사용
  3. local key가 여러 product identity로 이어지면 분류가 같더라도 quarantine/명시적 conflict flag
  4. 완전 동일행은 “원본 전체행”이 아니라 “읽은 컬럼 기준 동일행”임을 명시하고 source artifact hash/run manifest 기록
  5. 입력 순서 불변성, family 값 충돌, 동일 family지만 다른 representative ID인 회귀 테스트 추가

상류에서 local key 중복을 없애는 것보다 같은 코드의 이름 변경과 코드 재사용을 구분해 보존하는 것이 맞습니다. 소비자 방어를 넣더라도 임의 1행 선택은 피해야 합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants