o2o-negosium-original/negodata/backend/common/anchoring/reader.py
민헌 5f9dec66cc refactor(anchoring): DDL·compose 루트 통합 — postgres-init/05 신설, 모듈 schema.sql·docker-compose 제거
- postgres-init/05-anchoring-schema.sql 신설(02-learning-schema 스타일) — anchoring 스키마·
  adjustments·인덱스 2종(부분 인덱스 포함)·뷰 2종. sessions 컬럼은 01/04 소관으로 명시
- 루트 docker-compose.yml 에 anchoring + anchoring-redis 서비스 추가(기존 스타일),
  헤더 서비스 목록·DB 준비 절차 갱신, 모듈 compose 의 로그 로테이션(10MB×5) 이관
- 모듈 schema.sql·docker-compose.yml 삭제 — DDL 단일 원본은 postgres-init/05
- tests/conftest 부트스트랩 경로를 05 로 교체, 문서 4종·주석 포인터 갱신
  (인수인계의 낡은 'negodata 가 redis 참조' 문구도 무Redis 현실로 교정)
- 검증: 모듈 20·negodata 50 테스트 통과, docker compose config 정상

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:37:13 +09:00

56 lines
2.7 KiB
Python

"""현재 앵커링 값 조회(읽기 경로) — negodata 이식판.
원본 reader(schedules/anchoring)는 Redis 캐시를 먼저 보지만, 이식판은 DB 직조회 한 문장만 쓴다
(2026-07-03 단순화 결정 — 조회가 견적 생성 시에만 일어나 캐시가 불필요, Redis 의존 제거).
칸별 최신 조정 rate 는 모듈 소유 뷰 `anchoring.current_values` 가 제공하고,
조정 이력이 없는 칸은 결과에 없으므로 호출측이 정적 테이블 시작값으로 폴백한다.
견적 생성이 앵커 조회 때문에 실패해서는 안 된다(인수인계.md §1 규칙 6) — anchoring 스키마
미적용 환경을 포함해 어떤 실패도 밖으로 던지지 않고 빈 결과(전량 정적 폴백) + WARN 으로 처리한다.
"""
from sqlalchemy import column, select, table
from sqlalchemy.ext.asyncio import AsyncSession
from common.anchoring.constants import ANCHORING_VALUE_MAX, ANCHORING_VALUE_MIN, SAMPLEABLE_SUPPLIER_TYPES
from common.logger import LOG
# 모듈 소유 DDL(postgres-init/05-anchoring-schema.sql)의 조회용 뷰 — negodata 는 ORM 모델 없이 읽기만 한다.
_current_values = table(
"current_values",
column("company_id"),
column("price_range_index"),
column("anchoring_value"),
column("supplier_type"),
schema="anchoring",
)
async def fetch_current_values(db: AsyncSession, company_ids: list, supplier_type: int) -> dict:
"""칸별 현재 앵커링 값 일괄 조회. {(company_id, price_range_index): rate‰} 반환.
supplier_type 은 견적 단위로 하나뿐이라 키에 넣지 않는다.
조정 이력이 없는 칸은 결과에 없다(호출측 정적 폴백). 조회 실패 시 빈 dict."""
if not company_ids or supplier_type not in SAMPLEABLE_SUPPLIER_TYPES:
return {}
try:
stmt = select(
_current_values.c.company_id,
_current_values.c.price_range_index,
_current_values.c.anchoring_value,
).where(
_current_values.c.company_id.in_(company_ids),
_current_values.c.supplier_type == supplier_type,
)
rows = (await db.execute(stmt)).all()
except Exception as ex:
LOG.w(f"[앵커링] current_values 조회 실패 — 전량 정적 테이블 폴백: {ex}")
return {}
out = {}
for company_id, price_range_index, rate in rows:
if not ANCHORING_VALUE_MIN <= rate <= ANCHORING_VALUE_MAX: # 범위 밖 값은 오염 방어 — 버리고 정적 폴백
LOG.w(f"[앵커링] rate 범위 밖 — 무시(정적 폴백): company={company_id} bracket={price_range_index} rate={rate}")
continue
out[(company_id, price_range_index)] = rate
return out