Skip to content

v2.12.0 — 옵션 human-readable 전환 (Tranche E)#29

Merged
gejyn14 merged 41 commits into
mainfrom
feature/v2.12.0-human-readable
Jul 19, 2026
Merged

v2.12.0 — 옵션 human-readable 전환 (Tranche E)#29
gejyn14 merged 41 commits into
mainfrom
feature/v2.12.0-human-readable

Conversation

@gejyn14

@gejyn14 gejyn14 commented Jul 19, 2026

Copy link
Copy Markdown
Owner

숫자 API 코드를 그대로 받던 CLI 옵션 147개를 사람이 읽을 수 있는 이름으로 바꿉니다. market.py 41개 커맨드, stock.py 13개 커맨드가 대상입니다.

테스트 895 → 1580, ruff clean.

먼저 볼 것 — 이 릴리스는 비파괴적이지 않습니다

계획 단계에서는 "표기만 바꾸는" 작업으로 잡았지만, 실제로는 147개 중 125개가 breaking입니다.

전환 대상 옵션의 대부분은 type=이 없는 자유 텍스트였습니다. 즉 API가 받는 값이면 뭐든 그대로 통과했습니다. 여기에 enum을 씌우면 스펙에 없는 값이 전송 전에 거부되므로, 동작하던 표면이 줄어듭니다.

스펙에 있는 숫자 코드는 계속 통과합니다. 다만 스펙 밖에는 종전 기본값이던 --vol-type 0, --vol-cnd 0이나 elw change-rank --right-type 005 같이 실제로 쓰이던 값도 들어갑니다. 쓰던 값이 여기 해당하는지는 CHANGELOG의 Breaking 절에서 확인하세요.

Fixed — 전송값이 틀렸던 자리 10곳

전환 과정에서 드러난 결함입니다. 옵션에 enum을 씌우면 기본값이 실제로 유효한지 검사하게 되는데, 그때 기본값이 스펙 값 목록에 없는 자리가 나왔습니다.

  • ka10016/17/18/19 trde_qty_tp "0""00000", ka10020 → "0000", ka10027·ka10028 trde_qty_cnd"0000" — 전체조회 의도가 그대로 유지됩니다
  • ka10021/22/23은 스펙에 전체조회 값이 없어 "1"/"5"/"5"로 고쳤습니다. 이 셋은 기본 조회 결과가 좁아질 수 있습니다(CHANGELOG에 별도 기재)

값 커버리지 확대

  • --exchange all(3:통합)을 ka10030/ka90006/ka90007/ka40004에서 쓸 수 있습니다. v2.11.0은 rank volumerank amount가 서로 달랐는데 그 불일치가 해소됩니다
  • chart intraday-investor --market all(000:전체)
  • API 약어 옵션 5개에 human 별칭 추가: --stk-cnd--stock-cond 등. 구 이름은 그대로 동작합니다
  • account--exchangeall 추가 — 전송값은 종전 그대로(%, KRX, NXT, SOR)

검토

브랜치 전체를 세 갈래로 검토했습니다: 전송값 불변성, CHANGELOG 정확성, 상수 위생.

전송값 검토는 리프 커맨드 × 허용값 × 표기(신·구) 전수를 실제 전송 body로 캡처해 base와 대조했습니다. 1192개 공통 케이스에서 움직인 필드는 위 10곳뿐입니다. 상수 위생 검토는 175개 상수의 부분집합·극성 관계 242건을 양방향으로 계산했습니다.

검토에서 나온 것 중 가장 중요했던 건, 리터럴로 고정한 테스트인데도 형제 상수와 값이 같은 키만 뽑아 쓰고 있어 실제로는 아무것도 막지 못하던 자리입니다. ka10027의 상수 3개를 형제와 합쳐도 1552개 테스트가 전부 통과했습니다. 이후 병합 가능한 상수쌍 207건을 실제로 합쳐보는 하네스로 전수 검사해, 무력했던 고정 16건을 찾아 전부 고쳤습니다.

남긴 것

  • gold chart-minute --price-typeprogram stock-daily --unit은 기본값이 빈 문자열이라 그대로 뒀습니다. HumanChoice에 빈 문자열 키가 없어 감싸면 기본 호출이 깨집니다. 키 자체를 생략하는 방식이 맞지만 그건 전송 바이트가 바뀌므로 별도 작업입니다
  • sector 그룹은 --sector-code(옵션)와 INDS_CD(위치 인자)로 어휘가 둘입니다. 위치 인자 metavar를 바꾸는 게 맞는 방향입니다

🤖 Generated with Claude Code

gejyn14 and others added 30 commits July 19, 2026 23:37
ntl_tp/high_low_close_tp/stk_cnd/crd_cnd/updown_incls/updown_tp/sort_tp/
trde_gold_tp/high_low_tp/flu_tp/tm_tp/pric_cnd/trde_tp/rt_tp/pric_tp 등
31개 옵션 자리를 신규 상수(_constants.py)와 HumanChoice로 감싸 사람이 읽는
이름을 받게 했다. 전송값은 그대로 유지(표기만 변경) — 8개 커맨드 전부
전환 전/후 기본 호출 body가 byte-identical함을 확인.

trde_qty_tp(--vol-type)는 8곳 전부 이번 범위에서 제외했다: 현재 기본값
"0"이 8개 API 스펙의 유효 코드 목록에 없는 사전 결함이라(0000/00000
zero-pad 또는 애초에 "전체" 값 자체가 없음), HumanChoice로 감싸면 기본
호출이 BadParameter로 깨진다(재현 확인). 값 자체를 고치는 것은 전송값
변경이라 이 태스크(표기 전용) 범위 밖 — 상세 발견 내용은
.superpowers/sdd/task-31a-report.md 참고.

머지 해저드 규약에 따라 값 집합이 동일해 보이는 stk_cnd/crd_cnd도
API별로 별도 상수로 유지(예: NEW_HIGH_LOW_STK_CND/NEAR_HIGHLOW_STK_CND/
SURGE_STK_CND).

market/human_ux 테스트 48개 추가(기본 body 불변 스모크 + human 옵션 변환 +
레지스트리), HumanChoice 데코레이터 pinned count 55→86 갱신.
8개 rank 커맨드의 trde_qty_tp 기본값이 raw "0"이었는데, 8개 API 어느
스펙에도 "0"은 존재하지 않는다. 워크북 8개 시트와 kwcli arguments.csv를
교차확인해 API별 스펙 값으로 교정하고 함께 HumanChoice로 전환했다.

8개 전부 Required=Y라 ka10038 dt처럼 키를 생략하는 선택지는 없었다.
ka10016~20은 문서에 "전체/장시작전" 값이 있어 의도가 그대로 보존되고,
ka10021~23은 "전체" 개념 자체가 없어 사다리 최하단(필터를 가장 적게 거는
값)을 골랐다 — 이 셋은 기본 호출 결과가 좁아질 수 있다.

전송 바이트 변경 (--vol-type 미지정 기본 호출):
- ka10016/17/18/19: "0" -> "00000"
- ka10020: "0" -> "0000"
- ka10021: "0" -> "1"
- ka10022/23: "0" -> "5"

8개 코드북은 자릿수가 전부 달라(5자리/4자리/무패딩) 상수 이름에 wire
형식을 박고 주석에 짝 목록과 절대 합치지 말 것을 남겼다. ka10023은
ka10030 VOLUME_RANK_QTY_TYPE의 부분집합처럼 보이지만 "0"이 없다 —
이게 이번 결함의 발생 경로로 보인다.

테스트 986 passed. 8개 커맨드 각각 정확한 기본 body를 고정했고,
market.py만 되돌려 41개가 실제로 실패하는 것을 확인한 뒤 복원했다.
test_human_ux 데코레이터 카운트는 트리 순회로 재도출해 86 -> 94.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
stk_cnd/sort_tp/가격조건 31개 자리가 "정상 값이 맞게 나간다"는 테스트만
갖고 있어, 상수를 상위집합 형제로 바꿔치기해도 전부 통과했다. 기존
test_rank_vol_type_rejects_name_absent_from_that_api의 모양을 그대로 써서
세 계열에 거부 테스트 36개를 추가한다.

값 목록은 docs/미국 REST API 문서.xlsx의 각 api_id 시트에서 직접 확인했고
8개 시트 모두 현재 상수와 일치했다.

교차 바꿔치기 16건을 실제로 넣어 전부 실패하는 것을 확인했다. 바이트가
동일한 클러스터(*_CREDIT_CND 5개, *_QTY_TYPE_5DIGIT 4개 등)는 테스트로
구분이 불가능해 이름과 주석이 유일한 방어선으로 남는다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
전송 바이트가 바뀌고 종전에 받던 입력을 거부하므로 Unreleased에 기록한다.
기본값 8건은 의도가 보존되어 Fixed로, 전체 개념이 없는 ka10021/22/23의
하한 부과와 --vol-type 0 거부는 Breaking으로 분리했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ka10030/ka10032/ka10038은 Tranche B에서 이미 전환돼 있어 건너뛰고, 나머지
9개 커맨드(ka10027/29/31/33/34/35/36/37/39)의 코드 계열 옵션 28개를
HumanChoice로 감쌌다. ka10034/36/37의 dt(기간)는 값이 완전히 동일해
PERIOD_TODAY_PREV_5_60 하나로 수렴시켰다.

ka10027 --vol-cnd(trde_qty_cnd)는 부수적으로 와이어 값 결함도 고쳤다 —
기존 기본값 raw "0"은 4자리 zero-pad 스펙(0000~1000) 어디에도 없던 값이라
"0000"으로 교정했다(전송 바이트가 바뀌는 fix, CHANGELOG에 별도 기재 예정).

_constants.py에 26개 상수를 추가했다. 값 집합이 같아 보이는 자리는 31a와
동일하게 API별로 전부 분리했고(예: RANK_CHANGE_STK_CND vs
EXPECTED_CHANGE_STK_CND), 그 중 일부는 값이 100% 동일해 어떤 테스트로도
구분할 수 없다 — 이름 규약과 주석이 유일한 방어선이다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ka10027 --vol-cnd(trde_qty_cnd) 기본값 "0"→"0000" 교정(전송값 변경, breaking)과
ka10027~39 9개 커맨드 28개 옵션의 HumanChoice 전환(비파괴적, 하위호환)을
Unreleased 섹션에 기록한다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
리뷰어가 superset-closure 스크립트로 발견한 2개 미핀 해저드를 테스트로
고정한다:

- CREDIT_RATIO_CREDIT_CND(ka10033)는 EXPECTED_CHANGE_CREDIT_CND(ka10029)의
  진짜 부분집합이었다(exclude-overlimit/short 누락) - rank credit-ratio
  --credit exclude-overlimit|short 거부 테스트 추가.
- FOREIGN_CONSECUTIVE_BASE_DATE(ka10035)는 PERIOD_TODAY_PREV_5_60
  (ka10034/36/37)과 BROKER_TOP_PERIOD(ka10039) 양쪽의 진짜 부분집합이었다
  - base_dt_tp(ka10035)와 dt(ka10034/36/37, ka10039)라는 서로 다른 필드가
  today/previous라는 같은 키 이름을 쓰는 cross-field 해저드라 rank
  foreign-consecutive --base-date 5d|20d 거부 테스트 추가 + 주석에 두
  형제 상수 이름을 명시.
- PERIOD_TODAY_PREV_5_60에 3-API(ka10034/36/37) 공유 커플링 경고 한 문장
  추가(향후 dt 값이 갈라지면 제자리 수정 대신 분리할 것).

두 신규 해저드 모두 실제 치환으로 fail 확인 후 복원. 나머지 7개 기존
superset 해저드(RANK_CHANGE_CREDIT_CND, CREDIT_RATIO_QTY_TYPE,
CREDIT_RATIO_STK_CND, FOREIGN_CONSECUTIVE_SIDE, BROKER_TOP_QTY_TYPE,
BROKER_TOP_SIDE, BROKER_TOP_PERIOD)는 기존 테스트가 이미 핀 고정하고
있음을 동일한 방법으로 재확인(task-31b-report.md 참고).

pytest tests/ -q: 1093 passed (1089 + 4). ruff clean.
…자코드를 HumanChoice로 전환

market rank 그룹의 마지막 5개 커맨드(net-buyer/same-net-trade/investor-top/
afterhours-change/foreign-inst)의 숫자 API 코드 옵션을 HumanChoice로
전환했다. 순수 표기 전환 - 다섯 커맨드 전부 기본 호출 body가 전환 전과
동일함을 테스트로 고정했다(워크북·kwcli 이중 확인, wire-value fix 없음).

net-buyer(ka10042)의 date-type/pot-type/sort는 워크북으로 값이 완전히
동일함을 확인해 기존 stock analysis trader-analysis(ka10043)의 코드북을
그대로 공유한다. investor-top(ka10065)/foreign-inst(ka90009)의 --unit도
동일한 이유로 서로 공유한다. net-buyer의 --period(dt)는 값→라벨이
단위접미사 부착만으로 유도되는 폐쇄집합이라 이번 전환에서 제외했다.

superset-closure 스크립트(AST 기반, _constants.py 전체 dict 128개 대상)로
이번 태스크가 건드린 15개 상수 전수를 조사해 3개 해저드를 찾아 테스트로
핀 고정했다(SAME_NET_TRADE_SIDE/INVESTOR_TOP_SIDE가 FOREIGN_BROKER_SIDE의
부분집합, TRADER_ANALYSIS_POSITION이 PERIOD_TODAY_PREV_5_60/
BROKER_TOP_PERIOD의 cross-field 부분집합) - 실제 상수를 형제 상위집합으로
바꿔치기해 신규 테스트가 fail하는지 실측 확인했다.

test_human_ux.py의 HumanChoice 데코레이터 카운트를 트리 순회로 재도출해
122->138로 갱신했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Unreleased에 Non-breaking 항목 추가. 순수 표기 전환이라 Fixed/Breaking
섹션은 없다 - 일부 옵션이 이전엔 타입 제약 없는 자유 텍스트였다가
HumanChoice로 좁혀진 점만 별도 문단으로 명시했다(매핑에 있는 값의 동작은
불변).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
주석은 api_id 2개가 공유한다고 적혀 있었지만 실제로는 5곳이다. 공유 범위를
과소 기재하면, 한 곳을 분리한 뒤 "이제 하나만 남았다"고 오해해 나머지를
조용히 깨뜨릴 수 있다. 다섯 자리를 파일·줄까지 명시했다.

ka90009 시트는 "1:금액(천만), 2:수량(천)"이라 character-for-character
동일하다는 서술도 사실과 달랐다. 괄호 안이 응답 단위 주석이라 요청
코드북은 같다는 점을 대신 적었다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…1/ka90001) 숫자코드를 HumanChoice로 전환

sector investor/current/stocks/daily/codes, theme groups 7개 옵션을
human-readable 이름으로 전환. 순수 표기 전환이며 기본 호출 body는
종전과 동일(wire-value fix 없음, 워크북·kwcli arguments.csv 이중 확인).

sector current/stocks/daily(ka20001/02/09)는 워크북 문구가
character-for-character 동일해 SECTOR_PRICE_MARKET 하나로 수렴시켰고,
sector codes(ka10101)는 그 진짜 상위집합(kospi100/krx100 추가)이라
SECTOR_CODES_MARKET으로 분리했다. sector investor의 --unit은 기존
AMT_QTY_TP_0_1(ka10131과 공유)을 재사용했다.

형제 상수 상위집합 치환을 막는 거부/핀 테스트를 추가하고, cp 백업 후
실제로 상수를 바꿔치기해 테스트가 실패하는지 검증했다(SECTOR_PRICE_MARKET
↔ SECTOR_CODES_MARKET, AMT_QTY_TP_0_1 ↔ AMT_QTY_TP_1_2, THEME_LOOKUP_KIND
↔ PRODUCT_TYPE 3건 모두 확인). test_human_ux.py의 HumanChoice 데코레이터
고정 개수를 138 → 145로 갱신(트리 순회 재도출).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Task 32 — market sector/theme 6개 커맨드의 순수 표기 전환을
Unreleased에 기록. wire-value fix 없음.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
두 시트는 같은 매핑이지만 표기까지 같지는 않다. ka10051은 "금액:0, 수량:1",
ka10131은 "0:금액, 1:수량"으로 라벨과 코드 순서가 반대다. 결론은 맞고 근거
서술만 과장이었다. 이 규율은 주석을 믿을 수 있어야 성립하므로 정확도를 맞춘다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…/10,ka50079/80~83/91/92/101,ka90005~08/10/13) 숫자코드를 HumanChoice로 전환

market etf/elw/gold/program 4개 그룹, 21개 커맨드의 숫자 API 코드 옵션을
human-readable 이름으로 전환. 순수 표기 전환 - 전환된 옵션 전부 기본 호출
body가 종전과 동일함을 테스트로 고정했다(워크북·kwcli arguments.csv 이중
확인, wire-value fix 없음).

elw 그룹은 --right-type(권리구분)이 API마다 자릿수/EX 포함 여부가 달라
4개 상수로 분리했고(ELW_RIGHT_TYPE_3DIGIT/1DIGIT, ELW_RANK_RIGHT_TYPE_3DIGIT),
--exclude-expired(거래종료ELW제외)는 5개 api_id(ka30001/02/04/09/10)가 값이
완전히 동일함을 확인해 EXCLUDE_ENDED_ELW로 수렴시켰다(ELW_BROKER_END_SKIP
리네임). gold는 4개 api_id(ka50079/81/82/83)가 GOLD_PRICE_TYPE을 공유한다.
ka50080/ka90013은 Required=N + 기존 기본값이 빈 문자열이라 전환하지 않고
raw로 남겼다(억지로 0/1을 채우면 전송 바이트가 바뀌는 fix가 되므로).

v2.11.0에서 미룬 --exchange 3자리 정리도 함께 처리: rank volume(ka10030)/
program arbitrage-balance(ka90006)/program cumulative(ka90007)가 rank
amount(ka10032)와 동일한 패턴(EXCHANGE_ALL)으로 all(통합)을 받는다 - 순수
확대(widening), 기존 KRX/NXT 호출·기본값은 그대로 동작한다.

형제 상수 상위집합 치환을 막는 거부 테스트와, 키는 겹치지만 값이 다른
polarity 해저드(predicate 2)를 리터럴로 고정하는 pin 테스트를 추가했다.
cp 백업 후 실제로 상수를 바꿔치기해 9개 시나리오 전부 테스트가 실패하는지
검증했다(task-33-report.md 참고) - 그 과정에서 parametrize(list(MAPPING.items()))
형태의 human-options 테스트는 자기참조 때문에 polarity 해저드를 못 잡는다는
것을 발견해, 해당 pin 테스트는 리터럴 문자열로 다시 작성했다.

test_human_ux.py의 HumanChoice 데코레이터 고정 개수를 145 → 170으로
갱신(트리 순회 재도출, +25).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…nge 3자리 정리 기록

Unreleased 섹션에 21개 커맨드의 human-readable 이름 전환(non-breaking)과
rank volume/program arbitrage-balance/program cumulative의 --exchange
all(통합) widening을 기록.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
워크북 ka40004 시트의 stex_tp가 "1:KRX, 2:NXT, 3:통합"으로 이미 넓힌
ka10030/ka90006/ka90007과 동일한데 KRX/NXT만 받고 있었다. 같은 한 줄
패턴(click.Choice(list(EXCHANGE_ALL)) + EXCHANGE_ALL[stex_tp])을 적용한다.

기존 KRX/NXT 호출과 기본값(KRX)은 전송값이 그대로인 순수 widening이다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ange 확대 고정

AMT_QTY_TP_1_2는 이번 트랜치에서 5곳 → 7곳으로 확장됐는데, AMT_QTY_TP_0_1
(amount=0,quantity=1)·SAME_NET_TRADE_SORT(quantity=1,amount=2)와 키가 같고
값만 다른 극성 해저드를 갖는다. 두 dedicated 테스트가
parametrize(list(AMT_QTY_TP_1_2.items())) 형태라 상수를 형제로 바꿔치기해도
수집 시점에 바뀐 값을 그대로 가져와 자기참조로 통과했다.

실측: 상수를 0/1 극성으로 치환하면 종전 형태는 2 passed, 리터럴로 바꾼
형태는 정확히 실패한다.

etf all --exchange 확대도 함께 고정한다(all=3 신규, KRX=1/NXT=2 불변).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
브랜치 전체(base 6c76b21 → HEAD) 감사 결과, 전환한 115개 옵션은 전부
베이스에서 type= 없는 StringParamType이었다. 아무 문자열이나 받던 자리를
유한 집합으로 바꾼 것은 받아들이는 값의 집합이 줄어든 변경이므로
Breaking이다. 다섯 청크(31a/31b/31c/32/33) 전부 Non-breaking으로 적혀
있던 것을 바로잡는다.

스펙 합법 호출은 하나도 깨지지 않으므로 코드 수정이 아니라 분류 정정이다.
다섯 elw 커맨드를 3자리 권리구분 표 하나로 돌리던 스크립트가
change-rank/balance-rank의 005에서 깨지는 구체 예를 같이 적었다.

그 밖에:
- --right-type 상수 개수 4개 → 3개 정정
- --exchange 확대 3곳 → 4곳(etf all 추가)
- gold chart-minute/program stock-daily 미전환 근거를 HumanChoice에
  빈 문자열 키가 없다는 실제 이유로 교체 + ka10038 dt 패턴 후속 큐잉
- 청크마다 반복되던 Fixed/Breaking/Non-breaking을 한 벌로 병합

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…/11/44/45/58/59/61) 숫자코드를 HumanChoice로 전환

7개 커맨드(daily-price/today-exec/today-volume/analysis price-cluster/
analysis open-change/analysis instant-volume/analysis vi-trigger/
analysis warrant/investor daily-trade/investor stock-institution/
investor daily-by-investor/investor by-stock/investor by-stock-total)의
옵션 31개를 human-readable 이름으로 전환했다. 8개는 전환 전 자유
텍스트였다(open-change의 stock-cond/credit-cond/amount-cond/volume-cond,
instant-volume의 price-type, vi-trigger의 volume-type/amount-type,
daily-by-investor의 investor-type) — breaking. by-stock-total의 --trade는
스펙에 없던 1/2을 이제 거부한다(이미 click.Choice였던 자리의 값 집합
축소, breaking).

open-change의 --volume-cond(trde_qty_cnd) 기본값 "0"이 4자리 zero-pad
스펙 어디에도 없어 "0000"으로 교정했다(fix, market rank change와 동일
결함 패턴).

trader-analysis의 --days(dt)는 I2 규칙 재적용으로 raw 텍스트로 되돌렸다
— market.py:503(ka10042)과 동일한 자기서술적 수량 프리셋인데 v2.11.0에서
실수로 HumanChoice가 됐었다. TRADER_ANALYSIS_PERIOD_5_120 상수는 사용처가
없어져 제거했다.

merge-hazard: DAILY_PRICE_DISPLAY는 AMT_QTY_TP_0_1과 극성이 반대,
TODAY_PREV_1_2는 다른 today/previous 계열과 극성이 반대,
INVESTOR_DAILY_TRADE_SIDE/DAILY_BY_INVESTOR_TRADE_SIDE는
BROKER_TOP_SIDE와 극성이 정반대, VI_TRIGGER_SESSION은
VOLUME_RANK_SESSION과 after-hours 값이 다름, OPEN_CHANGE_AMOUNT_CND는
VOLUME_RANK_AMOUNT_TYPE과 50m 값이 다름 — 전부 별도 상수로 분리하고
주석에 절대 합치지 말 것을 명시했다. AMT_QTY_TP_1_2/TRDE_TP_NET_BUY_BUY_SELL
은 ka10059/ka10061로 확장(신규 상수 아님, 커플링 주석 갱신).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
daily_price~by_stock_total 13개 커맨드에 대해: 기본 호출 바디 고정
테스트, human 이름->wire 값 enum 테스트, 극성 해저드 자리(TODAY_PREV_1_2,
OPEN_CHANGE_SORT, OPEN_CHANGE_AMOUNT_CND, INSTANT_VOLUME_MARKET,
VI_TRIGGER_SESSION, INVESTOR_DAILY_TRADE_SIDE, DAILY_BY_INVESTOR_TRADE_SIDE,
INST_FOREIGN_PRICE_TYPE)의 리터럴 핀 테스트, 형제 상위집합 상수(예:
RANK_CHANGE_QTY_CND/STK_CND/CREDIT_CND, VOLUME_RANK_AMOUNT_TYPE,
FOREIGN_PERIOD_SIDE, TRDE_TP_NET_BUY_BUY_SELL)로부터만 있는 이름을
거부하는 테스트를 추가했다. trader-analysis --days 되돌림에 맞춰
관련 테스트 3개를 갱신했다.

값을 고정하는 모든 parametrize는 하드코딩 리터럴을 쓴다(매핑을
순회해 기대값을 도출하는 자기참조 패턴 금지). 10개 형제 상수
바꿔치기를 실제로 실행해 해당 테스트가 실패하는지 검증했다
(cp 백업/복원, git checkout 미사용).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Task 34a가 stock.py에 순증 30개(신규 31 - trader-analysis --days 되돌림
1) HumanChoice 데코레이터를 추가했다. 개수는 트리 순회로 직접 재도출했다
(추정 아님). 매핑 레지스트리에 신규 상수 24개를 추가하고
TRADER_ANALYSIS_PERIOD_5_120(사용처 없어짐)을 제거했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Unreleased의 기존 Fixed/Breaking/Non-breaking 섹션에 Task 34a 항목을
추가했다(새 헤딩 없음) — open-change --volume-cond 기본값 fix,
자유 텍스트 8곳의 breaking 좁힘, trader-analysis --days human 이름
제거, by-stock-total --trade 값 집합 축소, 나머지 23개 옵션의
non-breaking 확대.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
trader-analysis(ka10043) --days를 v2.11.0에 배포된 HumanChoice로
되돌린다. Task 34a는 이걸 I2 규칙(수량 프리셋 유지) 근거로 raw
텍스트로 되돌렸는데, 그 결과 --days 999 같은 스펙 밖 값이 exit 0으로
조용히 전송되게 됐다 — 이미 배포돼 검증되고 있던 걸 걷어내 회귀를
만든 셈이라 반대 방향(HumanChoice 유지)이 맞다. net-buyer(ka10042)의
--period는 동일 필드(TRADER_ANALYSIS_PERIOD_5_120, 워크북 값 동일
확인)인데 여태 자유 텍스트였던 쪽이 비대칭의 원인이라 함께
HumanChoice로 전환해 두 API가 상수를 공유하게 맞췄다.

나머지 3건은 형제 상수 해저드를 테스트로 고정한다:
- open-change --stock-cond 거부 테스트를 "exclude-liquidation" 한
  값에서, OPEN_CHANGE_STK_CND의 진짜 상위집합 5개(LIMIT_MOVE/
  RANK_CHANGE/EXPECTED_CHANGE/VOLUME_SURGE/AFTERHOURS_CHANGE_STK_CND)가
  갖는 초과 키의 합집합으로 일반화 — LIMIT_MOVE_STK_CND로의 오치환은
  기존 테스트로는 못 잡았다(검증 완료, 원복함).
- DAILY_BY_INVESTOR_TYPE(ka10058)/INVESTOR_TOP_ORGN(ka10065)은 10개
  키 값이 동일하지만 어느 쪽도 진짜 부분집합이 아니라(개별
  고유값 있음) superset-closure 두 predicate 다 못 잡는 부분 겹침
  해저드다 — 상호 배타적인 이름 거부를 테스트로 못 박았다.
- stock daily-price 부근의 MARKET_ALL 자기참조 파라미터화
  (list(MARKET_ALL.items()))를 하드코딩 리터럴로 교체 — 극성 스왑
  시뮬레이션으로 새 테스트가 실제로 깨지는 것까지 확인했다.

tests/test_human_ux.py의 HumanChoice 데코레이터 고정 개수를
200→202로 갱신(trader-analysis --days 원복 +net-buyer --period 전환)
하고 두 옵션을 이름으로도 못 박았다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
trader-analysis --days가 raw로 되돌아갔다던 Breaking 항목을 지웠다
(v2.11.0부터 이미 HumanChoice였던 걸 원복했을 뿐 이번 릴리스
기준으로는 변화가 없다). 대신 net-buyer --period가 자유 텍스트에서
HumanChoice로 좁아진 걸 새 Breaking 항목으로 적었고, Non-breaking
절의 "--period는 raw로 남겼다"는 문장도 지금 상태에 맞게 고쳤다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…64) 숫자코드를 HumanChoice로 전환

program-top(ka90003)의 --trade/--amount-qty, chart tick~year(ka10079~94)의
--adjusted, chart investor(ka10060)/intraday-investor(ka10064)의
--amount-qty/--trade/--unit 13개 옵션을 human-readable 이름으로 전환했다.
전환 전 전부 click.Choice였고 값 집합이 줄지 않아 non-breaking(순수 확대)이다.

신규 상수: PROGRAM_TOP_SIDE(ka90003 전용, 5개 형제 상수와 극성/구분불가
해저드 확인), CHART_ADJUSTED_PRICE(6개 api_id 공유, GOLD_PRICE_TYPE과
값 동일한 구분불가 클러스터). 기존 상수 확장: AMT_QTY_TP_1_2(12곳으로),
TRDE_TP_NET_BUY_BUY_SELL(4곳으로, 사전 예약돼 있던 자리), INVESTOR_BY_STOCK_UNIT
(3곳으로).

chart tick/minute의 --range/--interval(tic_scope)은 값=라벨 자기서술적
수량이라 전환하지 않았다. lending trend/by-stock의 --all(all_tp)은 스펙에
값이 하나만 문서화돼 있어(반대값 불명) 미확인으로 raw 텍스트 유지.
program-top/chart tick~year/chart investor/chart intraday-investor 13개
옵션 전부에 기본 body 불변 테스트 + 하드코딩 리터럴 enum 테스트를 추가했다.

PROGRAM_TOP_SIDE가 새로 만든 형제 상수 해저드 2건(NETSLMT_TP_NET_BUY_ONLY의
진짜 부분집합, FOREIGN_PERIOD_SIDE의 진짜 부분집합)과 극성 해저드 5건
(ELW_BROKER_SIDE 등)을 리터럴 거부/핀 테스트로 방어했다. 전부 상수를
형제 값으로 바꿔치기해 테스트가 실제로 실패하는지 확인 후 복원했다
(cp 백업, git checkout 미사용).
Task 34b가 전환한 13개 옵션(program-top 2 + chart tick~year 6 +
chart investor 3 + chart intraday-investor 2)만큼 순증. 개수는 트리
순회 스크립트로 직접 재도출했다(추정 아님). PROGRAM_TOP_SIDE/
CHART_ADJUSTED_PRICE를 매핑 레지스트리에 추가하고, lending trend/
by-stock의 --all(all_tp)이 전환되지 않았음을 이름으로 못 박았다.
Task 34b 6개 항목을 기존 Unreleased Non-breaking 섹션에 추가(새 헤딩
없음). 전부 전환 전 click.Choice였던 자리라 breaking 없음.
click.Choice(["kospi","kosdaq"])라 스펙의 000:전체(mrkt_tp)에 도달할 방법이
없던 사전 존재 결함을 HumanChoice(MARKET_ALL)로 교체해 고쳤다. kospi/kosdaq
전송값과 기본값은 그대로라 순수 확대(widening) — 리터럴 핀 테스트로 확인.

ka90003(program-top)의 --market은 스펙상 mrkt_tp가 P00101/P10102 두 값뿐이라
건드리지 않았다(워크북 재확인 완료).
chart intraday-investor의 --market 값 확대(kospi/kosdaq만 받던 자리에 all
추가)를 Non-breaking 섹션에 추가 — --exchange 스윕과 같은 종류의 수정.
gejyn14 and others added 11 commits July 20, 2026 03:36
AMT_QTY_TP_1_2의 12곳 체크리스트가 34b 이전부터 이미 전부 stale이었다
(market.py:776는 실제로 797, stock.py:1077은 실제로 1140 등). 줄번호는
편집마다 바로 어긋나므로 api_id + 파일명만 남기고 걷어냈다 — api_id는
grep으로 바로 찾을 수 있고 편집에 영향받지 않는다. 같은 파일의 다른
줄번호 참조 4곳(TRDE_TP_NET_BUY_BUY_SELL/AMT_QTY_TP_1_2/GOLD_PRICE_TYPE/
TRADER_ANALYSIS_PERIOD_5_120 근처 주석)도 같은 이유로 정리했다.
market.py의 --stk-cnd/--vol-cnd/--price-cnd/--amount-cnd/--inds-cd에
--stock-cond/--volume-cond/--price-cond/--amount-cond/--sector-code
별칭을 추가한다. 구 이름은 하위호환으로 남기고 --help에는 새 이름이
대표로 뜬다.

account.py의 --exchange 3곳(kt00007/kt00009/kt00015)에 "all" 별칭을
추가한다(dmst_stex_tp="%" 그대로 전송, 사용자 결정 E-1 — 전송값을
숫자코드로 바꾸는 계획서 원안은 따르지 않았다). kt00007/kt00009는
SOR 포함 4값, kt00015는 SOR 없는 3값이라 ACCOUNT_EXCHANGE_WITH_SOR/
ACCOUNT_EXCHANGE_NO_SOR로 상수를 분리했다.

tests/test_human_ux.py의 HumanChoice 데코레이터 고정 개수를 216→219로
갱신(트리 순회로 재도출)하고 매핑 레지스트리에 두 상수를 추가했다.
account.py/market.py에 구 이름·구 값·신 이름 회귀 테스트를 추가했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
단일 사이트(rank new-highlow/stk_cnd)만 고정하던
test_rank_new_highlow_help_shows_human_name_first를 Click 트리를
순회해 human/legacy 이중 철자 옵션 20곳을 전부 찾아 parametrize하는
테스트로 교체. 나머지 19곳(volume-cond 3, price-cond 3, amount-cond,
sector-code, stock-cond 11)은 선언 순서가 뒤바뀌어도 suite가 green을
유지했던 커버리지 공백이었다.

human/legacy 판별은 옵션 스펠링 길이(human이 항상 더 긴 풀어쓴 이름)로
결정하고 Click의 실제 선언 순서는 참조하지 않는다 — 선언 순서로
역산하면 항상 참인 순환 검증이 되어 순서가 뒤바뀌어도 잡아내지 못한다.
세 사이트(stock-cond/volume-cond/sector-code)에서 실제로 선언 순서를
뒤집어 테스트가 각각 실패하는 것을 확인 후 원복했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
AGENTS.md의 describe/find/api list 예제 8곳이 -f json을 서브커맨드 뒤에
적어 그대로 실행하면 "No such option '-f'"로 깨졌다(-f/--format은 루트
그룹 옵션이라 서브커맨드보다 앞에 와야 한다). --fields 예제 2곳도
QTY 위치인자를 --qty 옵션으로 잘못 적어 같은 방식으로 깨졌다. 모든 예제를
실제로 실행해 확인 후 고쳤다. README.md에도 같은 순서 버그가 하나 더
있었다(describe --paths -f json).

README.md의 human-readable 옵션 범위 안내도 갱신했다 — 이전엔 "account/
market 19개, stock은 범위 밖"이라고 적혀 있었는데 트랜치 E에서 market
115개 + stock 31개가 추가로 전환되어 더 이상 사실이 아니었다.
명령 그룹별 전환 옵션 수를 표로 정리했다 — market 41개 커맨드/115개
옵션(전부 자유 텍스트 이력이라 Breaking), stock 13개 커맨드/31개 옵션
(Breaking 8 / Non-breaking 23). 숫자는 base(6c76b21)와 HEAD의
market.py/stock.py를 옵션 단위로 직접 대조해 검증했다. --exchange/
--market 값 확장, --vol-type/--vol-cnd 기본값 교정은 raw 코드를 human
이름으로 바꾼 것과 성격이 달라 표에서 제외하고 각각 원래 있던 자리
(Non-breaking 서술, Fixed)를 그대로 가리키게 했다.
트랜치 E(human-readable 옵션 전환) 마지막 태스크 — 버전을 2.12.0으로
올리고 CHANGELOG [Unreleased]를 [2.12.0] - 2026-07-20으로 확정했다.
231개 명령어 기준으로 kiwoom_cli_commands.xlsx도 재생성했다(v2.11.0
때는 61개 커밋만큼 밀려 있었다).
전수 검증(base 6c76b21 vs HEAD의 Click 옵션 수용값 집합 비교)으로 확인한
네 건의 과소보고를 고쳤습니다.

- market rank 순매수~외국계기관 행 16 → 17
  (net-buyer --period가 빠져 있었음)
- 자유 텍스트였던 stock 옵션 목록에 open-change --volume-cond 추가
  (8개라고 쓰고 7개만 나열하고 있었음). 같은 이유로 Non-breaking 절의
  open-change 항목을 7개 → 3개로 정정
- by-stock-total --trade는 이미 click.Choice였지만 1/2를 잃었으므로
  Non-breaking 행에서 분리 → stock 소계 Breaking 9 / Non-breaking 22
- "raw 숫자코드는 계속 통과합니다" 단정 삭제. 종전 기본값 --vol-type 0,
  elw --right-type 005, by-stock-total --trade 1/2가 실제로 거부됨

합계 연쇄 수정: market 소계 115 → 116, 합계 146 → 147,
Breaking 125 / Non-breaking 22.

그 밖에:
- Fixed 절에 ka10021/22/23의 새 기본값("1"/"5") 명시
- 내부 용어 제거(트랜치/Tranche B/Task 7b/34b/제약 8)
- README 조사 오타, 숫자 코드 허용 범위 단정 완화,
  kt10000 예제의 생략부호를 실제 실행 가능한 JSON으로 교체

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
기존 값 고정 테스트는 샘플한 키가 형제 상수와 값이 같은 키뿐이라
상수를 통째로 형제와 합쳐도 전부 통과했다. ka10027의 세 상수
(stk_cnd/pric_cnd/trde_prica_cnd)를 형제 값으로 병합해도 1552개가
모두 통과하는 것을 확인했다 — 고정이 아무것도 고정하지 못했다.

병합 mutation 하네스로 207개 폐쇄 관계를 전수 검사해 16개가 무방비인
것을 확인하고, 전부 값이 갈리는 키로 고정했다(16 -> 1, 남은 하나는
MARKET_KOSPI_KOSDAQ -> MARKET_SEARCH로 dead 상수 제거로 해소).

브리프에 없던 4건도 함께 발견해 고정했다:
- VOLUME_SURGE_PRICE_TYPE -> VOLUME_RANK_PRICE_TYPE (4개 키 전부 갈림)
- MARKET_STATUS_KOSPI -> MARKET_ALL / -> CREDIT_MARKET
- EXCHANGE_ALL_ZERO -> EXCHANGE_ALL / -> ACCOUNT_EXCHANGE_NO_SOR

EXCHANGE_TWO -> EXCHANGE_ALL은 값이 아니라 키 집합이 위험한 관계라
(두 값은 동일) 키 집합 자체를 고정한다 — 병합 시 23개 사이트가
스펙에 없는 stex_tp="3"을 받아들이게 된다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
HumanChoice에 배선된 상수는 167개인데 레지스트리는 165개만 나열해
MARKET_ALL과 TRADER_ANALYSIS_PERIOD_5_120이 human 이름 왕복 검증을
전혀 받지 못하고 있었다.

TRADER_ANALYSIS_PERIOD_5_120 쪽은 단순 누락이 아니라 같은 파일 안의
두 주석이 서로 모순됐다 — 레지스트리 주석은 "더 이상 쓰이지 않음"이라
했지만 개수 주석은 그 전환을 세고 있었다. 실제로는 ka10043(stock.py)/
ka10042(market.py) 두 곳에 배선돼 있으므로 레지스트리 주석 쪽이 거짓
이었다. 주석을 사실에 맞게 고치고 항목을 추가했다.

레지스트리가 손 관리라 또 어긋날 수 있으므로, 배선 집합과 목록을 집합
비교하는 드리프트 가드를 추가한다(개수 비교는 한쪽이 늘고 다른 쪽이
빠지면 통과하므로 집합으로 본다). 항목을 지우고 실제로 실패하는 것을
확인했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
같은 결함 유형을 이미 두 번(2b58489, 12c8acb) 고쳤는데 세 건이 더
남아 있었다. 워크북(docs/미국 REST API 문서.xlsx)으로 재확인한 결과
셋 다 **값 대응**은 동일하지만 **문구**는 다르다 — 공유 자체는 계속
정당하고, 정밀도 주장만 거짓이었다:

- EXCLUDE_ENDED_ELW: 5개 시트가 동일하다고 했으나 실제 세 형태다
  (ka30001/ka30002, ka30004, ka30009/ka30010)
- ELW_RIGHT_TYPE_3DIGIT: ka30001/ka30004가 콜론 뒤 공백 유무로 다름
- CHART_ADJUSTED_PRICE: 6개 시트 전부 "0 or 1"이라 했으나 ka10081만
  수정주가 적용법 설명이 붙어 370자다

그 밖에:
- CHART_ADJUSTED_PRICE 커플링 범위 정정 — 국내 6개뿐 아니라
  usa06010~15에도 도달한다(stock chart day --exchange amex가
  upd_stkpc_tp를 usa06012로 전송). 값은 미국 스펙에서도 합법이라
  전송 바이트 결함은 없고, 주석이 영향 범위를 좁게 적고 있었다.
- MARKET_SEARCH 제거 — 참조 0곳. 배선된 적이 없어 값 고정 테스트를
  붙일 수 없으면서 살아 있는 mrkt_tp 코드북들과 키만 겹쳐 오용
  표면만 넓히고 있었다(4개 폐쇄 관계가 이 제거로 해소된다).
- AMT_QTY_TP_1_2 상호참조 13곳 -> 12곳 (정의부 표와 불일치했다)
- TRADER_ANALYSIS_PERIOD_5_120 "제거한다" 주석 정정 — 제거되지
  않았고 두 곳이 쓰고 있다
- dt 코드북 주석에서 내부 태스크 서술 제거, I2 예외 근거만 남김

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
이중 철자 옵션의 human/legacy 판별이 "둘 중 더 긴 쪽이 human"이라는
길이 휴리스틱이었고, 길이가 **같은** 경우만 막아 뒀다. legacy 쪽이 더
긴 쌍(예: --type vs --trde-upper-tp)이 생기면 legacy를 human으로 잘못
지목해 그 사이트에 정반대 선언 순서를 요구하게 되는데, 오늘은 그런
쌍이 없어 조용히 통과한다.

legacy 스펠링 5개를 _LEGACY_SPELLINGS에 명시적으로 열거하고 human을
"나머지 하나"로 정의한다. 등록되지 않은 새 쌍은 즉시 실패하며 등록을
요구한다. 실제로 --type/--trde-upper-tp 쌍을 주입해 실패하는 것을
확인했다.

ELW_BROKER_END_SKIP(제거된 이름)을 참조하던 docstring도 함께 정리했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@gejyn14
gejyn14 merged commit d4c0707 into main Jul 19, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant