跳轉到

#1746:已確認前低/前高支撐壓力

Phase A 將 technical ta_support_resistance_risk 的支撐與壓力語意切換為完整歷史中日期最近的已確認前低/前高。Pivot 必須嚴格高於或低於左右各 5 根 K 線;同價平台不成立,右側 5 根完成前不可使用,也沒有到期日。

計算 context 使用還原價(adjusted)OHLC。signal state、分數、event 與 story 均先依 adjusted 價格判定,再將 support signal 的價格 evidence 投影回快照的顯示尺度;這避免顯示換算的浮點誤差改變 strict 跌破或恰好 3% 的邊界。缺少、非有限、非正值或日期不早於 trading_date 的 level/pivot 會 fail closed。

support_break 僅在 close < major_support 時成立;相等不成立。resistance_nearby 是收盤在有效 major_resistance 下方 3% 內,且有效支撐未被跌破;support_nearby 是收盤在有效 major_support 上方 3% 內。兩者重疊時壓力風險優先。

20/60 日低點與其距離欄位仍保留於資料與 signal readiness,以維持既有 payload 相容性;它們不是 swing support,也不決定 Phase A 的 state、score 或三個事件。20/60 日 swing-high 指標與籌碼支撐壓力維持各自既有語意。

Phase A 已將 ta_support_resistance_risk.index_evidence 的 structure_stop 與 invalidation_level 切換為 adjusted confirmed swing support,再投影到顯示價格尺度。只有前高、尚未確認前低時,兩者明確缺值(value: null/present: false),不沿用 producer 的舊 ATR fallback;stop_risk_high 的必要停損 evidence 因此不齊全。獨立的 atr_stop_distance 診斷值仍保留。

持久化 snapshot 的 major_support/major_resistance producer、structure_stop/invalidation_level 與 action-advice 停損欄位於 Phase B 切換。僅部署 Phase A 會有 signal evidence 與尚未切換的持久化欄位語意差異,完整 rollout 必須包含 B/C、producer 回填及 SES replay。歷史作業若限定不改 cross_domain,使用下方 technical-only 路徑。

Phase B 將 major_support/major_resistance producer 與 structure_stop/invalidation_level 接上相同 pivot。raw twins 在 raw OHLC 獨立確認;停損等於 adjusted 前低,缺值時保留 NULL,不以 ATR 推算替代。action advice 沿用 strict close < stop 及既有 config gate;drawdown 停損分支不變。批次歷史使用預算好的 causal series,避免對每個日期重算完整歷史。

Technical cache rollout

Technical 與 cross-domain compact latest-cache rule version 都包含 :swing-support-v1。相同日期及 source timestamp 的舊 cross-domain compact cache 會失效並重建;日史 ledger/coverage 維持全域 ses-storage-v7,需另行重播歷史。只修 technical 支撐歷史時使用下方明確的 technical_support_history policy。

Technical-only 歷史修復

預設 --domain technical --signal-code ta_support_resistance_risk 會經 forbidden refs 展開 value_recovery_setup 並寫入 cross_domain。#1746 限定使用 --replay-policy technical_support_history:resolver 保留 5 個 support event、15 支直接 technical consumer 及 12 支 conflict peer,合計 27 支 story;cross consumer 記錄於 excluded_cross_domain_story_codes,所有 cross writer/evidence 範圍皆為空。計算入口在評估候選前只選這 27 支 story,正常重播不計算全套 technical story。預設 targeted、其他 domain 與全域 force replay 行為保留。

Snapshot 的 IS DISTINCT FROM 閘只在 parent 值或支撐警告改變時 bump updated_at。只有 named target 改變不會 bump;SES 又會從 OHLC context 計算前低/前高,所以相同 vintage 的 coverage 仍可能需要重建。只用非 current coverage 續跑不能證明完整性。

先量測 producer 的 S/B/no-B 集合,以及 coverage 的 current/mismatch/no_cov/invalid-state。漏網計算只對 no-B 精確鍵逐日執行 selected dry-run;同一天使用 --ticker 傳入該日的完整 no-B 外部 ticker 名冊,須由同一唯讀 snapshot 的 stock_code/listing_type 轉換並驗證一一對應。報告路徑必須是新檔,不能續接失敗批次。一般完整 selected dry-run 亦可使用,但本作業不再先算全範圍 technical story。

python scripts/backfill_support_dual_history.py \
  --date-from "$FROM" --date-to "$TO" --dry-run \
  --change-key-report /repair_evidence/snapshot-keys.jsonl

python -m scripts.backfill_ses_story_daily \
  --date-from "$DAY" --date-to "$DAY" --domain technical \
  --signal-code ta_support_resistance_risk --replay-policy technical_support_history \
  --ticker "$EXACT_NO_B_TICKER" \
  --dry-run --audit-full-diff --audit-key-report /repair_evidence/ses-keys.jsonl \
  --require-v2-inputs --fail-on-day-errors

定義 S 為 snapshot 鍵、C 為現行 replay 會跳過的 current coverage 鍵、B 為 producer 真正會 bump parent vintage 的鍵、D 為選定 5 event/27 story/support signal state 任一有差異的鍵。真正漏網集合為 (C−B)∩D,可在 no-B 報告中取 current 且 changed 的精確鍵;不得用 C 的總數代替。Audit 輸出 current/noncurrent × changed/unchanged 及 selected by-code replacement 數。同 code 分數改變會同時計入 deleted/inserted;只有 state 改變不虛增 event/story diff。正式 writer 對 S 每鍵評估 selected 範圍;沒有目前或舊 direct activity 的鍵仍檢查 selected state/vintage,保留其 ledger。

寫入前由已核對的五份 gzip COPY 備份串流建立磁碟 day/ticker receipt 分片,只載入單日或單檔。分片工作目錄建立於 canonical checkpoint 同一磁碟目錄,並記錄於 durable preflight;不得落在容器的有限暫存空間。第一輪 live 比對除了主鍵與欄位,還逐串流核對五份備份的完整列 hash;同鍵數值、timestamp 或 metadata 變動亦拒絕。U_invalid(缺 coverage、錯 rule、NULL/壞 JSON、未知 vintage 或無 source/coverage 的孤立 ledger)列出精確鍵並阻止任何 snapshot mutation。對備份中原已 mismatch 的 M_vintage,只針對那些鍵以全引擎唯讀檢查未選 event/story/known state parity;任何差異皆拒絕,不可用全鍵 stamp 假裝 current。通過後將 dependency fence、M 集合與結果的 SHA 綁入 checkpoint。Producer readback 驗證 allowlist 外全部 snapshot 欄位,再保存包括 no-B 的 incoming vintage;SES guard 核對同一 operation/source/scope/backup/preflight 與原 coverage,才准更新 selected JSON slice 及 source_updated_at。rule_version、created_at、listing_type、plb、未知 JSON keys 與未選 x/be 均保留;同 vintage JSON 競態會 rollback。

正式作業將起始 DB now()、MAX OHLC 日期、W=MAX−7 個日曆日、FROM、TO≤W−1、固定 source/image SHA 與 backup manifest SHA 綁在 operation。2018-01-02 是 technical event 的最早日期;不可套用 targeted 的 financial/cross floor 截短技術歷史。續跑沿用 frozen 範圍,不重新計算 W。

Apply 前須先取得 SES 寫入程序的維護互斥:financial、chip、daily/lagging/correction writer 都不能與本作業並行。既有 story-strategy source advisory lock 是 shared,不能當作操作 lease。外層 watchdog 與維護操作另依核准 payload 執行;runner 不會自行停止或重新啟動共享容器。

Runner 另以 canonical checkpoint 路徑旁的持久 .lock sidecar 取得 non-blocking OS flock,從讀取 checkpoint/verification log 前一路持有到最後 verification record fsync 與 async context 清理完成;sidecar 不刪除,是否鎖定以 kernel lock 為準。每個 operation 必須固定一組唯一 checkpoint 與 verification-log 路徑,不得讓不同 checkpoint 共用同一 verification log。此鎖只排除解析到同一 checkpoint 的本機 runner process,不是 DB lock、不同 operation 的全域鎖,也不能取代上述外部維護互斥。

逐日 protected gate 使用開跑時 source 與 protected 全表串流取得的固定代碼名冊;orphan coverage gate 再聯集 technical coverage 備份中的完整代碼。開跑後新增的 protected-domain 代碼由最後的全表 digest 查出;新增 technical coverage 代碼須由維護互斥防止,並在最終 T3 以不套固定名冊的全範圍 orphan coverage SQL 查核。固定名冊不能取代維護互斥。執行命令須使用已核對 source SHA 的唯讀封存原始碼與固定 image digest;runner 的身分參數用於比對 manifest/checkpoint,不會自行驗證容器 image 或 Git checkout。

備份包含 snapshot 完整原列、name='to_major_resistance' target 原列與 technical coverage/event/story;COPY text gzip 的實際列數須等於同一 consistent snapshot 的 live count,並封存 SHA-256、columns、predicate 與 key fingerprint。同步封存 cross/chip/financial 的完整 row digest。Manifest 的實際 schema 與驗證方式見 scripts/run_technical_history_repair.py;缺檔、範圍或 hash 不符均阻止第一筆寫入。

核准寫入後使用 durable runner:

python -m scripts.run_technical_history_repair --mode live-apply \
  --operation-id "$OPERATION_ID" --source-sha "$SOURCE_SHA" --image-sha "$IMAGE_SHA" \
  --date-from "$FROM" --date-to "$TO" --watermark "$W" \
  --backup-manifest /repair_evidence/backup-manifest.json \
  --backup-manifest-sha256 "$BACKUP_MANIFEST_SHA256" \
  --checkpoint /repair_evidence/checkpoint.json \
  --verification-log /repair_evidence/verification.jsonl

Runner 在第一筆 mutation 前把完整範圍切成連續、不重疊的曆月區間,逐月以 fresh REPEATABLE READ READ ONLY transaction 執行 technical V2 readiness;每份 report 的範圍與 technical-only evidence contract 都須一致,technical rows 加總須等於 sealed source key count。完整有序 reports 與 canonical SHA-256 先 fsync 到 verification log,成功後才允許 writer。這些跨 transaction 檢查依賴整段作業期間的外部 maintenance writer exclusion,不能單獨證明多月輸入來自同一 DB snapshot。snapshot 每檔、SES 每日由既有 writer 提交,再讀回及核對 protected ledger,最後以 fsync+atomic rename 寫 checkpoint。Snapshot producer 在其唯一 commit 前,先將完整 snapshot/named-target postimage、incoming vintage 與 operation pins fsync 到收據;封存失敗即 rollback。Commit 後 marker 前中斷時,resume 只接受完整 sealed old image(重新執行)或精確 prepared postimage(讀回並跳過 producer);混合、任意較新或未封存資料拒絕。SES 未標記的日批依同一收據重播;已完成單位仍須讀回驗證,不能單憑 current coverage 跳過。錯版本、錯範圍、壞 marker 或讀回差異均停止。Source-state 由正式 dirty marker 更新,不執行策略統計 refresh。

Checkpoint schema 2 綁定固定 profile 與 canonical scope hash,舊全 technical checkpoint 不相容。續跑仍讀回已完成單位,但 SES 計算只用 selected 27 支;commit 後 marker 前重跑接受原封存 baseline 或本 operation 的精確已完成輸出,其他變動拒絕。舊日誌只證明當時通過。只有驗證日誌最後一行未以換行結束、且 JSON 殘缺時,runner 才先封存原始 bytes 與 SHA-256,再截去該尾段。完整行損壞、有效 JSON 的身分或格式錯誤仍會阻止續跑;可解析且正確的尾行只補上換行。

驗收須另存獨立 strict ±5 raw/adjusted oracle、2024-09-30 等抽樣日的逐檔 breakdown、完整 coverage mismatch/no_cov/NULL state、未選 technical 三表 receipt hash、protected 三表全域 counts/hash 及 created_at≥T1 為零的證據。API before/after 使用 /api/signals/stock/{ticker}?qryDate=YYYY-MM-DD,並分開記錄即時計算 SES 與持久化 ledger。完整三表備份不等於完整三表寫入或回滾核可;回滾只還原 snapshot allowlist、named target、5 event/27 story、selected coverage JSON slice 與其 vintage,保留未選及後續無關更新,不回退全域 sequence 或 source-state revision。正式回滾 payload 另交使用者核可。

Runner 在掃描 sealed backup artifact 或 live DB 前,先計算官方交易日及完整曆月分區;任何分區沒有官方交易日即以該分區日期與原因拒絕。同一份分區序列供 readiness 逐月驗證,空分區仍為 hard-fail;完整日曆範圍及 public verifier 的空資料檢查保留。

Targeted replay(包含 cross_domain)

此變更的 selector 是 technical / ta_support_resistance_risk。replay scope resolver 會展開該 signal 的 event、受影響 technical story consumer、其 conflict closure,以及可觀察此 signal 的 cross-domain story consumer;不要以手寫事件或 story 名單取代 resolver。

以下是同時核准 technical 與 cross-domain 修改時的操作路徑。Technical-only 作業不得使用。全域 non-targeted CLI 沒有 --apply 開關;移除 --dry-run 即為寫入,不可照抄 targeted 的 --apply。

先建立寫入前基線,再執行 dry-run、apply,最後驗證相同 selector。日期須替換為實際核准範圍;以下命令不預設任何筆數或通過結果。

python scripts/verify_v2_sesi_replay.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --mode targeted-baseline \
  --domain technical --signal-code ta_support_resistance_risk \
  --report-json /tmp/swing-support-before.json

python scripts/backfill_ses_story_daily.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --domain technical \
  --signal-code ta_support_resistance_risk --dry-run --require-v2-inputs --fail-on-day-errors

python scripts/backfill_ses_story_daily.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --domain technical \
  --signal-code ta_support_resistance_risk --apply --require-v2-inputs --fail-on-day-errors

python scripts/verify_v2_sesi_replay.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --mode targeted \
  --domain technical --signal-code ta_support_resistance_risk \
  --baseline-json /tmp/swing-support-before.json \
  --report-json /tmp/swing-support-after.json

2026-09-24 完整名冊驗證

來源為 2026-09-28 唯讀擷取的實際資料:2,619 個主檔代碼,2,615 個有 OHLC,共 4,344,852 根(2018-01-02 至 2026-09-24)。缺少 OHLC 的四碼為 01111S、01112S、01113S、01114S。當日正式 snapshot 入口取得 technical 2,356、chip 2,404、financial 2,404 筆;cross-domain 交集 2,356 筆。

相同輸入以基準 6141a32e 與 Phase A 程式重播完整 signal → event → story。raw/adjusted 各自的前低及前高另用獨立 11 根視窗 oracle 逐列核對,零差異。OHLC 擷取檔 SHA-256:16824ebcafc13ba6fe779a67a7b165f12df96a0c210d7aa74c32296b43efaacd。

Event 修正前 修正後 新增 移除
support_break 1181 275 225 1131
support_nearby 556 506 198 248
resistance_nearby 1181 492 56 745
swing_low_breakdown 275 275 0 0
stop_risk_high 1178 767 281 692

support_break 修正前的 1,181 筆中,50 筆是真正跌破、1,131 筆是假陽性;新版另找回 225 筆跌破。stop_risk_high 沿用既有規則,但母訊號的 state/score 及 confirmed invalidation evidence 改變,因此也須納入 targeted replay。

以下涵蓋全部 13 個 support_break consumer;★ 同時使用 support_nearby(共 3 個)。筆數包含被 conflict suppression 標記但仍保留於 ledger 的 story;最後一欄比較完整 payload,而非只比較筆數。

Story 前 後 新增 移除 保留但 payload 改變
legacy_bullish_reversal_confirmation 0 0 0 0 0
bullish_reversal 3 2 0 1 0
trend_breakdown 2 2 0 0 2
ma_breakdown 157 157 0 0 122
channel_breakdown_story 50 50 0 0 50
distribution_risk 40 40 0 0 31
limit_down_streak_pressure 1 1 0 0 1
limit_down_streak_rebound_watch ★ 1 0 0 1 0
climax_capitulation_bottom ★ 7 11 7 3 4
volume_exhaustion_bottom ★ 93 86 26 33 60
value_recovery_setup 0 0 0 0 0
ma_year_breakdown_story 23 23 0 0 18
ma_quarter_breakdown_story 64 64 0 0 41

已完成的三個 story 名冊保持一致:high_volume_breakdown 25 → 25(25 筆分數/payload 改變)、new_high_breakout 391 → 391、volume_breakout_confirmation 81 → 81(後兩者 payload 也完全相同)。額外經 conflict/分數傳遞影響的 payload:volatility_expansion_story 6 筆、vp_organic_suffocation_bottom_story 6 筆、vp_high_volume_bottom_reversal_story 1 筆,三者名冊均不變。

只有 ta_support_resistance_risk 及其五個 event 改變;其他 technical signal/event、所有 chip/financial 及當日 cross-domain payload 完全一致。本次是隔離重播與測試證據,未寫入共享資料庫。

Phase B producer / 回填驗證

相同完整 OHLC 對 2026-09-24 的全部 2,356 筆 technical snapshot 檢查 forward producer 與 backfill;adjusted 支撐/壓力也與獨立 oracle 的 context 相同。全部通過。改變的筆數:adjusted 支撐及兩個停損欄位各 2,129、adjusted 壓力 2,266、raw 支撐與停損各 2,131、raw 壓力 2,265。所有既有 20/60 日低點與距離保持相同。既有 backfill 也會以已儲存 ATR 重算四位小數的 ATR distance;17 筆因此有最多 0.0001 的捨入差異,不是新增 ATR 停損。

回填也同步同日期的 to_major_resistance 目標:已確認壓力存在時保留恰好一筆,沒有壓力時移除舊目標;其他名稱的目標不變。只有目標過期也會納入 dry-run,無變更時保留原有 ID。

回填於同一交易同步 message_source_json 的 NEAR_SUPPORT/NEAR_RESISTANCE。它與日常 producer 共用既有 2%(含邊界)規則,並依原快照是否含 NEAR_ALL_TIME_HIGH 及原 prev_ath_price 套用嚴格小於 0.5% 的壓力警告去重;不重新解讀當次 ATH 設定。其他訊息、原 sort_order、附加中繼資料、bias 與 confidence 均保留,新增警告接在原順序後;不合法或互相矛盾的非空 JSON 會讓該檔失敗,不會丟棄資料後重建。

只有警告過期也會計入 dry-run 並在修復時更新 updated_at,讓下游來源版本檢查生效。JSON 寫入比對原始內容,遇到並行更新不覆蓋新訊息;提交前讀回完整 JSON、數值及目標,任何不一致均回滾該檔。SQL NULL 與 JSON null 都可補齊;缺 adjusted bar 則連同警告維持原值。重跑相同輸入不改資料或時間戳。

回填以每檔股票為一個 transaction:同一交易內讀回實際寫入值、日期或值不符就 rollback,其他股票仍可續跑;任何失敗都讓 CLI 回傳非零。重跑相同輸入不更新資料或 updated_at。缺 raw 當日 bar 的 snapshot 不寫入;raw 存在但 adjusted OHLC 不完整時只更新 raw,保留 adjusted 欄位並待補齊來源後重跑。完整 adjusted 當日資料沒有已確認 pivot 時,停損明確寫成 NULL。

先在核准的資料庫與日期範圍完成 producer 回填,再執行上方 targeted SES replay。以下命令須在 backend 目錄執行,DATABASE_URL 由操作環境提供;可加 --ticker 限定一檔,日期只限制寫入範圍,pivot 一律讀取該日期前完整歷史。

python scripts/backfill_support_dual_history.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --dry-run

# 僅在資料庫/範圍已核准後執行;這支既有 CLI 未加 --dry-run 即會寫入。
python scripts/backfill_support_dual_history.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD

# 再次 dry-run 應回報零筆改變;非零 exit code 須先處理失敗股票。
python scripts/backfill_support_dual_history.py \
  --date-from YYYY-MM-DD --date-to YYYY-MM-DD --dry-run

high_20 保留前一交易日 adjusted donchian_high_20 的因果欄位語意,不能引用已改義的 major_resistance。Category E 是保留但不評分的相容性槽位;舊組合仍可解析 E,新產生的 solo、ablation、forward selection、fallback、tuning 與報告會排除 E。20 日突破只由 Category M 的 breakout_20d 加分與產生 reason,避免兩個類別對同一門檻重複加分。

2026-09-28 review 回歸:9/24 全名冊 2,356 筆中,E 的 78 次條件命中全部與 M 重疊;停用 E 後,切換舊 E 設定對分數與 bias 的影響為零。支撐警告獨立 oracle 對照零差異,787 筆需更新;NEAR_SUPPORT 971 → 798、NEAR_RESISTANCE 819 → 659,其他訊息/中繼資料零變更、第二次執行零變更。整合單元 15,661 passed/151 existing skips;新增與既有回填/快取 PostgreSQL 測試 22 passed,涵蓋並行寫入、JSON 讀回破壞、整檔 rollback 與部分成功後重試。還原舊 scorer 出現兩個預期失敗、還原舊 backfill 的警告單獨修復回歸出現一個預期失敗。

Phase C 回測讀取與價格基準

回測 loader 保留 snapshot 的 raw OHLC,並固定提供 indicator_close 與 high_20,包括指定精簡欄位的讀取。indicator_close 只取同股票、同交易日的 stock_ohlc.adj_close;來源為 NULL 時不回退 raw close。high_20 取該股票前一個 OHLC 交易日 snapshot 的 adjusted donchian_high_20,不再別名引用 major_resistance。前一交易日沒有 snapshot 時維持 NULL,不跳過缺口抓更早的 snapshot,也不使用包含當日價格的通道。

支撐壓力、布林通道與一目均衡表的直接價格比較使用 adjusted indicator_close。明確為 NULL、非數字或非有限值時,不觸發須有效價格的條件;只有完全沒有該欄位的舊直接呼叫保留既有 close 相容性。保留的 high_20 不再直接評分。K 線形狀與 raw 價格欄位的用途保持相同。既有回測 proximity 門檻仍為 2%,不是 Phase A SES 事件的 3% 規則。

API consumer 已逐一檢查 api/routes/signals.py、stock_signals.py、agent.py 與 action_advice.py。前兩者沿既有欄位映射傳遞新版支撐/壓力;agent 的壓力距離改用同日 adjusted close,無有效值時回傳 NULL。既有 support_20d/60d 欄位僅保留區間低點相容性,未變成 confirmed swing。持股建議的停損、回檔與壓力空間使用與原價估值相同 OHLC 列的 adjusted close;原始價顯示與損益計算保持相同。最新還原價缺少或無效時回傳 hold 與 missing_data,不回退原價或舊值。

Phase 1 parquet 直接保留 loader 的 indicator_close,移除第二次合併造成 _x/_y 欄位的風險。部署及核准回填後,須重新產生分析 parquet/衍生回測結果,才能使用新版支撐壓力與讀取規則。

最終完整單元測試 15,615 項通過、151 項依既有設定略過;loader 與 advice 的實際 PostgreSQL 整合測試 6 項全數通過,涵蓋 raw/adjusted 分離、明確 NULL、前一交易日 snapshot 缺口、provenance join、無效尾端行情與股票別名。四個實際 FastAPI router 的 response 結構與欄位型別維持相容,OpenAPI description 與 API 文件同 commit 更新;databook 已在 Phase B 同步。

實際 2026-09-24 全名冊 loader 唯讀驗證:2,356 筆/2,356 股票,重複列、價格基準與前日高點 donor 差異均為零;8 筆 high_20 為 donor 本身尚無有效 Donchian 的正確 NULL。讀取含 14 日 warmup 花費 4.863 秒。各階段突變測試也確認還原舊生產邏輯會讓回歸測試失敗。

2018–2026 停損比較

完整報告與重現方式包含實際舊規則、前日已知低點敏感度、已確認前低,以及加入成交限制的敏感度。新版修正停損語意,但本次獨立停損策略的收益低於不觸發的舊規則,不宣稱績效提升。

Issue checklist

本表記錄程式與隔離驗證狀態,不表示共享資料已補算。

# Scope Status
1 producer major_support / major_resistance Done in Phase B
2 structure_stop / invalidation_level Done in Phase B
3 support_nearby / resistance_nearby / support_break Done in Phase A
4 signal state and score Done in Phase A
5 13 support_break story consumers Verified in Phase A
6 3 support_nearby story consumers Verified in Phase A
7 action-advice stop loss and API decision basis Done in Phase B/C
8 backtest loader high_20 alias and adjusted consumers Done in Phase C
9 API routes, documentation, OpenAPI, databook Done in Phase B/C