o2o-negosium-original/schedules/anchoring/README.md
민헌 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

61 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# anchoring — 앵커링 값 자동 조정 배치 (자립 모듈)
회사 × 협력사유형(1유통/2제조/3총판) × 가격구간(3,000원, 33,334칸)별 앵커링 값(‰)을
**격주 토 00:00 KST** 배치로 협상 성공률에 따라 자동 조정한다.
`schedules/anchoring` 아래에서 **완전 독립**으로 동작 — backend 코드를 import 하지 않는다.
> 규범 문서: **`docs/개발용.md`** (정책: `docs/기획용.md`, 흐름 해설: `docs/워크플로우.md`, 타 팀 적용: `docs/인수인계.md`)
## 경계
| 구분 | 대상 |
|---|---|
| 소유(쓰기) | `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 를 참조해 세션 생성 시 앵커가 박제 (인수인계) |
## 구조
```
schema.sql # 모듈 소유 DDL (rate_adjustments + sessions 3컬럼) — psql 수동 적용
src/anchoring/
constants.py # 상수·enum (δ={1:20, 2:10, 3:15} — 제조/총판 스왑 주의)
resources/anchoring_base.json # 정적 기본 테이블(33,334칸, 전부 10‰) — 불변, 시작값의 유일한 소스
base_table.py # 로드+검증(실패 시 기동 중단)
service.py # 순수 계산 (구간·앵커가·판정·평가) — negodata 이식 대상
reader.py # 현재 rate 조회: Redis → 조정 이력 → 정적 테이블 — negodata 이식 대상
redis_client.py # TTL 7일, socket timeout 0.3s, 장애 시 DB 폴백
batch.py # 격주 평가: 캐시 re-SET → 스캔·파생 판정 → 조정 INSERT+마킹(한 트랜잭션, rowcount 롤백)
scheduler.py # 매주 토 00:00 트리거 (격주 게이트는 잡 내부 ISO 주차 홀짝)
main.py # 엔트리 (상주 / --once)
tests/ # 골든 벡터(test_core) + DB 통합(test_batch — 로컬 Postgres 없으면 자동 스킵)
```
## 실행
```bash
# 0) DDL 적용 (신규 DB: postgres-init/01~04 이후)
psql -h 127.0.0.1 -U postgres -d negosium_db -f schema.sql
# 로컬(가상환경)
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
cp config.toml.example config.toml # DB/Redis 채우기 (env 로 대체 가능)
PYTHONPATH=src .venv/bin/python -m anchoring.main --once # 수동 1회(격주 게이트 무시)
PYTHONPATH=src .venv/bin/python -m anchoring.main # 스케줄러 상주
# 도커(자립 compose: redis 동봉)
docker compose up -d --build
# 테스트
PYTHONPATH=src .venv/bin/python -m pytest tests/ -q
```
## 운영 런북
- **미스파이어**: 토 00:00 에 서비스가 내려가 있었고 1시간(misfire_grace) 초과로 그 회차가 스킵됐다면,
재기동 후 `--once` 1회 실행으로 즉시 캐치업(격주 게이트만 무시, 정책 파라미터 불변).
- **Redis 유실/재기동**: 캐시는 파생값 — 매 실행(매주, 게이트 무관) 시작 시 조정 보유 칸 전체를 re-SET 하고
TTL 7일이 보조하므로 자가 회복된다. 수동 복구가 필요하면 `--once`.
- **노출률 0% WARN**: agent 스크립트 스텝명(`기존가격제시`) 변경이나 backend 노출 기록 배선 유실 신호 — 즉시 점검.
- 조정 이력은 append-only — UPDATE/DELETE 금지. 배치가 sessions 에 쓰는 컬럼은 `anchoring_adjustment_id` 하나뿐.