diff --git a/schedules/anchoring/README.md b/schedules/anchoring/README.md index d8bb94f..da8b1eb 100644 --- a/schedules/anchoring/README.md +++ b/schedules/anchoring/README.md @@ -14,7 +14,7 @@ |---|---| | 소유(쓰기) | `anchoring.rate_adjustments`(append-only 조정 이력), `sessions.anchoring_adjustment_id`(소비 마킹 — 이 컬럼만), Redis `anchor:*` 키 | | 읽기 전용 | `negotiation.sessions`(박제 컬럼), `quotation.quotations.supplier_type`, `partner.items.company_id` | -| 소비자 | negodata 가 `reader.get_anchor_rate` 이식 + 이 Redis 를 참조해 세션 생성 시 앵커가 박제 (인수인계) | +| 소비자 | negodata 가 reader 이식판으로 세션 생성 시 앵커가 박제 — **적용 완료(2026-07-04), 이식판은 Redis 미사용**(`current_rates` 뷰 직조회, 인수인계 §1) | ## 구조 diff --git a/schedules/anchoring/docs/개발용.md b/schedules/anchoring/docs/개발용.md index f6591d0..ee20457 100644 --- a/schedules/anchoring/docs/개발용.md +++ b/schedules/anchoring/docs/개발용.md @@ -377,7 +377,7 @@ anchoring.current_rates -- 칸별 현재값(최신 조정 행). 여기 없는 - 캐시는 파생값이다. Redis flush가 발생해도 조정 이력에서 완전 복구 가능해야 한다 (MUST). - ⚠️ **stale 키는 "미스"가 나지 않는다**: 배치의 DB 커밋 후 SET 실패, 또는 Redis 가 옛 스냅샷(RDB/AOF)으로 재기동하면 옛 rate 가 계속 서빙된다. 그래서 TTL + 주간 re-SET 이중 방어가 MUST 다. - 멀티 인스턴스 동시 미스 → 결과 동일(최신 조정 행은 하나)하므로 락 불필요. -- 클라이언트: `redis.asyncio` — 사용 주체는 **anchoring 서비스**(배치 SET/re-SET)와 **negodata**(reader GET, 인수인계). backend 는 Redis 를 쓰지 않는다. 설정은 모듈 `config.toml` + `REDIS_HOST/PORT/PASSWORD` env 오버라이드. Redis 인스턴스는 모듈 docker-compose 에 동봉(negodata 가 같은 인스턴스를 바라봄). +- 클라이언트: `redis.asyncio` — 사용 주체는 **anchoring 서비스**(배치 SET/re-SET)뿐이다. backend 는 Redis 를 쓰지 않고, **negodata 도 쓰지 않는다**(2026-07-04 적용된 이식판 reader 는 `current_rates` 뷰 직조회 — 인수인계 §1. Redis 캐시 전체 제거가 후속 백로그로 확정됨). 설정은 모듈 `config.toml` + `REDIS_HOST/PORT/PASSWORD` env 오버라이드. - 보안: 무인증 Redis 를 외부 네트워크에 노출 **MUST NOT** — 오염된 rate 는 실제 제안가를 왜곡한다. 모듈 compose 는 포트를 `127.0.0.1` 로만 바인딩한다. negodata 가 다른 호스트에서 접근해야 하는 배치라면 인증(requirepass)·네트워크 격리 적용 후 개방한다(TODO 백로그). --- @@ -442,7 +442,10 @@ anchoring.current_rates -- 칸별 현재값(최신 조정 행). 여기 없는 ### 9.1 견적/세션 생성 — negodata (인수인계 대상) -현재 negodata `_build_quotation`은 세션 생성 시 `target_anchoring_price`를 구 방식으로 채운다 +> ✅ 2026-07-04 적용 완료(인수인계.md §1 참조). 적용된 이식판은 아래 2번의 Redis GET/SET 없이 +> `current_rates` 뷰 직조회 → 정적 테이블 폴백으로 동작한다(단순화 결정). + +구(舊) negodata `_build_quotation`은 세션 생성 시 `target_anchoring_price`를 구 방식으로 채웠다 (신규: `int(tp * (1 - quotation_settings.anchoring_value))` float 계산 / 재생성: 직전 라운드 값 상속). 새 앵커링 모듈 전달 후 아래로 교체된다: diff --git a/schedules/anchoring/docs/인수인계.md b/schedules/anchoring/docs/인수인계.md index 3869878..dca76bd 100644 --- a/schedules/anchoring/docs/인수인계.md +++ b/schedules/anchoring/docs/인수인계.md @@ -22,6 +22,13 @@ ## 1. negodata 변경 (견적 생성 측) +> ✅ **적용 완료 (2026-07-04)** — negodata 담당자 승인 하에 backend 담당(민헌)이 이 절을 직접 적용했다. +> +> - 이식 위치: `negodata/backend/common/anchoring/` (constants·base_table·service 는 읽기 경로 발췌, reader 는 이식판) + `_build_quotation` 앵커 산출 교체 + 재생성 앵커 상속 폐지 + `sessions.anchor_rate_permille` 모델 매핑 +> - **이식판 reader 는 Redis 캐시를 쓰지 않는다**: `anchoring.current_rates` 뷰 단일 쿼리 → 실패·무이력 시 정적 테이블 폴백. (2026-07-03 단순화 결정 — 조회가 견적 생성 시 1회뿐이라 캐시 불필요. 모듈 쪽 Redis 제거는 후속 백로그로 진행) +> - 검증: negodata 테스트 스위트 50종 통과 — 앵커링 신설 5종(스키마 부재 폴백·칸별 조정 반영·유형 미지정 폴백·재생성 앵커 재계산·`calc_bracket_index` 경계 골든 벡터) 포함 +> - 전환기 점프 확인(§3-③): 적용 시점 `rate_adjustments` 0건 → 점프 없음(시작값 10‰ = 구 기본 `anchoring_value` 0.01 과 동일) + ### 1.1 변경 대상 `negodata/backend/services/quotation_service.py` — `_build_quotation()` 의 세션 생성 루프(현재 448~470행 부근)와 `regenerate` 경로의 상속 로직(현재 319행 부근). @@ -86,7 +93,7 @@ sessions(..., target_anchoring_price=ap, anchor_rate_permille=rate, ...) ② anchoring 서비스 기동 (schedules/anchoring 컨테이너 — 격주 배치·Redis 캐시 시작) + backend 배포 (마지막 제시가 기록·박제값 소비 — 이 시점부터 표본·조정이 쌓이기 시작) ③ 전환기 점프 확인 (negodata 적용 직전): - SELECT max(anchor_rate_after) FROM anchoring.current_rates; + SELECT max(anchor_rate_permille) FROM anchoring.current_rates; — ②~③ 사이에 학습이 진행되므로, 적용 순간 앵커가 학습된 rate 로 한 번에 이동한다 ("조정일당 한 계단" 원칙이 이 순간만 예외). 값이 크게 벌어져 있으면 점프 감수 여부 또는 이력 리셋을 정책 결정 후 진행.