Compare commits

...
Author SHA1 Message Date
leefer 2ef31f6115 refactor: enforce canonical backend imports 2026-08-02 00:10:43 +08:00
8 changed files with 83 additions and 13 deletions
+5 -1
View File
@@ -55,7 +55,11 @@ background scheduler
Root modules such as `screener.py`, `tushare_client.py`, and `mentor_agent.py` are compatibility Root modules such as `screener.py`, `tushare_client.py`, and `mentor_agent.py` are compatibility
aliases to canonical modules. They contain no second implementation and remain only because aliases to canonical modules. They contain no second implementation and remain only because
the original public import surface is part of the preservation contract. the original public import surface is part of the preservation contract. Canonical backend
modules must import other canonical modules directly rather than routing through these aliases.
The remaining `api_access` import in `backend/application.py` and preserved lazy
`sentiment_engine` import in the screener repository are registered transition boundaries;
the root `database.py` remains the documented schema/composition anchor.
## Non-negotiable maintenance rules ## Non-negotiable maintenance rules
+1 -1
View File
@@ -9,10 +9,10 @@ from backend.database.repositories import RepositoryBundle, build_repository_bun
from backend.features.alerts import AlertService from backend.features.alerts import AlertService
from backend.features.mentor.agent import MentorSkillRegistry from backend.features.mentor.agent import MentorSkillRegistry
from backend.features.review import TradeJournalService from backend.features.review import TradeJournalService
from backend.features.screener.engine import ScreenerEngine
from backend.features.screener.tracking import StrategyTrackingService from backend.features.screener.tracking import StrategyTrackingService
from backend.jobs import InProcessJobRunner, JobRegistry, SQLiteJobRunRepository from backend.jobs import InProcessJobRunner, JobRegistry, SQLiteJobRunRepository
from database import ReviewDatabase from database import ReviewDatabase
from screener import ScreenerEngine
from backend.data.providers.ifind_client import IfindHttpClient from backend.data.providers.ifind_client import IfindHttpClient
from backend.data.realtime import WebRealtimeAggregator from backend.data.realtime import WebRealtimeAggregator
from backend.features.market.charts import MarketChartClient from backend.features.market.charts import MarketChartClient
+1 -1
View File
@@ -4,7 +4,7 @@ import json
from typing import Any from typing import Any
from backend.llm import transport as llm_transport from backend.llm import transport as llm_transport
from screener import FACTOR_FIELDS, REGIMES from backend.features.screener.engine import FACTOR_FIELDS, REGIMES
class LLMCompilerError(RuntimeError): class LLMCompilerError(RuntimeError):
+2 -2
View File
@@ -8,11 +8,11 @@ from collections import defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any
from advanced_strategies import ADVANCED_CURATED_STRATEGIES
from backend.data.numbers import finite_number as _number from backend.data.numbers import finite_number as _number
from backend.data.providers.tushare_client import TushareClient, TushareError
from backend.features.screener.strategies import ADVANCED_CURATED_STRATEGIES
from database import ReviewDatabase from database import ReviewDatabase
from backend.features.sentiment.engine import build_sentiment_history, latest_contiguous_history from backend.features.sentiment.engine import build_sentiment_history, latest_contiguous_history
from tushare_client import TushareClient, TushareError
REGIMES = { REGIMES = {
+1 -1
View File
@@ -289,7 +289,7 @@
}, },
{ {
"path": "backend/features/screener/engine.py", "path": "backend/features/screener/engine.py",
"bytes": 108394, "bytes": 108434,
"lines": 2206 "lines": 2206
}, },
{ {
+2 -1
View File
@@ -3,7 +3,8 @@ from __future__ import annotations
import argparse import argparse
from datetime import date from datetime import date
from server import SERVICE, normalize_date from backend.application import SERVICE
from backend.bootstrap.config import normalize_date
def main() -> None: def main() -> None:
+47 -6
View File
@@ -20,12 +20,6 @@ class FeatureBoundaryTests(unittest.TestCase):
} }
violations = [] violations = []
for path in FEATURES.rglob("*.py"): for path in FEATURES.rglob("*.py"):
# The screener engine is an exact-preservation move of the legacy
# calculation module. Its provider dependency is covered by the
# slice equivalence tests and will be addressed only after the
# behavior-preserving migration is complete.
if path.relative_to(FEATURES).as_posix() == "screener/engine.py":
continue
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree): for node in ast.walk(tree):
names = [] names = []
@@ -38,6 +32,53 @@ class FeatureBoundaryTests(unittest.TestCase):
violations.append(f"{path.relative_to(ROOT)} -> {name}") violations.append(f"{path.relative_to(ROOT)} -> {name}")
self.assertEqual(violations, []) self.assertEqual(violations, [])
def test_backend_uses_root_compatibility_modules_only_at_declared_boundaries(self) -> None:
compatibility_modules = {
"advanced_strategies",
"alert_service",
"api_access",
"app_config",
"assistant_agent",
"chart_data_provider",
"heaven_agent",
"heaven_engine",
"ifind_client",
"llm_strategy",
"llm_stream",
"market_insights",
"mentor_agent",
"realtime_aggregator",
"screener",
"security",
"sentiment_engine",
"server",
"strategy_tracking",
"trade_journal",
"tushare_client",
}
allowed = {
"backend/application.py": {"api_access"},
"backend/features/screener/repository.py": {"sentiment_engine"},
}
violations = []
for path in (ROOT / "backend").rglob("*.py"):
relative = path.relative_to(ROOT).as_posix()
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
names = []
if isinstance(node, ast.Import):
names = [alias.name for alias in node.names]
elif isinstance(node, ast.ImportFrom) and node.module:
names = [node.module]
for name in names:
root_name = name.split(".")[0]
if (
root_name in compatibility_modules
and root_name not in allowed.get(relative, set())
):
violations.append(f"{relative} -> {name}")
self.assertEqual(violations, [])
def test_legacy_service_modules_are_compatibility_exports_only(self) -> None: def test_legacy_service_modules_are_compatibility_exports_only(self) -> None:
for filename in ("alert_service.py", "trade_journal.py", "strategy_tracking.py"): for filename in ("alert_service.py", "trade_journal.py", "strategy_tracking.py"):
tree = ast.parse((ROOT / filename).read_text(encoding="utf-8")) tree = ast.parse((ROOT / filename).read_text(encoding="utf-8"))
+24
View File
@@ -22,6 +22,7 @@
| CR-02 | HTTP精确POST委托 | 27个端点重复使用“比较路径、调用无参数处理器、返回”三行分支 | 用公开/受保护两张显式映射统一委托,同时保留复杂路由的原控制流 | 已完成 | | CR-02 | HTTP精确POST委托 | 27个端点重复使用“比较路径、调用无参数处理器、返回”三行分支 | 用公开/受保护两张显式映射统一委托,同时保留复杂路由的原控制流 | 已完成 |
| CR-03 | 股票市场后缀转换 | Tushare业务与iFinD图表各保留一份完全相同的沪深京代码转换函数 | 图表复用`bootstrap/config.py::tushare_code`,只保留一份函数体 | 已完成 | | CR-03 | 股票市场后缀转换 | Tushare业务与iFinD图表各保留一份完全相同的沪深京代码转换函数 | 图表复用`bootstrap/config.py::tushare_code`,只保留一份函数体 | 已完成 |
| CR-04 | 数值归一化策略 | 四个业务模块分别保留两组完全相同的数值转换函数体 | 由`backend/data/numbers.py`集中拥有两种既有语义,消费者保留原局部别名 | 已完成 | | CR-04 | 数值归一化策略 | 四个业务模块分别保留两组完全相同的数值转换函数体 | 由`backend/data/numbers.py`集中拥有两种既有语义,消费者保留原局部别名 | 已完成 |
| CR-05 | 根级兼容入口 | 正式后端仍有五处通过迁移兼容模块反向导入规范实现 | 正式代码改用规范路径;兼容入口只服务原公开导入契约 | 已完成 |
## CR-01验收口径 ## CR-01验收口径
@@ -115,6 +116,29 @@
本批基线为`xiaobai-reduction-03-market-symbol-20260801`;检查点为 本批基线为`xiaobai-reduction-03-market-symbol-20260801`;检查点为
`xiaobai-reduction-04-numeric-normalization-20260801` `xiaobai-reduction-04-numeric-normalization-20260801`
## CR-05验收口径
- 逐项扫描根级Python入口、生产代码、测试、工具和动态导入;没有消费者或兼容责任的入口才能删除。
- 规范后端不得经由`screener``advanced_strategies``tushare_client``server`兼容入口
间接访问已经归位的实现。
- 所有根级模块继续保持原导入名称、导出对象及模块对象身份,既有启动命令和第三方维护脚本不受影响。
- `api_access`、选股Repository的惰性`sentiment_engine`导入及根级`database.py`属于已登记边界,
分别留到HTTP、Repository阶段处理,不在本批跨边界修改。
## CR-05结果
- 审计确认21个根级兼容入口均有测试、工具、启动或原公开导入契约消费者,因此本批没有冒险删除文件。
- 容器、策略编译器、选股引擎及数据同步命令的五处导入改为规范模块路径,正式代码不再通过四个根级
兼容模块反向进入实现;运行代码行数未增加。
- 特性边界测试取消选股引擎旧例外,并新增全后端兼容导入门禁;只允许两项已登记过渡边界,后续代码
无法重新引入隐式根级依赖。
- 候选321项、纯`app/`导出258项、24个JavaScript文件、API/架构注册表、Git空白检查和SQLite
完整性检查通过;本批不涉及页面、CSS或浏览器行为。
- 本批不修改业务计算、策略公式、数据源、API、数据库、LLM、权限、前端或部署。
本批基线为`xiaobai-reduction-04-numeric-normalization-20260801`;检查点为
`xiaobai-reduction-05-compatibility-boundaries-20260801`
## 人工验收记录 ## 人工验收记录
- 2026-08-01:用户检查CR-02与CR-03运行结果,确认未发现明显异常。本记录仅表示本轮可见功能与 - 2026-08-01:用户检查CR-02与CR-03运行结果,确认未发现明显异常。本记录仅表示本轮可见功能与