Skip to content

[이슈 #52] AI 재고판정 산출물 → DB inventory handoff 변환·반영 (부서 접기 + SS/ROP 재계산) - #53

Merged
choigod1023 merged 1 commit into
devfrom
feat/inventory-policy-handoff
Aug 7, 2026
Merged

[이슈 #52] AI 재고판정 산출물 → DB inventory handoff 변환·반영 (부서 접기 + SS/ROP 재계산)#53
choigod1023 merged 1 commit into
devfrom
feat/inventory-policy-handoff

Conversation

@choigod1023

Copy link
Copy Markdown
Contributor

배경 — DB 의 숫자는 AI 산출물이 아니다

코드를 따라가 보니 inventorymu/sigma/z_used/lead_time_used/ss/ropbackend 스톱갭(scripts/fix_inventory_stats.py)이 계산한 값입니다. ai_policy_adapter.py 주석이 그 상황을 그대로 적어놨습니다.

fix_inventory_stats.py (임시·계산 주체=backend) → ai_policy_adapter.py (정식·계산 주체=ai)
그동안은 ai 서빙 API 가 배포 전이라 backend 가 임시로 직접 계산해왔다(스톱갭)

AI 쪽은 inventory-status-v1.1 에서 이미 mean_daily_usage_floor: 0.0 으로 바닥값을 없앴는데, 그 결과가 DB 로 오는 경로가 없습니다. 운영 DB 실측(2026-07-30):

항목
inventory 전체 409,459
mu <= 0.5 296,537 (72.4%)
sigma <= 0.1 201,814

왜 그대로 붓지 못하나 — 키 단위가 다르다

AI : (institution_code, department, item_code)   416,128 시계열
DB : (institution_id,              standard_code) 409,459 행   ← 부서 축 없음

1) compute_inventory_policy_handoff.py — 부서 접기

AI 산출물이 시계열별 모멘트 합(all_time_normal_outbound, normal_outbound_squared_sum)을 들고 있어서 근사 없이 재계산됩니다.

출고합·제곱합 SUM · 첫관측일 MIN → 노출일수 재산정
mu    = 출고합 / 노출일수
sigma = sqrt(제곱합/노출일수 − mu²)

inventory_status.py_prepare_series_status 와 같은 식입니다. 합성 입력으로 손계산 대조했습니다 — mu 0.27360 / sigma 1.54516 일치.

알려진 한계: 부서가 2개 이상인 조합에서 sigma 는 부서간 공분산을 무시합니다(SS 과소 방향). 해당 비율을 실행 때 출력합니다.

기존 스크립트의 오매핑 위험을 막았습니다

기존 handoff 는 익명 기관코드 ↔ institution_id정렬-zip 으로 맞추는데 개수 검사가 없습니다. AI 산출물은 3,530 기관이고 전체는 3,598 이라, 산출물만으로 zip 하면 전부 밀립니다. 개수가 어긋나면 중단하도록 했고, 2열 매핑(INST_CODE_MAP)을 주면 정렬 가정 자체를 없앨 수 있습니다.

2) apply_inventory_policy.py — mu/sigma 교체 + SS/ROP 재계산

mu 만 갈아끼우면 안 됩니다. apply_supply_risk_policy.py 는 주석에 "z_used/lead_time_used/SS/ROP는 안 건드림"이라고 명시돼 있어, SS/ROP 가 옛 mu 기준으로 남습니다.

실측(헤모포스정 WMD0317947):

mu 63.12 · mu_corrected 70.18 · sigma 219.96 · z_used 2.05 · L 16.2
저장 ss 1,817.7 / rop 2,843.5  →  (rop − ss)/L = 63.32  = mu 기준

ROP 는 mu 로, 화면 소진곡선은 mu_corrected 로 그려져 기준이 어긋나 있었습니다(frontend#43 에서 화면 쪽 임시 대응 완료). mu 만 바꾸고 SS/ROP 를 두면 같은 어긋남이 재발합니다.

ai_policy_adapter.py 에 문서화된 계약과 같은 식으로 재계산합니다.

SS = z*sigma*sqrt(L) · ROP = mu*L + SS · target = mu*(L+1) + SS

z_used · lead_time_used · supply_risk_level · status 는 건드리지 않습니다. 발주 억제는 AI 정책대로 DORMANT/NOT_OPERATED → 0.

안전장치: DRY_RUN 기본 1 · 조인율 90% 미만이면 중단 · 반영 후 (rop−ss)/Lmu 불일치 행수 출력(0 이어야 함).

실행에 필요한 것 (부탁드립니다)

outputs/.gitignore 라 산출물이 레포에 없어서 아직 돌리지 못했습니다. 둘 중 하나만 주시면 바로 돌려서 결과를 붙이겠습니다.

  1. outputs/stock_inventory_status.csv (또는 접근 경로)
  2. 익명 기관코드 → institution_id 2열 매핑 — 있으면 정렬-zip 을 영구히 안 써도 됩니다

기존 data/handoff/mu_forecast.csv · zero_stock_reason.csv 처럼 handoff CSV 를 커밋해주시는 것도 같은 효과입니다.

알려진 편차 — backend DDL 필요

DATA_MISSING / stale 은 AI 정책상 권고량 null 이지만 inventory.order_recommendationNOT NULL 입니다. 지금은 0 으로 넣고 건수를 보고합니다. null 계약을 지키려면 backend 스키마 변경이 필요합니다.

Type

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

PR Checklist

  • Commit Message Convention을 준수했습니다.
  • Code Convention을 준수했습니다.
  • 변경한 기능이 잘 동작하는지 테스트했습니다. — 문법·가드·수치 검증은 했으나 실산출물 미확보로 전체 실행 미완

DB 의 mu/sigma/SS/ROP 는 AI 산출물이 아니라 backend 스톱갭
(scripts/fix_inventory_stats.py)이 계산한 값이다. 그래서 인공 바닥값이 그대로
남아 있다 — 운영 DB 실측 2026-07-30 기준 mu<=0.5 가 296,537 / 409,459행(72.4%),
sigma<=0.1 이 201,814행. AI 정책은 이미 바닥값을 0 으로 뒀는데(inventory-status-v1.1,
mean_daily_usage_floor 0.0) DB 로 오는 경로가 없었다.

두 쪽 키 단위가 달라 그대로 붓지 못한다.
  AI : (institution_code, department, item_code)  416,128 시계열
  DB : (institution_id,              standard_code) 409,459행 — 부서 축 없음

compute_inventory_policy_handoff.py — 부서 접기
  AI 산출물이 시계열별 모멘트 합을 들고 있어 근사 없이 재계산된다.
    출고합·제곱합 SUM · 첫관측일 MIN → 노출일수 재산정
    mu = 출고합/노출일수 · sigma = sqrt(제곱합/노출일수 - mu^2)
  inventory_status.py 의 _prepare_series_status 와 같은 식이다.
  합성 입력으로 손계산 대조 통과(mu 0.27360 / sigma 1.54516).

  sigma 는 부서 2개 이상 조합에서 부서간 공분산을 무시한다(SS 과소 방향). 해당 비율을
  실행 때 출력하도록 했다.

  ⚠️ 기관 매핑 오매핑 차단을 추가했다. 기존 handoff 스크립트는 익명코드↔institution_id
  를 정렬-zip 으로 맞추면서 개수 검사가 없었다. AI 산출물은 3,530 기관인데 전체는
  3,598 이라 산출물만으로 zip 하면 전부 밀린다. 개수가 어긋나면 중단한다.
  2열 매핑(INST_CODE_MAP)을 주면 정렬 가정 자체를 없앨 수 있다.

apply_inventory_policy.py — mu/sigma 교체 + SS/ROP 재계산
  mu 만 갈아끼우면 안 된다. 기존 apply_* 는 컬럼만 덧칠하고
  apply_supply_risk_policy.py 는 "z_used/lead_time_used/SS/ROP는 안 건드림"이라고
  명시돼 있어, SS/ROP 가 옛 mu 기준으로 남는다.

  실측(헤모포스정 WMD0317947): mu 63.12 · mu_corrected 70.18 · z 2.05 · L 16.2 ·
  저장 ss 1,817.7 / rop 2,843.5 → (rop-ss)/L = 63.32 = mu 기준.
  ROP 는 mu 로, 화면 소진곡선은 mu_corrected 로 그려져 기준이 어긋나 있었다
  (frontend#43 에서 화면 쪽 임시 대응).

  ai_policy_adapter.py 에 문서화된 계약과 같은 식으로 재계산한다.
    SS = z*sigma*sqrt(L) · ROP = mu*L+SS · target = mu*(L+1)+SS
  z_used·lead_time_used·supply_risk_level·status 는 건드리지 않는다.
  발주 억제는 AI 정책대로 DORMANT/NOT_OPERATED → 0.

  안전장치: DRY_RUN 기본 1, 조인율 90% 미만이면 중단,
  반영 후 (rop-ss)/L 과 mu 의 불일치 행수를 출력(0 이어야 한다).

알려진 편차: DATA_MISSING/stale 은 정책상 권고량 null 이지만
inventory.order_recommendation 이 NOT NULL 이라 0 으로 넣고 건수를 보고한다.
null 계약을 지키려면 backend DDL 변경이 필요하다.

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

Copy link
Copy Markdown
Contributor Author

스키마 제약을 푸는 PR 을 올렸습니다 — DGU-TeamLex/backend#72. 머지되면 이 PR 의 DATA_MISSING/STALE 을 정책대로 NULL 로 내보내도록 바꾸겠습니다(지금은 0 + 건수 보고). order_suppress_reason·raw_order_recommendation·mu_is_floored·sigma_is_floored 컬럼도 함께 생기니 handoff 에 이미 담고 있는 값을 그대로 적재할 수 있습니다.

@choigod1023

Copy link
Copy Markdown
Contributor Author

이 PR 의 SS/ROP 재계산 부분을 보류하려 합니다. 확인 없이 진행한 부분이라 먼저 여쭙니다.

apply_inventory_policy.pymu/sigma 갱신과 함께 SS/ROP 를 SS = z_used·σ·√L 로 재계산하도록 짜여 있습니다. "mu 만 바꾸면 ROP 가 옛 mu 기준으로 남는다"는 문제 자체는 실측으로 확인했지만, 고치는 방향을 backend 식으로 잡았습니다.

그런데 src/modeling/inventory_policy.py 는 다른 모형을 씁니다 — 정기검토(검토주기 30일 + L), SS = 보호기간수요 × 0.20, σ 미사용. 모형이 정해지지 않은 상태에서 한쪽 식으로 운영 DB 를 덮어쓰는 건 위험하다고 판단했습니다. 상세는 #54 에 정리했습니다.

제안: 이 PR 범위를 mu · sigma · mu_forecast · demand_class · zero_stock_reason 갱신까지로 줄이고, SS/ROP/발주권고는 모형이 확정된 뒤 별도 PR 로 다루겠습니다.

다르게 보시면 알려주세요. 지금은 손대지 않고 답을 기다리겠습니다.

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.

1 participant