Commit Graph

7 Commits

Author SHA1 Message Date
민헌
a2c299aa14 refactor(anchoring): 도메인 이름 전면 개편 — adjustments·anchoring_value/price·price_range·sample 용어 통일
용어 체계: 값=anchoring_value(정수‰)·가격=anchoring_price·조정=adjustment·구간=price_range·표본=sample

- DB: rate_adjustments→anchoring.adjustments (id→adjustment_id, price_bracket_index→price_range_index,
  nego_count→sample_count, anchor_rate_before/after→anchoring_value_before/after,
  consumed_session_ids→used_session_ids)
- sessions: target_anchoring_price→anchoring_price, anchor_rate_permille→anchoring_value,
  last_offered_price→last_offer_price, anchoring_adjustment_id→used_by_adjustment_id
- 뷰: rate_history/current_rates→value_history/current_values, delta_permille→value_change
- 코드: calc_price_range_index·calc_anchoring_price·evaluate_samples·get_current_value·
  get_latest_adjusted_value·get_current_anchoring_value·fetch_current_values·get_base_anchoring_value·
  Adjustment(ORM)·update_last_offer_price, 상수 ANCHORING_VALUE_MIN/MAX·ADJUSTMENT_STEP·
  PRICE_RANGE_COUNT/INDEX_MAX, 배치 로그 키 bracket=→price_range=
- API: negodata protocol 필드 target_anchoring_price→anchoring_price (front 생성 모델·컴포넌트 동반)
- 기존 DB 마이그레이션 신설: schedules/anchoring/migrations/20260706_rename_anchoring.sql
  (멱등 DO 블록 — 테이블·컬럼·뷰·인덱스·PK 제약. 코드 배포와 동시 적용 필요)
- postgres-init 01·04, 문서 6종 동기화
- 실배포 전 수정 포함: main.py argparse 화(--dry-run 단독·오타 플래그 기동 전 차단),
  박제 정합식 calc_anchoring_price 재사용, clamped 지표가 실제 포화만 집계(경계값 유지 제외)

주의: sessions.anchoring_value(정수‰)와 quotation_settings.anchoring_value(구 float 비율)는
같은 이름·다른 단위 — 구 컬럼은 미변경.

검증: 모듈 20·negodata 50·backend 57 테스트 통과, front tsc·vite build 통과,
로컬 DB 마이그레이션 적용 후 배치 dry-run·상주 기동·양 서버 부팅 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:17:01 +09:00
민헌
b4d6cf817b feat(anchoring): dry-run 모드·박제 정합 감시·전환기 점프 절차 — 실배포 준비
종합 피드백에서 남긴 마지막 개선 3종:

- --once --dry-run: 판정·예상 조정(조정예정 라인)·제외 예정 건수를 로그로만
  보고 DB/Redis 를 일절 변경하지 않는 예행 연습 — 첫 운영 실행(레거시 전량
  판정·마킹) 전에 규모를 눈으로 확인하는 안전장치. status=dry_run, 종료코드 0
- 박제 정합 감시: 스캔 시 정수식 tp×(1000−rate)//1000 과 박제 anchor 를 대조,
  불일치 시 WARN + 요약에 snapshot_mismatch — negodata 이식 오류(float 잔재·
  칸 해석 오류)를 적용 첫 주에 자동 감지. 전환기(rate 미박제)엔 자동 스킵
- 전환기 점프 절차화: 인수인계 적용 순서에 "negodata 적용 직전 current_rates
  분포 확인 → 점프 감수/이력 리셋 정책 결정" 단계 삽입 + TODO 등재

테스트 2종 추가(dry-run 무변경·정합 WARN) — 모듈 20개·backend 57개 통과.
리허설 완료: dry-run 예상과 실제 실행 결과 일치 확인(제외 3·조정 1칸 10‰→30‰).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:13:28 +09:00
민헌
ba996f47c1 fix(anchoring): 3방향 적대 리뷰 반영 — 테스트 격리·Redis 방어·운영 견고화
모듈·backend·문서 3개 관점의 적대 리뷰에서 확인된 결함 일괄 수정:

[모듈]
- 배치에 company_ids 스코프 옵션 추가 — 통합 테스트가 공유 dev DB 의 실세션을
  소비/마킹하던 문제 해소(테스트는 시드 회사로 한정), 표적 수동 실행 옵션 겸용
- Redis 방어: compose 포트를 127.0.0.1 바인딩(무인증 공개 차단), get_rate 에
  범위([10,200]) 검증 — 오염 캐시값은 미스 취급 후 자가 교정, 미스 백필은 SET NX
  (배치가 방금 쓴 새 값을 구값으로 덮는 write-after-read 경합 방지)
- 배치: Redis ping 후 re-SET(다운 시 셀×timeout 지연 없이 즉시 스킵), 스캔 조인
  ON 절에 quotations/items deleted 필터(철회 거래를 학습에서 배제), 제외 마킹을
  청크별 커밋(레거시 대량 첫 실행의 장시간 단일 트랜잭션 방지)
- main: SIGTERM/SIGINT 핸들러(docker stop 시 정리 로직 보장), --once 부분 실패 시
  종료코드 1(런북/cron 감지 가능)

[backend]
- finalize_session·update_last_offered_price 에 status=IN_PROGRESS 가드 —
  negodata 일괄마감/중복 전송 경합이 종료된 세션을 되살리거나 가격 흔적을
  사후 변경하는 것 차단(파생 판정 결정성 보호)
- 신규 DB 부트스트랩: sessions 3컬럼을 postgres-init/01-schema·04-alter 에도
  반영(backend 가 모듈 DDL 없이 기동) — anchoring 스키마 자체는 모듈 소유 유지
- 낡은 주석 정리(agent_client·quotation_settings 의 구 앵커 산출 서술)

[테스트·문서]
- 신규 테스트: 격주 게이트 골든(ISO 주차), supplier_type NULL, 가격 제시율 0%
  WARN — 모듈 18개·backend 57개 통과
- 문서 정합 감사 20건 반영: 잔존 33,334/노출 문구 제거, §10 SQL 을 실제 코드
  (LEFT JOIN+deleted)와 일치, §11 자동/수동 검증 구분, FastAPI 오기 제거,
  인수인계 reader 시그니처(db 인자), TODO 백로그 5건 기록

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:55:46 +09:00
민헌
a132dac57a feat(anchoring): 조회용 뷰 2종 신설 — records 테이블은 검토 후 기각 (TODO 2)
요구(회사별 앵커링 값 업데이트 리스트업 + 이전 값 판별)는 rate_adjustments
한 행에 anchor_rate_before→after 가 박제되어 이미 충족 — 신규 테이블은 동일
정보의 사본만 만들므로 기각하고, 조회를 제품화하는 파생 뷰로 해결:

- anchoring.rate_history: 값 변경 이력 리스트업(이전→새 값, delta_permille,
  success_rate, created_at)
- anchoring.current_rates: 칸별 현재값(최신 조정 행 — 없는 칸 = 시작값 10‰)

뷰는 상태가 없어 오염·재구축 이슈 자체가 없고 append-only 보호 대상 아님.
통합 테스트에 뷰 검증 추가(이력 before/after·성공률, 현재값). 운영 문서 §8
쿼리를 뷰 기반으로 단순화, TODO 과제 2 종결(대시보드 페이징 요구 시 스냅샷
테이블 승격 재검토 명시).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:22:25 +09:00
민헌
e1940c69aa feat(anchoring): 가격구간을 자릿수 계단식 사다리(46칸)로 재설계 (TODO 1)
균일 3,000원 × 33,334칸 → 자릿수 사다리 46칸. 폭 = 구간 상한의 10%
(선행 자릿수 밴드): [0, 1,000) 통일 1칸 + 자릿수(1천~1억, 5개)당 9칸
— "1천 원대·2천 원대 … 9천만 원대". 1억 초과는 마지막 인덱스(45) 클램프.

- 고가 구간의 무의미한 3,000원 해상도 제거 → 칸당 표본 밀도 대폭 개선
  (희소 칸 이월 문제 구조적 완화), 정적 테이블 2MB → 3KB
- 사다리 단일 소스 = constants.UPPER_BOUNDS(생성식), json 은 기동 시 대조 검증
- calc_bracket_index: // 3000 → bisect_right (좌폐우개 경계 규약 동일 유지)
- 조정 이력 0건 시점 적용 — 인덱스 재매핑/마이그레이션 없음. DB 스키마 무변경
- 골든 테스트 재작성(경계·자릿수 진입·클램프) + 사다리 형태 검증 추가 — 16 passed
- 문서 5종·인수인계·README·스키마 주석 동기화, TODO 과제 1 완료 처리

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:08:23 +09:00
민헌
e33754961b feat(anchoring): 운영 로그 개선 — run_id·칸별 조정 상세·회사별 요약
장기 운영 관점 자체 점검에서 나온 6개 구멍 반영:

- 회차 추적: 모든 배치 라인에 [batch {run_id}] 태그 + 시작 로그(ISO 주차·force)
  + 상주 기동 시 다음 실행 예정 시각 출력
- 회사별 구분: 칸별 조정 상세(company= type= bracket= n= 성공= before‰→after‰
  adj_id=)와 회사요약(테넌트당 1줄: 평가/상승/유지/하락/이월/실패/제외) —
  grep company=<uuid> 로 테넌트 단위 추적, adj_id 로 DB 행 교차 확인
- 경보 연결: failed_cells>0 이면 종료 요약 WARNING 승격, Redis 실패는 연산별
  처음 5건만 WARN 후 누계 요약(폭주 억제)
- 시간대: 로그 타임스탬프를 컨테이너 TZ 무관 KST(+0900) 고정, slim 컨테이너
  zoneinfo 보장용 tzdata 의존 추가
- 스케줄 가시성: apscheduler 로거를 동일 핸들러에 연결(misfire 등 유실 방지)
- 로테이션: compose 에 json-file 10MB×5 설정(디스크 보호)

docs/개발용.md §8 로그 규약, README 로그 확인법 추가. 테스트에 로그 규약
검증(run_id 태그·조정 라인·회사요약) 포함 — 15 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:46:16 +09:00
민헌
f29e9723b0 feat(anchoring): 앵커링 값 자동 조정 자립 모듈 신설 (v1.2)
schedules/anchoring — backend 를 import 하지 않는 독립 컨테이너 배치 서비스.
회사 × 협력사유형(1유통/2제조/3총판) × 가격구간(3,000원, 33,334칸)별 앵커링
값(정수 천분율)을 격주 토 00:00 KST 에 협상 성공률로 자동 조정한다.

- 판정 = "가격 흔적" 기준: last_offered_price 가 있는 종료 재협상만 표본,
  DONE & bid ≤ 박제 앵커만 성공, 나머지(초과 합의·결렬·가격 쓰고 이탈) 실패.
  앵커는 비노출(엔진 내부 체결 임계) — agent 무변경
- 저장 = anchoring.rate_adjustments 1개(append-only, consumed_session_ids 박제),
  소비 경계 = sessions.anchoring_adjustment_id 마킹(멱등·이월). DDL 은 모듈
  소유(schema.sql, sessions 3컬럼 ALTER 포함)
- 안정성: 조정 INSERT+마킹 한 트랜잭션 + rowcount 불일치 전체 롤백,
  Redis TTL 7일 + 매주 조정 칸 re-SET, socket timeout 0.3s, DB 폴백,
  가격 제시율 0% WARN, --once 수동 캐치업
- 정적 기본 테이블(전 구간 10‰, 상한 정확히 1억·초과분 마지막 인덱스 클램프)
  기동 검증 실패 시 기동 중단
- 전체 async(SQLAlchemy+asyncpg, redis.asyncio) — negodata 가 reader 를 그대로
  이식 가능(docs/인수인계.md). 최종 문서 docs/{개발용,기획용,워크플로우}.md
- 테스트 15종: 골든 벡터(§11) + DB 통합(멱등·이월·격리·rowcount 롤백)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:29:46 +09:00