Skip to content

feat(schema): 발주권고 NULL 허용 + 감사 컬럼 · 미사용 인덱스 2개 제거(36MB 회수) - #72

Merged
choigod1023 merged 3 commits into
mainfrom
feat/order-reco-nullable
Aug 7, 2026
Merged

feat(schema): 발주권고 NULL 허용 + 감사 컬럼 · 미사용 인덱스 2개 제거(36MB 회수)#72
choigod1023 merged 3 commits into
mainfrom
feat/order-reco-nullable

Conversation

@choigod1023

Copy link
Copy Markdown
Contributor

Description

ai 정책(inventory-status-v1.1)은 판단 불가 건에 권고량 NULL 을 요구합니다.

상황 정책상 권고량 의미
DATA_MISSING (재고 기재 누락 의심) NULL 산출 불가
STALE (관측이 오래됨) NULL 산출 불가
DORMANT / NOT_OPERATED 0 발주 대상 아님

지금은 order_recommendationNOT NULL 이라 이 둘을 모두 0으로 뭉갭니다. 화면에서 "발주 안 함"과 "모르겠음"이 같아집니다. 담당자가 0을 보고 넘기는데 실제로는 재고 기재가 누락돼 판단을 못 한 건일 수 있습니다.

DGU-TeamLex/ai#53 이 이 제약 때문에 정책을 지키지 못하고 0으로 내보내고 있습니다.

스키마 변경

ALTER TABLE inventory ALTER COLUMN order_recommendation DROP NOT NULL;
ALTER TABLE inventory ADD COLUMN IF NOT EXISTS raw_order_recommendation INTEGER;
ALTER TABLE inventory ADD COLUMN IF NOT EXISTS order_suppress_reason TEXT;
ALTER TABLE inventory ADD COLUMN IF NOT EXISTS mu_is_floored BOOLEAN;
ALTER TABLE inventory ADD COLUMN IF NOT EXISTS sigma_is_floored BOOLEAN;
  • raw_order_recommendation · order_suppress_reason — ai 정책이 "원시 권고량과 억제 여부·사유를 감사용으로 보존"하도록 요구합니다. 결과만 덮어쓰면 왜 0/NULL 인지 되짚을 수 없습니다.
  • mu_is_floored · sigma_is_floored — 같은 정책이 이 플래그를 판정·예측·API 에 노출하도록 합니다. 운영 DB 는 지금 mu<=0.5296,537 / 409,459행(72.4%) 인데, 그게 실측값인지 바닥값인지 구분할 수단이 없습니다.

CREATE TABLE IF NOT EXISTS 는 기존 테이블을 바꾸지 않으므로 멱등 ALTERschema.sql 끝에 따로 뒀습니다(seed_db.py 가 파일을 그대로 실행).

함께 고친 것 — 이대로 두면 깨집니다

db/queries.py — 파이썬에서 None > 0TypeError 입니다.

- sum(1 for r in inv if r["orderRecommendation"] > 0)
+ sum(1 for r in inv if (r["orderRecommendation"] or 0) > 0)

routers/graphql_schema.pyorder_recommendation: int 는 strawberry 가 Int! 로 노출해 NULL 반환 시 스키마 위반입니다. Optional[int]3곳 변경.

그대로 둔 것 — NULL 안전 확인

위치 동작 판단
count(*) FILTER (WHERE order_recommendation > 0) NULL 미집계 "모르겠음"을 발주 필요로 세지 않는 게 맞음
order_recommendations()> 0 필터 NULL 제외 의도된 동작
ORDER BY order_recommendation DESC 해당 쿼리에 > 0 필터가 있어 NULL 없음 영향 없음

응답 노출

orderSuppressReason · rawOrderRecommendation · muIsFloored · sigmaIsFloored 를 재고정책 응답에 추가했습니다. 프론트가 0 과 NULL 을 구분해 표기할 근거가 필요합니다.

To Reviewer

운영 DB 에 트랜잭션으로 DDL 을 적용하고 롤백해 검증했습니다.

멱등성(동일 DDL 2회 실행)      통과
NULL 삽입                      21행 성공
FILTER 집계                    157,456 정상
롤백 후 is_nullable             NO 로 원복 확인

db/queries.py · routers/graphql_schema.py 문법 검사 통과.

후속으로 프론트 수정이 필요합니다. frontend/app/inventory/page.tsxr.orderRecommendation > 0 으로 분기하는데, JS 에서 null > 0false 라 예외는 안 나지만 회색 "0" 으로 표시됩니다. 지금 고치려는 "발주 안 함 vs 모르겠음" 혼동이 화면에 그대로 남습니다. 이 PR 머지 후 별도로 올리겠습니다.

Type

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

PR Checklist

  • Commit Message Convention을 준수했습니다.
  • Code Convention을 준수했습니다.
  • 변경한 기능이 잘 동작하는지 테스트했습니다.

ai 정책(inventory-status-v1.1)은 판단 불가 건에 권고량 NULL 을 요구한다.

  DATA_MISSING(재고 기재 누락 의심) · STALE(관측이 오래됨) → 산출 불가 = NULL
  DORMANT / NOT_OPERATED                                → 발주 대상 아님 = 0

지금은 order_recommendation 이 NOT NULL 이라 둘을 0 으로 뭉갠다. 그러면 화면에서
"발주 안 함"과 "모르겠음"이 같아진다. 담당자가 0 을 보고 발주를 넘기는데, 실제로는
재고 기재가 누락돼 판단을 못 한 건일 수 있다.

스키마
- order_recommendation 을 nullable 로
- raw_order_recommendation · order_suppress_reason 추가 — ai 정책이 "원시 권고량과
  억제 여부·사유를 감사용으로 보존"하도록 요구한다. 결과만 덮어쓰면 왜 0/NULL 인지
  되짚을 수 없다
- mu_is_floored · sigma_is_floored 추가 — 같은 정책이 이 플래그를 판정·예측·API 에
  노출하도록 한다. 운영 DB 는 지금 mu<=0.5 가 296,537/409,459행(72.4%)인데 그게
  실측값인지 바닥값인지 구분할 수단이 없다
- 기존 DB 용 멱등 DDL 을 파일 끝에 추가 (CREATE TABLE IF NOT EXISTS 는 기존 테이블을
  바꾸지 않으므로 ALTER 를 따로 둔다)

깨지는 곳을 함께 고쳤다
- db/queries.py: sum(1 for r in inv if r["orderRecommendation"] > 0) 은 파이썬에서
  None > 0 → TypeError 다. (r[...] or 0) 으로 바꿨다
- routers/graphql_schema.py: order_recommendation: int 는 strawberry 가 Int! 로
  노출해 NULL 반환 시 스키마 위반이다. Optional[int] 로 3곳 변경

NULL 안전한 것은 그대로 뒀다
- count(*) FILTER (WHERE order_recommendation > 0) → NULL 은 미집계. "모르겠음"을
  발주 필요로 세지 않는 게 맞다
- order_recommendations() 의 > 0 필터·ORDER BY → NULL 제외가 의도된 동작

응답에 orderSuppressReason / rawOrderRecommendation / muIsFloored / sigmaIsFloored
를 노출한다. 프론트가 0 과 NULL 을 구분해 표기할 근거가 필요하다.

검증: 운영 DB 에 트랜잭션으로 DDL 적용 후 롤백 — 멱등성(2회 실행) 통과,
NULL 삽입 21행 성공, FILTER 집계 157,456 정상, 롤백 후 is_nullable=NO 원복 확인.
db/queries.py · routers/graphql_schema.py 문법 검사 통과.

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

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
backend Ready Ready Preview Aug 7, 2026 2:47am

Request Review

Neon 무료 한도 512MB 중 354MB 를 쓰고 있었고, 그 122MB 가 inventory 인덱스 6개였다.
실측 스캔 횟수를 보니 둘은 사실상 놀고 있었다.

  idx_inventory_is_medical     19MB  스캔    6회   ← boolean, 값 2종
  idx_inventory_demand_class   16MB  스캔   10회   ← text, 값 3종
  ─ 비교 ─
  inventory_institution_id_standard_code_key  34MB  스캔 192,131회
  idx_inventory_institution                   12MB  스캔   4,183회

둘 다 저카디널리티 컬럼이라 인덱스 이득이 거의 없고, db/queries.py 에서 WHERE 절로
쓰이는 곳이 하나도 없다(전수 확인). 운영 DB 에서 DROP INDEX CONCURRENTLY 로 제거했다.

  DB 크기            354MB → 318MB  (36MB 회수)
  inventory 인덱스   122MB →  87MB
  남은 여유                  193.6MB

idx_inventory_is_medical 은 커밋된 코드에 생성문이 없어(수동 생성분) DB 에서만 지웠다.
idx_inventory_demand_class 는 이 파일에 있었으므로 함께 제거한다 — 안 그러면
seed_db.py 가 schema.sql 을 적용할 때 되살아난다.

되살리는 문장은 주석으로 남겼다. 롤백 스크립트도 함께 보관했다.

검증: 삭제 후 운영 API 3개 확인 — /inventory-policy(500행) · /dashboard/central ·
/order-recommendations 모두 HTTP 200, 응답 정상.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@choigod1023 choigod1023 changed the title feat(schema): 발주권고 NULL 허용 + 억제 사유·바닥값 플래그 컬럼 (ai#52) feat(schema): 발주권고 NULL 허용 + 감사 컬럼 · 미사용 인덱스 2개 제거(36MB 회수) Jul 30, 2026
@choigod1023

Copy link
Copy Markdown
Contributor Author

커밋 추가 — 미사용 인덱스 2개 제거 (이미 운영 DB 반영됨)

Neon 무료 한도 512MB 중 354MB 를 쓰고 있었고, 그 중 122MB 가 inventory 인덱스 6개였습니다. 실측 스캔 횟수를 보니 둘은 사실상 놀고 있었습니다.

인덱스 크기 스캔 횟수
idx_inventory_is_medical 19 MB 6
idx_inventory_demand_class 16 MB 10
inventory_institution_id_standard_code_key 34 MB 192,131
idx_inventory_institution 12 MB 4,183

둘 다 저카디널리티 컬럼(is_medical boolean 2종 / demand_class 3종)이라 인덱스 이득이 거의 없고, db/queries.py 에서 WHERE 절로 쓰이는 곳이 하나도 없습니다(전수 확인).

DROP INDEX CONCURRENTLY 로 운영 DB 에서 제거했습니다(락 없음).

DB 크기            354MB → 318MB   (36MB 회수)
inventory 인덱스   122MB →  87MB
남은 여유                  193.6MB

idx_inventory_is_medical 은 커밋된 코드에 생성문이 없어(수동 생성분) DB 에서만 지웠습니다. idx_inventory_demand_classdb/schema.sql 에 있었으므로 이 커밋에서 함께 제거합니다 — 안 그러면 seed_db.py 가 스키마를 적용할 때 되살아납니다.

되살리는 문장은 주석으로 남겼습니다.

검증: 삭제 후 운영 API 3개 — /inventory-policy(500행) · /dashboard/central · /order-recommendations 모두 HTTP 200, 응답 정상.

@choigod1023
choigod1023 merged commit cb186a9 into main Aug 7, 2026
1 of 2 checks passed
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