perf(slots): BAT-040 — slots RPC DB-level status 필터 (REQ-002/006/073, issue #25)
REQ-002/006 change-req(CHG-20260612-01)→구현→confirm-req-implemented, REQ-073 change-req(CHG-20260612-02, 관찰 계약 불변 Implemented 유지) 전체 delivery. - repository.py/in_memory_repository.py: SlotRepository.list_slots(instance_key, status_filter=None) — status 값 집합 필터, None=전체(기존 호출자 불변). - postgresql.py: list_slots DB-level WHERE status = ANY(%s) (full scan + Python 필터 제거). 스키마/마이그레이션 변경 없음. - messaging_handlers.py: _FILTER_STATUS 매핑으로 filter→status 집합 위임, watchdog 갱신용 holding 조회를 all/holding fetch 결과에서 재사용(2nd 커넥션 생략). - 관찰 가능한 RPC 계약(REQ-073 AC-1~6) 불변 — 동작 보존 최적화. - tests: in-memory status_filter 5종 + PG 게이트 1종. 439 passed (+5), ruff clean. REQ-002·006 Approved→Implemented, REQ-073 Implemented 유지, BAT-040 release-candidate. issue #25 close. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# BAT-040 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-040
|
||||
|
||||
## Architecture Summary
|
||||
- status 필터링을 핸들러(Python)에서 저장소 경계로 내린다. `SlotRepository.list_slots` 에 optional `status_filter: set[str]` 를 추가하고(REQ-002 포트), PostgreSQL 은 `status = ANY(%s)`(REQ-006), in-memory 는 set 멤버십으로 필터한다.
|
||||
- 핸들러(REQ-073)는 요청 filter 토큰을 status 값 집합으로 매핑해 `list_slots` 에 위임하고, watchdog 갱신용 holding 슬롯을 이미 fetch 한 결과에서 재사용할 수 있으면(all/holding) 두 번째 쿼리를 생략한다.
|
||||
- 관찰 가능한 응답 계약은 불변 — 동작 보존 최적화.
|
||||
|
||||
## Changed Areas
|
||||
- Modules:
|
||||
- `trex_core/domain/slot/repository.py` — `list_slots(self, instance_key, status_filter: Optional[set[str]] = None)`
|
||||
- `trex_core/domain/slot/in_memory_repository.py` — `status_filter` 멤버십 필터
|
||||
- `trex_core/adapters/persistence/postgresql.py` — `status_filter` → `WHERE instance_key = %s AND status = ANY(%s)`
|
||||
- `trex_core/application/messaging_handlers.py` — filter→status 집합 매핑 + 위임 + watchdog 재사용
|
||||
- Tests:
|
||||
- `tests/domain/`(in-memory status_filter), `tests/application/test_slots_handler.py`(filter/spy/키 회귀), `tests/adapters/persistence/`(PG 필터 게이트)
|
||||
- Docs: 없음(관찰 계약·흐름 불변)
|
||||
|
||||
## Key Decisions
|
||||
1. **`status_filter: Optional[set[str]]` (status 값 집합)**: 단일 status 가 아니라 집합 — `pending` 토큰이 `{pending_buy, pending_sell}` 두 status 로 매핑되기 때문. `None` = 전체(기존). 핸들러가 토큰→집합 매핑을 소유(저장소는 status 값만 안다).
|
||||
2. **filter 토큰 매핑(핸들러)**: `inactive→{"inactive"}`, `pending→{"pending_buy","pending_sell"}`, `holding→{"holding"}`, `error→{"error"}`, `all`/미인식→`None`. REQ-073 AC-2(미인식 filter → all 일관 처리) 보존.
|
||||
3. **PG `status = ANY(%s)`**: psycopg 가 Python list 를 PG array 로 어댑팅. `IN` 대신 `ANY` 로 단일 파라미터 바인딩. None 이면 기존 `WHERE instance_key = %s` 분기 유지.
|
||||
4. **watchdog 두 번째 쿼리 생략 조건**: `status_filter is None`(all) 또는 filter=="holding" 이면 이미 fetch 한 `slots` 에 holding 이 전부 포함되므로 거기서 추출. 그 외(inactive/pending/error)는 holding 이 응답에 없으므로 기존 `list_holding_slots` 호출. spy=True 면 watchdog 자체를 갱신하지 않아 어느 경우든 두 번째 쿼리 없음.
|
||||
5. **동작 보존**: 핸들러 출력(slots/available_slots/degraded/reason/message, spy 시맨틱)은 불변. AC-1~6 회귀로 보증.
|
||||
|
||||
## Edge Cases
|
||||
- `list_slots` 무인자 호출자(reconcile/safety-sell/kill-switch): default `None` → 기존 전체 fetch, 동작 불변.
|
||||
- filter=="all": status_filter=None → 전체 fetch, watchdog 은 fetch 결과에서 holding 추출(두 번째 쿼리 생략).
|
||||
- filter=="holding": status_filter={"holding"} → holding 만 fetch, watchdog 재사용(두 번째 쿼리 생략).
|
||||
- filter=="inactive"/"pending"/"error": holding 미포함 → watchdog 위해 `list_holding_slots` 호출(spy=false 시).
|
||||
- 빈 결과(해당 status 슬롯 0): 빈 리스트 반환(정상).
|
||||
|
||||
## Architecture Impact
|
||||
- none (runtime-flows 흐름·관찰 계약 불변. 내부 쿼리 위치/호출 수 최적화만)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: none
|
||||
- runtime-flows.md: none (체결/슬롯 흐름 불변, 핸들러 내부 쿼리 최적화)
|
||||
@@ -0,0 +1,33 @@
|
||||
# BAT-040 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-040
|
||||
|
||||
## Plan Summary
|
||||
- `list_slots(status_filter)` 포트(REQ-002) + in-memory + PG(REQ-006) 구현, 핸들러(REQ-073) filter→status 집합 위임 + watchdog 조회 재사용, 테스트.
|
||||
|
||||
## Changed Files
|
||||
- `trex_core/domain/slot/repository.py` — `list_slots(instance_key, status_filter=None)` Protocol 시그니처 + docstring
|
||||
- `trex_core/domain/slot/in_memory_repository.py` — `status_filter` 멤버십 필터
|
||||
- `trex_core/adapters/persistence/postgresql.py` — `list_slots` `status = ANY(%s)` 분기(None=전체)
|
||||
- `trex_core/application/messaging_handlers.py` — `_FILTER_STATUS` 매핑 + `list_slots(status_filter)` 위임 + watchdog 조회 재사용(all/holding 시 두 번째 커넥션 생략)
|
||||
- `tests/domain/slot/test_list_slots_filter.py` (신규, 5)
|
||||
- `tests/adapters/persistence/test_postgresql_repository.py` — `test_list_slots_status_filter_pg` (PG 게이트)
|
||||
|
||||
## Execution Log
|
||||
1. `repository.py`: Protocol `list_slots` 에 `status_filter: Optional[set[str]] = None` 추가.
|
||||
2. `in_memory_repository.py`: `status_filter is None or s.status.value in status_filter` 필터.
|
||||
3. `postgresql.py`: `status_filter` truthy 면 `WHERE instance_key = %s AND status = ANY(%s)`(list 파라미터), 아니면 기존 전체 쿼리.
|
||||
4. `messaging_handlers.py`: 모듈 상수 `_FILTER_STATUS`(inactive/pending/holding/error→status 집합, all/미인식→None). 핸들러가 `list_slots(instance_key, status_filter)` 위임. watchdog: `status_filter is None or filter=="holding"` 면 fetch 결과에서 holding 추출, 아니면 `list_holding_slots`(spy=true 면 둘 다 생략).
|
||||
5. 테스트: in-memory 5종(None 전체/단일 status/pending 그룹/무매치 빈/instance 스코프), PG 게이트 1종.
|
||||
|
||||
## Validation
|
||||
- `pytest tests/application/test_slots_handler.py` — 16 passed (filter/spy/응답 키/available_slots 회귀, 관찰 계약 불변 확인)
|
||||
- `pytest tests/domain/slot/test_list_slots_filter.py` — 5 passed (신규)
|
||||
- `pytest tests/` — **439 passed, 46 skipped** (BAT-039 434 → +5 in-memory; PG status_filter 테스트는 DSN 부재로 skip, 동일 게이트)
|
||||
- `ruff check trex_core tests` — All checks passed
|
||||
- 무인자 `list_slots` 호출자(reconcile/safety-sell/kill-switch 등) 전수 회귀 없음(default None 동작 보존).
|
||||
|
||||
## Remaining Risks
|
||||
- 없음. R-1(무인자 호출자 회귀)=default None 보존+전체 회귀 green, R-2(ANY 어댑팅)=psycopg list→array(PG 게이트 테스트, DSN 가용 시 실행), R-3(watchdog 재사용)=spy/holding 터치 테스트 회귀 green.
|
||||
- PG `status = ANY` 경로의 라이브 검증은 `TREX_POSTGRESQL_TEST_DSN` 설정 환경에서 게이트 테스트로 수행(CI/로컬 DSN). in-memory parity 가 동작 계약을 보증.
|
||||
@@ -0,0 +1,34 @@
|
||||
# BAT-040: slots RPC per-request full scan 개선 — list_slots DB-level status 필터 (CHG-20260612-01)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: 없음 (issue #25, REQ-002/006/073 change-req)
|
||||
- Included REQ: REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02)
|
||||
- Excluded REQ: 없음
|
||||
|
||||
## Summary
|
||||
- `trex.slots.req` 핸들러가 매 요청마다 `list_slots`(전 행 fetch) 후 Python 에서 status 를 거르던 구조(issue #25)를 DB 레벨 필터로 바꾼다. `SlotRepository.list_slots` 에 `status_filter` 옵션을 추가(REQ-002 포트 + REQ-006 PG 구현)하고, 핸들러가 filter 토큰을 status 집합으로 매핑해 위임한다(REQ-073). watchdog 갱신용 holding 조회는 응답 필터가 이미 holding 을 포함한 경우(all/holding) fetch 결과를 재사용해 두 번째 커넥션을 생략한다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `trex_core/domain/slot/repository.py` — `SlotRepository.list_slots(instance_key, status_filter=None)` (REQ-002)
|
||||
- `trex_core/domain/slot/in_memory_repository.py` — status_filter 구현 (REQ-002)
|
||||
- `trex_core/adapters/persistence/postgresql.py` — `list_slots` DB-level `status = ANY(%s)` (REQ-006)
|
||||
- `trex_core/application/messaging_handlers.py` — `make_slots_rpc_handler` filter→status 집합 매핑·위임 + watchdog 조회 재사용 (REQ-073)
|
||||
- 테스트: in-memory status_filter 단위, PG 필터(게이트), 핸들러 filter/spy/응답 키 회귀
|
||||
- Out of Scope:
|
||||
- 관찰 가능한 RPC 계약 변경(REQ-073 AC-1~6 불변 — 동작 보존 최적화)
|
||||
- 스키마/마이그레이션 변경(쿼리 한정)
|
||||
- overlay(paused/warned) 서버측 필터(REQ-074 후속)
|
||||
- 인덱스 추가/튜닝(별도 운영 판단)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- 동작 보존 내부 최적화 — REQ-073 관찰 계약(filter 결과/키 집합/spy/degraded/available_slots) 불변. REQ-073 은 Implemented 유지(재검증), REQ-002/006 은 신규 AC-6 으로 Approved→Implemented 복귀.
|
||||
- 도메인 포트 시그니처 변경(인터페이스 영향) + 다중 REQ 횡단 → minor-change fast-path 불가, standard profile.
|
||||
- `list_holding_slots` 는 이미 status-scoped(full scan 아님). 주 개선은 `list_slots` full scan 제거 + all/holding 필터 시 두 번째 커넥션 생략.
|
||||
@@ -0,0 +1,37 @@
|
||||
# BAT-040 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-040
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-002 (CHG-20260612-01): `SlotRepository.list_slots(instance_key, status_filter=None)` 포트 + in-memory 구현. AC-6 신규.
|
||||
- REQ-006 (CHG-20260612-01): PostgreSQL `list_slots` DB-level `status = ANY(%s)`. AC-6 신규.
|
||||
- REQ-073 (CHG-20260612-02): 핸들러 filter→status 집합 매핑·위임 + watchdog 조회 재사용. 관찰 계약 불변, Implemented 유지.
|
||||
|
||||
## Delivery Plan
|
||||
1. `repository.py` — `list_slots` Protocol 시그니처에 `status_filter: Optional[set[str]] = None` 추가(docstring 갱신).
|
||||
2. `in_memory_repository.py` — `status_filter` 가 주어지면 `s.status.value in status_filter` 필터, None 이면 전체.
|
||||
3. `postgresql.py` — `status_filter` 주어지면 `WHERE instance_key = %s AND status = ANY(%s)`(list 파라미터), None 이면 기존 전체 쿼리.
|
||||
4. `messaging_handlers.py` — filter 토큰→status 집합 매핑(`inactive→{inactive}`, `pending→{pending_buy,pending_sell}`, `holding→{holding}`, `error→{error}`, all/미인식→None) 후 `list_slots(instance_key, status_filter)`. watchdog: `status_filter is None or filter=="holding"` 면 fetch 결과에서 holding 추출(두 번째 쿼리 생략), 아니면 `list_holding_slots`.
|
||||
5. 테스트 — in-memory status_filter 단위, 핸들러 filter/spy/응답 키 회귀(기존), PG 필터(게이트, DSN 가용 시).
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- `list_slots` 호출자 전수(reconcile/safety-sell/kill-switch 등)는 무인자 → default None 으로 동작 불변(회귀 확인 필요)
|
||||
- `SlotState` 값 문자열(`inactive/pending_buy/holding/pending_sell/error`)
|
||||
- External: PostgreSQL(통합 게이트). 단위는 in-memory.
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: 도메인 포트 시그니처 변경(인터페이스) + status 집합 매핑 규칙 + watchdog 쿼리 재사용 조건 + in-memory/PG parity 를 design 에서 확정.
|
||||
|
||||
## Milestones
|
||||
1. Design confirmed — 포트 시그니처·status 집합 매핑·watchdog 재사용 조건 확정
|
||||
2. Implementation — repository/in_memory/postgresql/handler + 테스트
|
||||
3. Verification — REQ-002/006 AC-6 + REQ-073 AC-1~6 회귀 + 기존 list_slots 호출자 회귀 + ruff
|
||||
|
||||
## Risks
|
||||
- R-1 (Medium): `list_slots` 무인자 호출자(reconcile 등)가 status_filter default 로 동작이 바뀌면 회귀 — 완화: default None = 기존 전체, 전수 호출 회귀 테스트.
|
||||
- R-2 (Low): PG `ANY(%s)` 파라미터 어댑팅(list→array) — 완화: psycopg list→array 어댑팅 사용, PG 게이트 통합 테스트로 확인.
|
||||
- R-3 (Low): watchdog 조회 재사용 조건 오류 시 holding 워치독 미갱신 — 완화: filter별 spy/watchdog 테스트로 holding 터치 보존 확인(REQ-073 AC-4).
|
||||
@@ -0,0 +1,53 @@
|
||||
# BAT-040 Verification
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-040
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
- REQ-002 AC-6 (`list_slots(instance_key, status_filter)` 포트 + in-memory, None 전체):
|
||||
- Evidence (`tests/domain/slot/test_list_slots_filter.py`):
|
||||
- `test_none_returns_all`: None/무인자 → 전체 5 PASSED.
|
||||
- `test_single_status_filter`: `{"holding"}`→1, `{"inactive"}`→3 PASSED.
|
||||
- `test_pending_group_filter`: `{"pending_buy","pending_sell"}`→pending 1 PASSED.
|
||||
- `test_empty_when_no_match`: `{"error"}`→[] PASSED.
|
||||
- `test_instance_scoped`: 타 instance 0 PASSED.
|
||||
|
||||
- REQ-006 AC-6 (PostgreSQL `list_slots` DB-level `status = ANY(%s)`, None 전체):
|
||||
- Evidence: `tests/adapters/persistence/test_postgresql_repository.py::test_list_slots_status_filter_pg` — None→전체 3, `{"inactive"}`→2, pending 그룹→1, `{"error"}`→[]. `TREX_POSTGRESQL_TEST_DSN` 설정 시 실행(현재 미설정 → skip, 전 PG 테스트와 동일 게이트). in-memory parity(REQ-002 AC-6)가 동작 계약을 보증.
|
||||
|
||||
- REQ-073 AC-1 (토폴로지 — `trex.slots.req` 존재):
|
||||
- 변경 범위 밖(핸들러 내부 위임만). Evidence: 전체 회귀 439 passed.
|
||||
|
||||
- REQ-073 AC-2 (filter 동작 — inactive/pending/holding/all):
|
||||
- Evidence: `test_filter_*` 5개 PASSED (`test_slots_handler.py`) — DB 위임 후에도 filter 결과 동일. 미인식 filter→None(all) 일관 처리 보존.
|
||||
|
||||
- REQ-073 AC-3 (응답 키 집합 — slots/available_slots/degraded/reason/message):
|
||||
- Evidence: `test_response_keys_only_slots_health` PASSED — 키 집합 불변.
|
||||
|
||||
- REQ-073 AC-4 (spy=true 워치독 미갱신 / spy=false 갱신):
|
||||
- Evidence: `test_spy_true_no_watchdog_update`, `test_spy_false_updates_watchdog` PASSED — watchdog 조회 재사용 최적화 후에도 holding 터치 시맨틱 보존.
|
||||
|
||||
- REQ-073 AC-5 (degraded 신호 전파):
|
||||
- Evidence: `test_degraded_signal_propagated` PASSED.
|
||||
|
||||
- REQ-073 AC-6 (available_slots 값 규칙):
|
||||
- Evidence: `test_available_slots_*` 3개 PASSED — capacity hint 불변.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Test:
|
||||
- `pytest tests/domain/slot/test_list_slots_filter.py` — 5 passed (신규)
|
||||
- `pytest tests/application/test_slots_handler.py` — 16 passed (REQ-073 AC-1~6 회귀, 관찰 계약 불변)
|
||||
- `pytest tests/` — **439 passed, 46 skipped** (BAT-039 434 → +5; PG status_filter 테스트는 DSN 부재로 skip, 동일 게이트)
|
||||
- `ruff check trex_core tests` — All checks passed
|
||||
|
||||
- Manual / Architecture Impact (none):
|
||||
- design.md Architecture Impact = none — `docs/project-structure.md`, `docs/runtime-flows.md` 동기화 불필요(관찰 계약·흐름 불변, 핸들러 내부 쿼리 위치/호출 수 최적화만).
|
||||
- 무인자 `list_slots` 호출자(reconcile/safety-sell/kill-switch) default None 동작 보존 — 전체 회귀로 확인.
|
||||
- 핸들러 watchdog 조회 재사용 조건(all/holding fetch 재사용, 그 외 list_holding_slots) 코드 리뷰 + AC-4 테스트로 확인.
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues: 없음
|
||||
- PG `status = ANY` 경로의 라이브 검증은 `TREX_POSTGRESQL_TEST_DSN` 가용 환경의 게이트 테스트로 수행(REQ-006 기존 PG 테스트 전부 동일 조건). in-memory parity 가 release-ready 를 보증 — 차단 아님.
|
||||
@@ -47,3 +47,4 @@
|
||||
| BAT-037 | standard | release-candidate | REQ-073 (CHG-20260612-01) | dcy-025 (REQ-073 change-req) | [bat-037](./bat-037_20260612_slots-rpc-available-slots/) |
|
||||
| BAT-038 | standard | release-candidate | REQ-018 (CHG-20260612-01) | dcy-026 (issue #24) | [bat-038](./bat-038_20260612_sell-transaction-tax-fee/) |
|
||||
| BAT-039 | standard | release-candidate | REQ-011 (CHG-20260612-01) | 없음 (issue #4) | [bat-039](./bat-039_20260612_paper-seq-redis-persistence/) |
|
||||
| BAT-040 | standard | release-candidate | REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) | 없음 (issue #25) | [bat-040](./bat-040_20260612_slots-rpc-db-filter/) |
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
3. Given 드리프트 테스트, When `EXPECTED_SLOT_FIELDS` ↔ information_schema 대조, Then 일치하고 컬럼 누락 시 실패한다(dcy-001 OQ-2 DB-side 검사 실현).
|
||||
4. Given 상태 변경, When transit_slot, Then `pg_advisory_xact_lock` 하 단일 트랜잭션으로 직렬화되고 lock 보유 중 외부 I/O 가 없다.
|
||||
5. (CHG-20260610-02, dcy-023/issue #22) Given 마이그레이션 013(`DROP COLUMN IF EXISTS allocated_capital`, 멱등) 적용, Then `daily_slot` 에 `allocated_capital` 컬럼이 없고 persistence flat 매핑(SELECT/UPDATE/행 복원)에서 해당 컬럼 참조가 제거되며, `EXPECTED_SLOT_FIELDS` ↔ information_schema 드리프트 테스트가 green 이다. 적용은 코드 배포와 동시(`apply_migrations` 자동, dcy-023 OQ-2), 비가역 drop 이므로 적용 전 백업 절차를 release 문서에 명시한다.
|
||||
6. (CHG-20260612-01, issue #25) Given PostgreSQL `list_slots(instance_key, status_filter)`, When `status_filter` 가 status 값 집합이면, Then `WHERE instance_key = %s AND status = ANY(%s)` 로 DB 에서 직접 필터링해 해당 행만 fetch 하고(전 행 fetch + Python 필터 제거), `None` 이면 기존대로 `WHERE instance_key = %s` 전체를 반환한다. 반환 결과는 in-memory 구현과 동일하다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/data-model.md(테이블/계약), dcy-001 부록 A(기준)
|
||||
- Modules: trex_core/ 하위 persistence 어댑터(psycopg) + 마이그레이션 SQL; 도메인 패키지는 무의존 유지(NFR-1); (CHG-20260610-02) postgresql.py flat 매핑 allocated_capital 제거 + 013_drop_allocated_capital.sql
|
||||
- Modules: trex_core/ 하위 persistence 어댑터(psycopg) + 마이그레이션 SQL; 도메인 패키지는 무의존 유지(NFR-1); (CHG-20260610-02) postgresql.py flat 매핑 allocated_capital 제거 + 013_drop_allocated_capital.sql; (CHG-20260612-01) postgresql.py `list_slots` DB-level status 필터(`status = ANY(%s)`)
|
||||
- Tests: PostgreSQL repo 통합테스트 + 드리프트 테스트
|
||||
|
||||
## Notes
|
||||
@@ -321,3 +322,50 @@
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음.
|
||||
|
||||
### CHG-20260612-01
|
||||
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- (change-req, issue #25) PostgreSQL `list_slots` 에 `status_filter` 를 받아 `WHERE instance_key = %s AND status = ANY(%s)` 로 DB 레벨 필터링한다. slots RPC 의 per-request full scan(전 행 fetch + Python 필터)을 제거. `status_filter=None` 은 기존 전체 fetch. 스키마/마이그레이션 변경 없음(쿼리 한정). 신규 AC-6 → **Implemented → Approved 되돌림**.
|
||||
|
||||
#### Intent
|
||||
|
||||
- `trex.slots.req` 가 200-slot 인스턴스에서 매 요청마다 전 슬롯을 읽어오던 비용을 줄인다(issue #25 (1) DB 레벨 status 필터링). REQ-002 포트 확장에 대응하는 PostgreSQL 구현.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added:
|
||||
- AC-6: `list_slots(instance_key, status_filter)` DB-level `status = ANY(%s)` 필터 + `None` 전체. in-memory 와 결과 동일.
|
||||
- Updated/Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: 없음
|
||||
- Modules: `trex_core/adapters/persistence/postgresql.py`(`list_slots` 쿼리)
|
||||
- Tests: PG `list_slots` status 필터 통합 테스트(PG 게이트), in-memory parity
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-040 (예정, issue #25)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: yes (BAT-040 에서 AC-6 + 기존 AC 회귀)
|
||||
- Existing Implementation Invalidated: no (쿼리 확장, default None 기존 동작 보존). 신규 AC-6 미구현으로 Implemented 유지 불가 → Approved.
|
||||
- Status Recommendation: Implemented → **Approved**
|
||||
- Follow-up Action: confirm-req → BAT-040 구현·검증 → confirm-req-implemented
|
||||
|
||||
#### Validation Plan
|
||||
|
||||
- BAT-040: PG `list_slots` status 필터 통합(PG 게이트, DSN 가용 시) + in-memory parity 단위 + 전체 suite + ruff clean. 스키마 드리프트 테스트 회귀(컬럼 불변).
|
||||
|
||||
### CHG-20260612-IMPL
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-040(release-candidate) verification — AC-6 PG `list_slots` `status = ANY(%s)` 게이트 테스트(`test_list_slots_status_filter_pg`, DSN 가용 시 실행; 현재 skip) + in-memory parity(REQ-002 AC-6)로 동작 계약 보증, 스키마 변경 없음(쿼리 한정), 전체 439 passed + ruff clean. Blocking Issues 없음.
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
3. Given 공개 API, When `__init__.py` 의 `__all__` 을 확인, Then `PostgreSQLSlotRepository`·`check_slot_domain_schema_drift` 등 인프라 심볼이 노출되지 않는다.
|
||||
4. Given `SlotService`, When 6 상태/9 base 전이(`closed→pending_buy` 포함, dcy-019)/advisory-lock 직렬화/`closed→idle` 재진입 +1/`→pending_buy` 시 slot `entry_count` +1(dcy-019)/`closed→pending_buy` clean-slate 점유 초기화/lock 보유 중 외부 I/O 금지 동작을 확인, Then 보존된다. (NFR-2 — 검증은 REQ-003)
|
||||
5. (CHG-20260610-02, dcy-023/issue #22) Given allocated_capital 제거 후, Then `Slot` 모델과 `EXPECTED_SLOT_FIELDS` 에 `allocated_capital` 필드가 없고, `SlotService` 의 설정/전이 경로(buy 진입·전이 시 set/clear)에서 해당 파라미터가 제거된다.
|
||||
6. (CHG-20260612-01, issue #25) Given `SlotRepository.list_slots(instance_key, status_filter=None)`, When `status_filter` 가 status 값 집합(예: `{"inactive"}`, `{"pending_buy","pending_sell"}`)이면, Then 해당 status 슬롯만 반환하고 `None`(미지정)이면 기존대로 전체를 반환한다(in-memory 구현 포함). 기존 무인자 호출자는 동작이 불변이다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/interface-contract.md, docs/data-model.md (계약 기준 — 큰 변경 없을 수 있음)
|
||||
- Modules: trex_core/domain/slot/{__init__,state,models,errors,repository,service,in_memory_repository}.py, (CHG-20260610-02) models.py `Slot`/`EXPECTED_SLOT_FIELDS` + service.py allocated_capital 제거
|
||||
- Modules: trex_core/domain/slot/{__init__,state,models,errors,repository,service,in_memory_repository}.py, (CHG-20260610-02) models.py `Slot`/`EXPECTED_SLOT_FIELDS` + service.py allocated_capital 제거, (CHG-20260612-01) repository.py `SlotRepository.list_slots` 시그니처 `status_filter` 옵션 + in_memory_repository.py 구현
|
||||
- Tests: REQ-003 에서 이식 단위테스트 + 계약 테스트로 검증
|
||||
|
||||
## Notes
|
||||
@@ -321,3 +322,50 @@
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-034(release-candidate) verification — allocated_capital 전 경로 제거(도메인/intent/영속/snapshot/CLI), 마이그레이션 013 테스트 DSN 멱등 round-trip + 컬럼 부재 확인, 전체 428 passed + PG 게이트 21 passed + ruff clean, grep 전수 0건. Blocking Issues 없음.
|
||||
|
||||
### CHG-20260612-01
|
||||
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- (change-req, issue #25) `SlotRepository.list_slots` 에 optional `status_filter` 파라미터를 추가해 status 필터링을 저장소 경계로 내린다. 시그니처 `list_slots(instance_key, status_filter: Optional[set[str]] = None)` — `None` 은 기존(전체), 값 집합이면 해당 status 만 반환. in-memory 구현도 동일 의미로 갱신. slots RPC 핸들러(REQ-073)의 per-request full scan + Python 필터를 제거하기 위한 포트 확장. 신규 AC-6 추가 구현·검증 필요 → **Implemented → Approved 되돌림**.
|
||||
|
||||
#### Intent
|
||||
|
||||
- `trex.slots.req` 가 매 요청마다 전 슬롯을 fetch 한 뒤 Python 에서 거르던 구조(issue #25)를 DB 레벨 필터로 바꾸기 위해, 도메인 포트(`list_slots`)와 in-memory 더블을 먼저 확장한다. 기존 무인자 호출자(reconcile/safety-sell 등)는 default `None` 으로 동작 불변.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added:
|
||||
- AC-6: `list_slots(instance_key, status_filter)` — status 값 집합 필터 + `None` 전체, in-memory 포함, 기존 호출자 불변.
|
||||
- Updated/Removed: 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: 없음
|
||||
- Modules: `trex_core/domain/slot/repository.py`(Protocol 시그니처), `trex_core/domain/slot/in_memory_repository.py`(구현)
|
||||
- Tests: in-memory `list_slots(status_filter)` 단위 — status 집합 필터 + None 전체 + 기존 무인자 회귀
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-040 (예정, issue #25)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: yes (BAT-040 에서 AC-6 + 기존 AC 회귀)
|
||||
- Existing Implementation Invalidated: no (추가 파라미터는 default None 으로 기존 동작 보존). 단 신규 AC-6 미구현으로 Implemented 유지 불가 → Approved.
|
||||
- Status Recommendation: Implemented → **Approved**
|
||||
- Follow-up Action: confirm-req → BAT-040 구현·검증 → confirm-req-implemented
|
||||
|
||||
#### Validation Plan
|
||||
|
||||
- BAT-040: in-memory `list_slots` status 집합 필터 + None 전체 단위 테스트, 기존 무인자 호출 회귀, 전체 suite + ruff clean.
|
||||
|
||||
### CHG-20260612-IMPL
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
- Type: status
|
||||
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-040(release-candidate) verification — AC-6 `list_slots(status_filter)` in-memory 5종(None 전체/단일 status/pending 그룹/무매치/instance 스코프) PASSED, 무인자 호출자 default None 동작 보존(전체 439 passed), ruff clean. Blocking Issues 없음.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
## Impacted Area
|
||||
- Docs: docs/interface-contract.md(snapshot→slots 계약 — REQ-075), docs/runtime-flows.md
|
||||
- Modules: trex_core/adapters/messaging/topology.py(큐명), trex_core/application/messaging_handlers.py(slots 핸들러·snapshot 핸들러 제거), trex_core/application/portfolio.py(`View` enum 폐기)
|
||||
- Modules: trex_core/adapters/messaging/topology.py(큐명), trex_core/application/messaging_handlers.py(slots 핸들러·snapshot 핸들러 제거; (CHG-20260612-02) status 필터 repo 위임 + watchdog 조회 재사용), trex_core/application/portfolio.py(`View` enum 폐기)
|
||||
- Tests: tests/application/(slots 핸들러 filter/spy/응답 키 집합), tests/adapters/messaging/(토폴로지)
|
||||
|
||||
## Notes
|
||||
@@ -37,6 +37,45 @@
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260612-02
|
||||
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- (change-req, issue #25) slots RPC 핸들러(`make_slots_rpc_handler`)가 status 필터링을 `SlotRepository.list_slots(instance_key, status_filter=...)`(REQ-002/006) 에 위임해 per-request full scan + Python 필터를 제거한다. 추가로 watchdog 갱신용 holding 조회를, 응답 필터가 `all`/`holding` 이라 이미 holding 을 포함한 경우 fetch 결과를 재사용해 두 번째 커넥션을 생략한다(그 외 필터는 기존대로 `list_holding_slots`). **관찰 가능한 응답 계약(AC-1~6)은 불변** — filter 결과·키 집합·spy·degraded·available_slots 동일. AC delta 없음 → **Implemented 유지**(BAT-040 재검증).
|
||||
|
||||
#### Intent
|
||||
|
||||
- issue #25 (1) DB 레벨 status 필터 + (2) 두 쿼리 통합. orb-v1 이 매 cycle `{filter:"inactive"}` 로 요청할 때 200-slot 인스턴스에서 전 행 fetch 를 반복하던 비용 제거. 동작 보존 내부 최적화.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added/Updated/Removed: 없음 (AC-1~6 관찰 계약 불변, 핸들러 내부 위임만)
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: 없음
|
||||
- Modules: `trex_core/application/messaging_handlers.py`(`make_slots_rpc_handler` — `list_slots(status_filter)` 위임 + watchdog 조회 재사용)
|
||||
- Tests: 기존 filter/spy/응답 키 집합 테스트 회귀 + status_filter 위임 확인
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: BAT-040 (예정, issue #25)
|
||||
- Releases: none
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: yes (BAT-040 에서 filter/spy/degraded/available_slots 회귀)
|
||||
- Existing Implementation Invalidated: no (관찰 응답 동일, 내부 위임만 — AC evidence 유효)
|
||||
- Status Recommendation: **Implemented 유지**
|
||||
- Follow-up Action: BAT-040 구현·검증으로 재확인(REQ-002/006 Implemented 복귀와 동일 배치)
|
||||
|
||||
#### Validation Plan
|
||||
|
||||
- BAT-040: 기존 `test_filter_*`/`test_spy_*`/`test_response_keys_*`/`test_available_slots_*` 회귀 green + status_filter 위임 경로(필터별 list_slots 인자) 확인.
|
||||
|
||||
### CHG-20260612-IMPL
|
||||
- Date: 2026-06-12
|
||||
- Author: Justin Ko
|
||||
|
||||
+2
-2
@@ -159,8 +159,8 @@
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-06-12 :: REQ-002·006 Approved → **Implemented** (confirm-req-implemented, BAT-040, issue #25): slots RPC full scan 개선 구현·검증 완료. `list_slots(status_filter)` DB-level 필터 + 핸들러 위임 + watchdog 조회 재사용. 439 passed (in-memory 필터 5 신규, 핸들러 16 회귀, PG 게이트 1), ruff clean. REQ-073 관찰 계약 불변 Implemented 유지. Blocking Issues 없음. issue #25 close.
|
||||
- 2026-06-12 :: REQ-002·006·073 change-req (CHG-20260612-01/01/02, issue #25): slots RPC per-request full scan 개선 — `list_slots(status_filter)` DB-level status 필터 + 핸들러 위임 + watchdog 조회 재사용. REQ-002/006 AC-6 신규 → Implemented → **Approved** 되돌림(default None 동작 보존). REQ-073 관찰 계약 불변 → **Implemented 유지**.
|
||||
- 2026-06-12 :: REQ-011 Approved → **Implemented** (confirm-req-implemented, BAT-039): paper order seq Redis 영속화 구현·검증 완료. 434 passed (seq store 5 신규 포함), 재시작 단조·correlation_id 비충돌·in-memory 폴백 green. Blocking Issues 없음. issue #4 close.
|
||||
- 2026-06-12 :: REQ-011 change-req (CHG-20260612-01, issue #4): paper 브로커 order seq Redis 영속화 — 재시작 후 `correlation_id` 충돌(매도 원장 누락) 방지. optional seq store(RedisSeqStore INCR + in-memory 폴백) 주입. AC-4 신규. 기존 비동기 계약 불변이나 신규 AC 미구현 → Implemented → **Approved** 되돌림. BAT-039 구현·검증 후 복귀. paper 한정(프로덕션 버그 아님).
|
||||
- 2026-06-12 :: REQ-018 Approved → **Implemented** (confirm-req-implemented, BAT-038): 매도 증권거래세 정책 계산 + `tax=0.0` 제거 구현·검증 완료. 429 passed (fee·tax 정책 6 신규 포함), issue #24 1,376.9원 회귀 green, `tax=0.0` 하드코딩 grep 0건, runtime-flows 동기화. Blocking Issues 없음.
|
||||
- 2026-06-12 :: REQ-018 change-req (CHG-20260612-01, dcy-026/issue #24): 매도 증권거래세 누락 정정 — `FillStatus.tax` 매도 정책 세율(0.2%) 계산, `tax=0.0` 하드코딩(paper/kis) 제거, 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`). AC-3 갱신·AC-6~9 신규. `tax=0` evidence 무효 → Implemented → **Approved** 되돌림. BAT-038 구현·검증 후 복귀.
|
||||
- 2026-06-12 :: REQ-073 Approved → **Implemented** (confirm-req-implemented, BAT-037): `available_slots: int|null` capacity hint 추가. AC-3 반전·AC-6 신규. 16 tests passed (AC-6 3개 포함), 423 전체 회귀 없음. Blocking Issues 없음.
|
||||
|
||||
@@ -71,6 +71,19 @@ def test_buy_intent_idempotent_on_pg(pg_clean):
|
||||
assert len(pending) == 1 # no double transition
|
||||
|
||||
|
||||
def test_list_slots_status_filter_pg(pg_clean):
|
||||
# REQ-006 CHG-20260612-01 (issue #25): DB-level status filter (status = ANY).
|
||||
repo, svc, intents = _wire(pg_clean)
|
||||
repo.reconcile_universe(INST, _SYMS) # 3 inactive
|
||||
intents.submit_buy(BuyIntent(intent_id="pg-f1", qty=10, portfolio_id="PF-1")) # PF-1 -> pending_buy
|
||||
|
||||
assert len(repo.list_slots(INST)) == 3 # None -> all
|
||||
assert len(repo.list_slots(INST, status_filter={"inactive"})) == 2
|
||||
pending = repo.list_slots(INST, status_filter={"pending_buy", "pending_sell"})
|
||||
assert len(pending) == 1 and pending[0].status is SlotState.PENDING_BUY
|
||||
assert repo.list_slots(INST, status_filter={"error"}) == []
|
||||
|
||||
|
||||
def test_buy_by_slot_id_contract_pg(pg_clean):
|
||||
# dcy-020/REQ-061: slot_id is the canonical buy key against real PG.
|
||||
repo, svc, intents = _wire(pg_clean)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""list_slots status_filter (REQ-002/006 CHG-20260612-01, issue #25).
|
||||
|
||||
Pushes status filtering to the repository boundary so the slots RPC handler no
|
||||
longer fetches every row and filters in Python. None preserves prior behaviour
|
||||
(all slots), so existing unfiltered callers are unaffected.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from trex_core.domain.slot import SlotService, SlotState
|
||||
from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository
|
||||
|
||||
INST = "inst-1"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo():
|
||||
return InMemorySlotRepository()
|
||||
|
||||
|
||||
def _seed(repo):
|
||||
"""3 inactive + 1 holding + 1 pending_buy for INST."""
|
||||
svc = SlotService(repo, instance_key=INST)
|
||||
repo.reconcile_universe(INST, {f"pf-{i}": "SYM" for i in range(5)})
|
||||
# pf-0 -> holding
|
||||
s0 = repo.find_slot_by_portfolio(INST, "pf-0")
|
||||
svc.transit_slot(s0.slot_id, s0.status, SlotState.PENDING_BUY,
|
||||
reason="r", actor="a", symbol="005930")
|
||||
svc.transit_slot(s0.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING,
|
||||
reason="r", actor="a", avg_price=70_000)
|
||||
# pf-1 -> pending_buy
|
||||
s1 = repo.find_slot_by_portfolio(INST, "pf-1")
|
||||
svc.transit_slot(s1.slot_id, s1.status, SlotState.PENDING_BUY,
|
||||
reason="r", actor="a", symbol="005930")
|
||||
# pf-2,3,4 remain inactive
|
||||
return svc
|
||||
|
||||
|
||||
def test_none_returns_all(repo):
|
||||
_seed(repo)
|
||||
assert len(repo.list_slots(INST)) == 5
|
||||
assert len(repo.list_slots(INST, status_filter=None)) == 5
|
||||
|
||||
|
||||
def test_single_status_filter(repo):
|
||||
_seed(repo)
|
||||
holding = repo.list_slots(INST, status_filter={"holding"})
|
||||
assert len(holding) == 1 and holding[0].status is SlotState.HOLDING
|
||||
|
||||
inactive = repo.list_slots(INST, status_filter={"inactive"})
|
||||
assert len(inactive) == 3
|
||||
assert all(s.status is SlotState.INACTIVE for s in inactive)
|
||||
|
||||
|
||||
def test_pending_group_filter(repo):
|
||||
_seed(repo)
|
||||
pending = repo.list_slots(INST, status_filter={"pending_buy", "pending_sell"})
|
||||
assert len(pending) == 1 and pending[0].status is SlotState.PENDING_BUY
|
||||
|
||||
|
||||
def test_empty_when_no_match(repo):
|
||||
_seed(repo)
|
||||
assert repo.list_slots(INST, status_filter={"error"}) == []
|
||||
|
||||
|
||||
def test_instance_scoped(repo):
|
||||
_seed(repo)
|
||||
# other instance has no slots
|
||||
assert repo.list_slots("other", status_filter={"holding"}) == []
|
||||
@@ -66,9 +66,21 @@ class PostgreSQLSlotRepository:
|
||||
conn.rollback()
|
||||
raise
|
||||
|
||||
def list_slots(self, instance_key: str) -> list[Slot]:
|
||||
def list_slots(
|
||||
self, instance_key: str, status_filter: Optional[set[str]] = None
|
||||
) -> list[Slot]:
|
||||
with self._psycopg.connect(self._dsn) as conn:
|
||||
with conn.cursor() as cur:
|
||||
if status_filter:
|
||||
# issue #25: filter by status at the DB instead of fetching
|
||||
# every row and filtering in Python. ANY(%s) binds the set
|
||||
# as a single PG array parameter.
|
||||
cur.execute(
|
||||
f"SELECT {_SLOT_COLUMNS} FROM daily_slot "
|
||||
"WHERE instance_key = %s AND status = ANY(%s)",
|
||||
(instance_key, list(status_filter)),
|
||||
)
|
||||
else:
|
||||
cur.execute(
|
||||
f"SELECT {_SLOT_COLUMNS} FROM daily_slot WHERE instance_key = %s",
|
||||
(instance_key,),
|
||||
|
||||
@@ -168,6 +168,17 @@ def _handle_update_thresholds(
|
||||
|
||||
# ---- slots RPC handler (REQ-073/074) ----------------------------------------
|
||||
|
||||
# Maps a `trex.slots.req` filter token to the set of slot status values it
|
||||
# selects (issue #25 — pushed to the repository via list_slots status_filter).
|
||||
# "all" and unrecognized tokens map to None (no filter — REQ-073 AC-2).
|
||||
_FILTER_STATUS: dict[str, set[str]] = {
|
||||
"inactive": {"inactive"},
|
||||
"pending": {"pending_buy", "pending_sell"},
|
||||
"holding": {"holding"},
|
||||
"error": {"error"},
|
||||
}
|
||||
|
||||
|
||||
def make_slots_rpc_handler(
|
||||
slot_repo: SlotRepository,
|
||||
*,
|
||||
@@ -196,21 +207,21 @@ def make_slots_rpc_handler(
|
||||
filter_val = str(body.get("filter") or "all").lower()
|
||||
spy = bool(body.get("spy", False))
|
||||
|
||||
# Retrieve and filter slots by status
|
||||
slots = slot_repo.list_slots(instance_key)
|
||||
if filter_val == "inactive":
|
||||
slots = [s for s in slots if s.status.value == "inactive"]
|
||||
elif filter_val == "pending":
|
||||
slots = [s for s in slots if s.status.value in ("pending_buy", "pending_sell")]
|
||||
elif filter_val == "holding":
|
||||
slots = [s for s in slots if s.status.value == "holding"]
|
||||
elif filter_val == "error":
|
||||
slots = [s for s in slots if s.status.value == "error"]
|
||||
# "all" or unrecognized → no filter (REQ-073 AC-2)
|
||||
# issue #25: filter by status at the DB (REQ-002/006 list_slots
|
||||
# status_filter) instead of fetching every row and filtering in Python.
|
||||
# "all"/unrecognized → None (no filter, REQ-073 AC-2).
|
||||
status_filter = _FILTER_STATUS.get(filter_val)
|
||||
slots = slot_repo.list_slots(instance_key, status_filter=status_filter)
|
||||
|
||||
# REQ-034 CHG-01: update watchdog for holding slots unless spy=True
|
||||
# REQ-034 CHG-01: update watchdog for holding slots unless spy=True.
|
||||
# When the fetched set already contains every holding slot (filter "all"
|
||||
# or "holding"), reuse it and skip the second connection (issue #25 (2)).
|
||||
if not spy and watchdog_store is not None:
|
||||
for s in slot_repo.list_holding_slots(instance_key):
|
||||
if status_filter is None or filter_val == "holding":
|
||||
holding_slots = [s for s in slots if s.status.value == "holding"]
|
||||
else:
|
||||
holding_slots = slot_repo.list_holding_slots(instance_key)
|
||||
for s in holding_slots:
|
||||
watchdog_store.update(str(s.slot_id))
|
||||
|
||||
# Build merged slot rows (REQ-074)
|
||||
|
||||
@@ -51,8 +51,14 @@ class InMemorySlotRepository:
|
||||
"""Test seeding helper — insert a slot row directly."""
|
||||
self._slots[slot.slot_id] = slot
|
||||
|
||||
def list_slots(self, instance_key: str) -> list[Slot]:
|
||||
return [s for s in self._slots.values() if s.instance_key == instance_key]
|
||||
def list_slots(
|
||||
self, instance_key: str, status_filter: Optional[set[str]] = None
|
||||
) -> list[Slot]:
|
||||
return [
|
||||
s for s in self._slots.values()
|
||||
if s.instance_key == instance_key
|
||||
and (status_filter is None or s.status.value in status_filter)
|
||||
]
|
||||
|
||||
def fetch_slot(self, slot_id: UUID) -> Optional[Slot]:
|
||||
return self._slots.get(slot_id)
|
||||
|
||||
@@ -37,8 +37,15 @@ class SlotRepository(Protocol):
|
||||
...
|
||||
|
||||
# --- daily_slot CRUD ---
|
||||
def list_slots(self, instance_key: str) -> list[Slot]:
|
||||
"""Return all slots owned by this instance."""
|
||||
def list_slots(
|
||||
self, instance_key: str, status_filter: Optional[set[str]] = None
|
||||
) -> list[Slot]:
|
||||
"""Return slots owned by this instance.
|
||||
|
||||
status_filter: when given, only slots whose status value is in the set
|
||||
are returned (filtered at the storage boundary — REQ-006 pushes this to
|
||||
SQL). None returns all slots (prior behaviour). issue #25.
|
||||
"""
|
||||
...
|
||||
|
||||
def fetch_slot(self, slot_id: UUID) -> Optional[Slot]:
|
||||
|
||||
Reference in New Issue
Block a user