reorg: 业务线重组 B1–B9(provider seam + 参数归属 + 预取 + 验收整改) - #10
Merged
Merged
Conversation
业务线重构计划书 §6 B1,行为不变。
- 新增 data_pipeline/providers/{base,_log,_registry,yfinance_provider,yf_snapshot}.py:
MarketDataProvider 协议 + canonical schema(CanonicalBar / OptionLeg /
OptionChainSnapshot)+ 名称注册表(MARKET_DATA_PROVIDER,默认 yfinance)
- yf_client.py 退化为兼容 re-export;downloader.py 只保留缺口检测与
raw_prices upsert,不再 import yfinance
- doc_guard 的 single-yf-exit / yfinance-throttle 作用域改为
data_pipeline/providers/
- 过 §8 决策闸 Q5:协议对照 futu 字段表 → iv 归一为小数、bid/ask 可空、
inTheMoney 不入 canonical(结论表记入 ADR 0011)
- 计划书 §0 台账 B1 → landed;实际形态与两处偏差记在 §8
验收:pytest -m "not network" --ignore=tests/e2e → 459 passed / 5 skipped;
doc_guard clean;arch_metrics --check ok(无需重置 baseline);audit_tags 不变;
生产代码 import yfinance 仅 providers/ 两处;routes/ 与 services/ 零改动。
…feature_bars) 业务线重构计划书 §6 B2;先过 §8 决策闸 Q4(name-only rename)。 - db.py:新增 canonical 三表,列集与旧名逐一相同(由同一列元组生成, 结构上杜绝漂移);旧名保留为 shadow 表一个 release,upsert_many 双向镜像 - 读写全部切到 canonical 名(downloader/cleaning/processing/repos/data_ops) - ingest 经 provider registry 取数:download_bars() 取代 _download_yf,返回 CANONICAL_BAR_COLUMNS,yfinance 列名映射只存在于 provider - scripts/migrate_canonical_tables.py:一次性、幂等、永不覆盖 canonical 的回填 - 测试:新增 tests/test_canonical_tables.py(列一致性 / 双向镜像 / 一次 pipeline 运行两族均有数据 / health 读 raw_bars / 迁移脚本回填且幂等); test_processing 改为 seed clean_bars、读 feature_bars - 文档:l0 schema、glossary(新增 feature_bars + 兼容期说明)、constraints §2、 .github 诊断与提示文档表名、ADR 0011 加 B2 修订(symbol 列延后) 验收:pytest -m "not network" --ignore=tests/e2e → 468 passed / 5 skipped; doc_guard clean;arch_metrics --check ok(无 god-file 回归);audit_tags 不变。
review 发现 --dry-run 仍会走 init_db() 建表,与「dry」语义矛盾:改为 先校验 DB 文件存在,只读地报告两族行数;建表与回填仅在非 dry-run 分支执行。 补 tests/test_canonical_tables.py::test_migration_dry_run_has_no_side_effects (断言 raw_bars 未被创建),并在计划书 §8 B2 记录。
…read/orchestrate) 业务线重构计划书 §6 B3;ADR 0011 的目标布局落地。 - data_pipeline/ 拆为六个单向阶段 + _state.py;data_ops/ 删除; yf_client.py 移到 providers/(仍是一 release 兼容 shim,但 services→providers 这条边现在可见) - 守卫升级为子层感知:doc_guard._layer_of / _imported_heads 与 arch_metrics.layer_of 把 data_pipeline/<stage>/ 解析为独立层; _ALLOWED_DEPS 补上 B1 欠下的 providers 层;sqlite-bypass 与 db-access 重新指向 store/db.py、store/repos.py - 断环:orchestrate/scheduler 改调 orchestrate.update.manual_update (原先经 DataService,会与 read→orchestrate 形成环) - tests/test_architecture_purity.py 新增 3 项契约:分层图与声明一致、 transform 不得 import providers、两份层表必须相等 - 文档:l0(新增 data_pipeline 分节 + 度量)、architecture_review §2/§3、 CODEBUDDY/CLAUDE、README、constraints/glossary/automation/ frontend_architecture、.github 指令/技能/agents/failure-registry 路径; tag_baseline 重生成(路径变了、计数仍为 16) 验收:pytest -m "not network" --ignore=tests/e2e → 472 passed / 5 skipped; pytest tests/e2e → 38 passed;ruff check/format clean;doc_guard clean; arch_metrics --check ok(layer 0 / cycles 0 / god 0 / dead 1,无需重置基线)。
业务线重构计划书 §6 B4;core/ 从此零 data_pipeline 依赖。
- 拆分:core/market/data_context.py 的取数部分移到
services/market/data_context_fetch.py::fetch_data_context;
core 只保留纯 DataContext + refrequency() + 纯装配
build_data_context(*, ticker, frequency, horizon, raw_data) + empty_data_context()
两个 allow=core-purity 标记删除
- 反转构造方:MarketAnalyzer(data_context) / CorrelationValidator(price_data=…)
接收注入的 context(沿用 2026-09 对 OptionsChainAnalyzer(snapshot=) 的做法);
CorrelationValidator 缺 price_data 时明确报错而不是偷偷取数;
新增 MarketAnalyzer.data_context 公开属性,statistical.py 不再触碰 _ctx
- 守卫收紧:core 层允许依赖从 {read, providers} 收紧为 {utils}
(doc_guard 与 arch_metrics 两份表同步);新增
test_core_has_zero_data_pipeline_imports,刻意不认 allow 标记
- 测试迁移:test_frontend_api 改用纯构造器(删掉三处闭包)、
test_nvda_analysis 新增 _analyzer() 助手(5 处)、test_chart_time_range 与
test_ticker_format_integration 同步(后者顺带去掉 __init__ monkeypatch 技巧)
- 文档:architecture_review §2(该行关闭、标记数 2→0)与 §3 层表、
§5 开放事项、CODEBUDDY/CLAUDE 的 core 规则、计划书台账/§8
验收:pytest -m "not network" --ignore=tests/e2e → 473 passed / 5 skipped;
pytest tests/e2e → 38 passed;ruff clean;doc_guard clean;
arch_metrics --check ok(无需重置基线);grep allow=core-purity 为空。
业务线重构计划书 §6 B5;决策闸 Q1 选 A(保留流式 + 模块参数随 /render 带)。 - orchestrate/readiness.py(新):KIND_DATASETS 模块→数据集映射、plan_datasets 求并集、check_and_kick(每 ticker 一次 DB-only 探测 + 守护线程 kick)、 status_for/hold_seconds_left/should_hold/is_backfill_running;守护线程 backfill kicker 从 read/_query.py 上移到此,POST 侧与分片侧共用一份 - services/market/readiness.py(新):services 侧一半——调用 plan/kick 后为 实时链模块在守护线程预热 options.preload(orchestrate 不能 import services) - FormService.extract_modules:接受重复或逗号分隔 token,默认全模块(B7 前 前端还不发这个字段),未知 token 丢弃而非报错 - job_cache.create_job(..., plan=) 存下 readiness 结果;POST / 组装后传给 job - dispatch 冷启动保持:plan 说 kicked + 无任何历史 + backfill 线程仍在跑时, 返回自刷新的 partials/fragments/readiness.html,而不是渲染空图; 受 HOLD_SECONDS 与线程存活双重约束 (review 抓到仅靠计时器会让“下载失败”白转 30s 盖住错误,加存活判断修复, 由 test_should_hold_stops_as_soon_as_the_backfill_is_gone 钉住) - 测试:新增 tests/test_readiness.py(18 例);后台 backfill 测试改从 readiness 导入 kicker - 文档:ADR 0012 补 Q1 决议与 B5 实现状态、frontend_architecture 流式段 (冷启动 + 模块 token 词汇表)、plan §8 Q1/台账/B5 注记、l0 度量、 CODEBUDDY/CLAUDE 流式段 验收:pytest -m "not network" --ignore=tests/e2e → 493 passed / 5 skipped; pytest tests/e2e → 38 passed;ruff clean;doc_guard clean; arch_metrics --check ok(无需重置基线);audit_tags 16 vs 16。
业务线重构计划书 §6 B6;ADR 0012 参数归属第一批。 - 新 templates/partials/parameters_bar.html + static/parametersBar.js:位于 header 与 .app-body 之间、position: sticky(top: var(--header-h)),只拥有 ticker 一个输入 + Run + 校验徽标;折叠状态按访客持久化 (localStorage['parametersBarCollapsed'],每次访问都 try/catch,禁用存储 时降级为「不记忆」),折叠后显示一行摘要 ▸ ^SPX;删除 tab_parameter.html 及其侧栏按钮 - Q3 = 独立 Portfolio 页签:templates/partials/tab_portfolio.html 承接持仓表与 Portfolio Analysis 结果面板;#positions-tbody 仍在每次加载的 DOM 中,既有 全局处理器(addPositionRow / runPortfolioAnalysis)无需改动 - 记录的临时偏差:B6 删掉 Parameters 页签时 B7 尚未落地,若把时间窗/仓位/ Config 桥一并撤掉会造成功能回退,故它们暂留在同一 <form> 内的可折叠 「Analysis settings」组(明确标注为 B7 的抽取源),POST 契约逐字未变 - Pages 镜像:build_pages_site 的断言与 showcase/parameter.html 指向 tab-portfolio,横幅链接改「去 Portfolio 页」;test_pages_build 的 ticker 输入断言改为与属性顺序无关(原断言被新增 class 属性打破) - 测试:新增 tests/unit/parametersBar.test.js(8 例)并纳入覆盖率清单; 5 个 e2e 去掉「先激活 Parameter 页签」(栏常驻),持仓级联测试改开 tab-portfolio,test_smoke 的页签清单 tab-parameter → tab-portfolio 验收:pytest -m "not network" --ignore=tests/e2e → 493 passed / 5 skipped; pytest tests/e2e → 38 passed;npx vitest run → 187 passed / 15 files; doc_guard clean;arch_metrics --check ok;audit_tags 16 vs 16。 无 axe 自动化工具,改用人工核对(<label for>、真 <button> + aria-expanded / aria-controls + sr-only 文案、摘要 aria-hidden)。
业务线重构计划书 §6 B7 的前半:提交契约的接收端。前端 toolbar/store 部分待续。
- FormService.MODULE_PARAM_KEYS + extract_module_params(module, args):按模块声明
白名单;from/to 经与 POST 相同的 parse_month_str 产出 start_time/end_time 与
parsed_*;frequency 限 D/W/ME/QE;side_bias 同时推导 target_bias;assessment
各值做类型收敛,非法值跳过(让 job 里的 POST 值继续兜底)
- dispatch.render_streaming_slice:{**job.form_data, **module_params, ticker} 合并,
直链/书签无参数时行为不变,toolbar 可覆盖自己的参数
- 不变量(tests/test_module_params.py 11 例):只读模块声明过的键,
?option_position=…&ticker=EVIL 无法向 slice 的 form_data 夹带键
- 计划书 §0 台账 B7 标为 🔨 in progress,§8 记录已落地部分与剩余清单
验收:pytest -m "not network" --ignore=tests/e2e → 504 passed / 5 skipped;
pytest tests/e2e → 38 passed;ruff clean;doc_guard clean;arch_metrics --check ok;
audit_tags 16 vs 16。
业务线重构计划书 §6 B7(§8 Q1 = manifest 的前端落地),替换 B6 的过渡组。 - state/paramsStore.js(工厂)+ market/assessment/optionFilter 三个参数组: 每组一个 localStorage 键;hydrate() 在解析期执行(HTMX 处理 load 触发器之前), 首次 hx-include 扇出即带上已存值;init() 返回 store 本身 - 四个模块 toolbar(Market Review/Statistical/Assessment/Option Chain), 占位元素 hx-include="#<toolbar>";static/moduleParams.js 负责 kind → 元素 + 所属 store 的映射,按 skeleton + htmx.process 的既有写法 只重跑受影响的模块;链过滤器是客户端直发,不触发 /render - 删除桥:FormManager.syncConfigToForm/loadConfig/saveConfig、四个隐藏字段与 option_position 赋值;bar 只保留 ticker + Run + 徽标,以及 POST 校验/预取 深度所需的 start_time/end_time 两个提交期镜像(由 marketParams 同步) - tab_config.html 清空为「设置已迁移」说明,外壳留待 B8 答 §8 Q2 - 测试:tests/unit/paramsStore.test.js 10 例 + 新脚本纳入覆盖率清单; e2e 新增 test_module_params.py(组内重跑且携带新值与时间窗、链过滤器 不触发 /render、刷新后值存活)并把 localstorage 恢复测试重写到 一组一键契约;test_frontend_api 的首页断言改按模块 toolbar - 三个真实 bug 由测试抓出并钉住:①同一字段绑定多个输入时全量回读会用 旧值覆盖新值;②init() 无返回值导致 init() 链式调用把已发布的全局 覆盖成 undefined;③input+change 双触发使两次重发竞态、htmx 抛 swapError(select 只认 change + rerun 跳过同 URL 在飞骨架) 验收:pytest -m "not network" --ignore=tests/e2e → 505 passed / 5 skipped; pytest tests/e2e → 41 passed;npx vitest run → 198 passed / 16 files; ruff clean;doc_guard clean;arch_metrics --check ok;audit_tags 16 vs 16。
业务线重构计划书 §6 B8——整次重构的最后一个批次。 - 删除 templates/partials/tab_config.html、侧栏按钮与 include; 「System」分区标签随之移除(最后一个分区是 Portfolio) - 计划书预期的 switchTab config 特例并不存在(static/ 下 grep tab-config 为空); 页签清单断言在三处:tests/test_pages_build.py、tests/e2e/test_smoke.py、 scripts/build_pages_site.py::build——后一处是本次遗漏后补的 - 风险利率仍硬编码在 static/sim/black_scholes.js 与 core/options/greeks 两处; 把它做成真正的全局设置是新特性而非清理,已记入 architecture_review.md §2 观察清单(不改一处不改另一处会使两套定价静默分叉) - 文档:计划书状态改为 LANDED、Q2 决议、B8 台账与 §8 注记、§9 引用路径、 frontend_architecture 页签表、l0 模板行、CODEBUDDY/CLAUDE 指针 验收:grep -rn tab_config(templates/ static/ tests/ site/)为空; pytest -m "not network" --ignore=tests/e2e → 505 passed / 5 skipped; pytest tests/e2e → 40 passed;npx vitest run → 198 passed / 16 files; doc_guard clean;arch_metrics --check ok;audit_tags 16 vs 16。
机械校验全绿(pytest 505/5skip、vitest 198、ruff/doc_guard/arch_metrics clean),8 批次退出标准与 §2 债务清零逐条核对通过。 计划书 §10 记录评审结论与 F1–F7 整改项,新增 B9 台账行: - F1(高,已复现):job_cache.compute_or_get 按 (ticker,kind) 记忆, 未纳入 module_params;提交后 90s 内改模块参数会重发 /render 但返回 旧分片。B7 头号行为实际失效。 - F2/F3:参数栏「收起」在 B7 掏空 body 后形同虚设 + aria-controls 悬空 + FA 图标未加载 - F4:readiness 用 clean_bars 探针门控 feature_bars 覆盖 - F5:option_data 死路径 / GET 分支冗余模板变量 / header badge 恒显默认值 - F6:providers/* 文档字符串陈旧引用(downloader.py→ingest/ohlcv.py 等) + 台账 planning 行状态 —— 本提交已修 - F7:yf_client shim 无移除触发器;market_review_prices L5 仍在缝外 architecture_review.md §2 watch list 补两行(shim 移除触发器、L5)。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
验收评审(§10)发现的阻塞项与「可收起」诉求: F1(correctness,已复现):job_cache.compute_or_get 按 (ticker,kind) 记忆,dispatch 的 module_params 来自 query string 却不在 key 里, 提交后 90s 内改 frequency/时间轴会重发 /render 但返回旧分片。 - compute_or_get 增加 keyword-only variant,key 改 (ticker,kind,variant) - dispatch._params_variant(module_params) 生成有序摘要 - 直链/旧调用方不传 variant → "" → 旧行为,无副作用 - 新测试:test_variant_computes_independently、 test_a_param_change_recomputes_within_the_same_job(原 e2e 只查 URL) F2(UX/a11y):B7 掏空 bar body 后「收起」形同虚设、aria-controls 悬空 - label+输入+徽章包进 <div class="parameters-bar-fields" id="parameters-bar-body"> - 收起隐藏该 div + .ticker-validation,只留 toggle + ▸ ^SPX + Run - 删除失效的 .parameters-bar-body / -group-title CSS - 新 vitest 断言 aria-controls 目标存在且含 #ticker F3(UX):收起图标用未加载的 Font Awesome - 改内联 SVG chevron,按 [data-collapsed] 旋转 -90° - parametersBar.js 去掉 icon class 交换,改 title 文案 docs:计划书 §10 表加 Status 列、§8 补 B9 batch note、台账 B9 行; frontend_architecture.md 参数栏段落更新到 B7/B9 现状。 pytest exit 0;vitest 199/16;e2e(bar 相关 20)pass; ruff/doc_guard/arch_metrics/audit_tags clean。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
F4(readiness feature_bars 缺口): - backfill.py: _range_covers 抽公共 3 天容差;_feature_bars_behind 探针(frequency='D',与 clean_bars 1:1) - needs_backfill: clean 覆盖但 feature 滞后时返回 True - _ensure_range_impl: clean 已覆盖的短路分支,feature 滞后则跑 process_frequencies(不下载)再 memo - read/_query.py::get_processed: 加 needs_backfill → kick + 宽限等待 自愈(同 get_cleaned_daily);memo 守卫也拒绝 partial read - readiness.py docstring INVARIANT 更新 - 测试 TestFeatureBarsHeal(clean-only 探针为 True;ensure_range 零下载重建 feature) F5-b/c(B7 遗留陈旧 UI): - routes/core.py GET 分支删除 B7 后无人读的 frequency/risk_threshold/ rolling_window/side_bias 模板变量与 import - index.html header badge 去掉 badge-meta(恒显 "Monthly, Neutral"),只留 ticker - market_review.html meta-bar 删掉 3 个显示常量的 chip,保留 ticker + 区间 - 4 处 fragment 空状态 + tab_simulation 占位符不再指向已删的 Parameter 页签 - form.py: option_data 加 DORMANT 注释(见 F5-a) F5-a 不是死代码,是 B7 回归(assessment.py 仍读 option_data 做 projection-vs-positions 叠加图 + sizing max-loss,B7 后永久断供、 静默降级)。retire vs re-feed 待定,已进 §2 watch list + 计划书 §10。 计划书 §10 补 B9 二轮 note + watch-list 权衡表;§2 watch list 加 F5-a 行。 full pytest exit 0;full e2e exit 0;ruff/doc_guard/arch_metrics clean。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
F5-a(按 owner 决定 retire,非 re-feed): B7 把持仓移到 Portfolio 页签、POST / 不再带 option_position, Assessment 的 projection-vs-positions 叠加图与 sizing max-loss 永久断供。删除: - assessment.py 的 option_data 两处分支;sizing 改 debit-only(加 WHY) - FormService.parse_option_data + option_data key(json import 一并去) - MarketAnalyzer.analyze_options / MarketChartAssembly.analyze_options - core/market/option_pnl.py + core/market/charts/option_pnl.py(整文件) - assessment.html 的 plot_url 卡片 持仓 P&L 的家现在是 Portfolio 页签。 summary.py 死代码(观察清单,捎带清理): generate_summary_analysis fan-in 0;summary_data 全仓无处设置。删除: - services/market/analysis/summary.py - templates/partials/tab_summary.html + index.html 里的 include / 侧栏按钮 / correlation heatmap JS 块 - routes/core.py 的 summary_pending - market_review_chart.js 的 renderCorrelationHeatmap / corrToColor(孤儿) - arch_baseline.json dead_code_candidates 1 → 0 - test_smoke.py TAB_IDS 收到 10 个 docs: 计划书 §0/§10 + 台账 B9 行标 landed;architecture_review.md §2/§4、 l0_architecture.md §2 更新;CLAUDE.md/CODEBUDDY.md/copilot-instructions 的 reorg 指针改为「B1–B9 landed,仅剩 §10 延期项」。 full pytest exit 0;full e2e exit 0;vitest 199/16;ruff/doc_guard/ arch_metrics(dead 0)/audit_tags clean。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
承接已合并的 #7(计划书 + ADR)和 #8(ADR Accepted + 脚手架)。本 PR 是 B1–B9 的实现,共 15 个提交。
批次(ADR 0011 / 0012,均 Accepted)
data_pipeline/providers/(唯一import yfinance)raw_bars/clean_bars/feature_bars(旧名保留一个 release 作影子表 + 迁移脚本)data_pipeline/重排为六阶段providers/store/ingest/transform/read/orchestrate+_state.py;子包级 doc_guard/arch_metricscore/对data_pipeline零 import,取数下沉到services/market/data_context_fetch.pyreadiness.plan_datasets规划 + 守护线程预取(不引入队列);冷启动自刷新 hold 片段positions移到独立 Portfolio 页签(§8 Q3)/render按模块 query args 取参 + 白名单 INVARIANT;state/*ParamsState.js;删除隐藏字段桥variantkey,已复现的功能阻塞)、F2(参数栏收起真实生效 + aria-controls)、F3(内联 SVG 图标)、F4(feature_bars覆盖自愈)、F5(a=退役 Assessment 期权叠加图 per owner;b/c=清理 B7 遗留陈旧 UI)、F6(文档陈旧引用);顺带删除summary.py死代码§2 架构债务
single-yf-exit/db-access/core-purity三类doc-guard: allow=标记全部清零;dead_code_candidates1 → 0。验证
pytest -m "not network" --ignore=tests/e2e→ exit 0pytest tests/e2e→ exit 0npx vitest run→ 199 / 16 filesruff check+ruff format --checkclean(344 files)scripts/doc_guard.pyclean;scripts/arch_metrics.py --checkok(layer 0 / cycles 0 / god 0 / dead 0);audit_tags16/16仍延期(计划书 §10)
无风险利率两处硬编码 → 真全局设置;
market_review_prices(L5)→ 并入 provider seam(B10);ADR 0011symbol列(等第二个数据商)。详见
docs/plans/business_line_reorg.md(§0 台账 / §8 批次实况 / §10 验收评审)。🤖 Generated with Claude Code