Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cab4b9cdf | ||
|
|
9028cb342d |
@@ -472,38 +472,6 @@ class DashboardService(
|
||||
**self.database.status(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _ifind_field(row: dict[str, Any], tokens: tuple[str, ...]) -> Any:
|
||||
for key, value in row.items():
|
||||
label = str(key or "")
|
||||
if any(token.casefold() == label.casefold() for token in tokens):
|
||||
return value
|
||||
for key, value in row.items():
|
||||
label = str(key or "")
|
||||
if any(token in label for token in tokens):
|
||||
return value
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _ifind_row_code(cls, row: dict[str, Any]) -> str:
|
||||
value = cls._ifind_field(row, ("股票代码", "证券代码", "代码", "thscode"))
|
||||
match = re.search(r"(?<!\d)(\d{6})(?!\d)", str(value or ""))
|
||||
if match:
|
||||
return match.group(1)
|
||||
for value in row.values():
|
||||
match = re.search(r"(?<!\d)(\d{6})\.(?:SH|SZ|BJ)(?![A-Z])", str(value or ""), re.I)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
|
||||
SERVICE = DashboardService()
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,35 @@ from typing import Any
|
||||
|
||||
|
||||
class HeavenRepositoryMixin:
|
||||
def list_sector_phase_overrides(self) -> dict[str, str]:
|
||||
with self.connect() as connection:
|
||||
rows = connection.execute(
|
||||
"SELECT name, element FROM sector_phase_overrides ORDER BY updated_at DESC, name"
|
||||
).fetchall()
|
||||
return {row["name"]: row["element"] for row in rows}
|
||||
|
||||
def save_sector_phase_override(self, name: str, element: str) -> None:
|
||||
now = datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
with self.connect() as connection:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO sector_phase_overrides (name, element, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
element = excluded.element,
|
||||
updated_at = excluded.updated_at
|
||||
""",
|
||||
(name, element, now),
|
||||
)
|
||||
|
||||
def delete_sector_phase_override(self, name: str) -> bool:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM sector_phase_overrides WHERE name = ?",
|
||||
(name,),
|
||||
)
|
||||
return cursor.rowcount > 0
|
||||
|
||||
@staticmethod
|
||||
def _heaven_reading_dict(row: sqlite3.Row | None) -> dict[str, Any] | None:
|
||||
if not row:
|
||||
|
||||
@@ -117,6 +117,30 @@ class PoolServiceMixin:
|
||||
finally:
|
||||
self._ifind_event_lock.release()
|
||||
|
||||
@staticmethod
|
||||
def _ifind_field(row: dict[str, Any], tokens: tuple[str, ...]) -> Any:
|
||||
for key, value in row.items():
|
||||
label = str(key or "")
|
||||
if any(token.casefold() == label.casefold() for token in tokens):
|
||||
return value
|
||||
for key, value in row.items():
|
||||
label = str(key or "")
|
||||
if any(token in label for token in tokens):
|
||||
return value
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _ifind_row_code(cls, row: dict[str, Any]) -> str:
|
||||
value = cls._ifind_field(row, ("股票代码", "证券代码", "代码", "thscode"))
|
||||
match = re.search(r"(?<!\d)(\d{6})(?!\d)", str(value or ""))
|
||||
if match:
|
||||
return match.group(1)
|
||||
for value in row.values():
|
||||
match = re.search(r"(?<!\d)(\d{6})\.(?:SH|SZ|BJ)(?![A-Z])", str(value or ""), re.I)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _normalize_ifind_event_time(value: Any) -> str:
|
||||
text = str(value or "").strip()
|
||||
|
||||
@@ -374,8 +374,8 @@
|
||||
},
|
||||
{
|
||||
"path": "backend/application.py",
|
||||
"bytes": 48513,
|
||||
"lines": 1119
|
||||
"bytes": 47552,
|
||||
"lines": 1087
|
||||
},
|
||||
{
|
||||
"path": "frontend/styles/theme.css",
|
||||
@@ -384,8 +384,8 @@
|
||||
},
|
||||
{
|
||||
"path": "database.py",
|
||||
"bytes": 33284,
|
||||
"lines": 746
|
||||
"bytes": 32073,
|
||||
"lines": 716
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-31
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -665,36 +665,6 @@ class ReviewDatabase(
|
||||
)
|
||||
MigrationRunner().apply(connection, MIGRATIONS)
|
||||
|
||||
|
||||
|
||||
def list_sector_phase_overrides(self) -> dict[str, str]:
|
||||
with self.connect() as connection:
|
||||
rows = connection.execute(
|
||||
"SELECT name, element FROM sector_phase_overrides ORDER BY updated_at DESC, name"
|
||||
).fetchall()
|
||||
return {row["name"]: row["element"] for row in rows}
|
||||
|
||||
def save_sector_phase_override(self, name: str, element: str) -> None:
|
||||
now = datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
with self.connect() as connection:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO sector_phase_overrides (name, element, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
element = excluded.element,
|
||||
updated_at = excluded.updated_at
|
||||
""",
|
||||
(name, element, now),
|
||||
)
|
||||
|
||||
def delete_sector_phase_override(self, name: str) -> bool:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM sector_phase_overrides WHERE name = ?",
|
||||
(name,),
|
||||
)
|
||||
return cursor.rowcount > 0
|
||||
def list_wencai_saved_queries(
|
||||
self, user_id: int, limit: int = 30
|
||||
) -> list[dict[str, Any]]:
|
||||
|
||||
@@ -38,6 +38,9 @@ HEAVEN_SERVICE_METHODS = {
|
||||
}
|
||||
|
||||
HEAVEN_REPOSITORY_METHODS = {
|
||||
"list_sector_phase_overrides",
|
||||
"save_sector_phase_override",
|
||||
"delete_sector_phase_override",
|
||||
"_heaven_reading_dict",
|
||||
"save_heaven_reading",
|
||||
"list_heaven_readings",
|
||||
|
||||
@@ -29,6 +29,8 @@ POOL_METHODS = {
|
||||
"_apply_reason_overrides",
|
||||
"_schedule_ifind_event_enrichment",
|
||||
"_refresh_ifind_event_enrichment",
|
||||
"_ifind_field",
|
||||
"_ifind_row_code",
|
||||
"_normalize_ifind_event_time",
|
||||
"_merge_ifind_event_enrichment",
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
| CR-06 | 紧凑日期显示 | Tushare与选股引擎各保留一份完全相同的`YYYYMMDD`显示转换 | 由`bootstrap/config.py`拥有唯一格式策略,消费者保留原局部别名 | 已完成 |
|
||||
| CR-07 | 数据Provider组装 | 核查网关、容器和业务服务是否重复创建外部数据客户端 | 固化唯一创建位置及兼容例外,不改数据源语义 | 已完成 |
|
||||
| CR-08 | NDJSON流式传输 | 问师与复盘助手重复维护响应头、事件写入、完成、断线及关闭流程 | HTTP共享层拥有唯一流式连接生命周期 | 已完成 |
|
||||
| CR-09 | 应用服务门面 | 两个仅服务股池iFinD补全的方法仍错位在全局`DashboardService` | 原函数体机械归位到`PoolServiceMixin` | 已完成 |
|
||||
| CR-10 | Repository所有权 | 五行行业阶段覆盖的三项持久化方法仍错位在根级数据库门面 | 原函数体机械归位到问天Repository,兼容数据继续保留 | 已完成 |
|
||||
|
||||
## CR-01验收口径
|
||||
|
||||
@@ -206,6 +208,45 @@
|
||||
本批基线为`xiaobai-reduction-07-provider-ownership-20260801`;检查点为
|
||||
`xiaobai-reduction-08-ndjson-transport-20260801`。
|
||||
|
||||
## CR-09验收口径
|
||||
|
||||
- 只有消费者全部属于单一领域、且能够按原函数体机械移动的方法才从应用门面移出。
|
||||
- `_ifind_field`与`_ifind_row_code`继续保持静态/类方法签名、字段优先级、大小写规则和代码正则。
|
||||
- 账号委托属于稳定公开门面;系统设置属于跨领域协调;后台刷新留到CR-11,本批均不得删除或重写。
|
||||
- 移动后`DashboardService`必须继续通过Mixin解析同名方法,调用点和返回值不变。
|
||||
|
||||
## CR-09结果
|
||||
|
||||
- 将iFinD字段匹配和股票代码提取两个方法从应用大类机械移动到股池服务,原版与迁移方法AST逐项
|
||||
相等;应用大类不再直接拥有股池专属实现。
|
||||
- 连同迁移期遗留空行,`backend/application.py`减少32行,股池服务增加24行,生产代码净减少8行。
|
||||
- 候选324项、纯`app/`导出261项、24个JavaScript文件、API/架构注册表、Git空白检查和SQLite
|
||||
完整性检查通过;本批不涉及页面、CSS或浏览器行为。
|
||||
- 本批不修改字段匹配、涨跌停原因补全、接口、数据源、缓存、数据库、权限或前端。
|
||||
|
||||
本批基线为`xiaobai-reduction-08-ndjson-transport-20260801`;检查点为
|
||||
`xiaobai-reduction-09-service-facade-20260801`。
|
||||
|
||||
## CR-10验收口径
|
||||
|
||||
- 只移动调用方、数据表和业务含义均明确属于单一领域的方法;数据库连接、事务和返回值必须保持不变。
|
||||
- `list_sector_phase_overrides`、`save_sector_phase_override`与`delete_sector_phase_override`必须由
|
||||
`backend/features/heaven/repository.py`拥有,并继续通过`ReviewDatabase`的Mixin解析。
|
||||
- 不修改表结构、迁移顺序、时间格式、排序、冲突更新或删除结果语义。
|
||||
- `wencai_saved_queries`及其三个方法属于已登记的账户隔离兼容数据;即使前端入口已取消,也必须保留。
|
||||
|
||||
## CR-10结果
|
||||
|
||||
- 将五行行业阶段覆盖的查询、保存和删除三个方法从根级`database.py`机械移动到问天Repository;
|
||||
调用名称、SQL、事务边界、时间值和返回结果均未改变。
|
||||
- 根级数据库门面不再直接拥有问天领域的持久化实现,问财历史兼容表和方法完整保留,未扩大删除范围。
|
||||
- 34项Repository、问天、账户隔离、清理契约及迁移定向测试通过;候选324项、纯`app/`导出261项、
|
||||
24个JavaScript文件、API/架构注册表、Git空白检查和SQLite完整性检查通过。
|
||||
- 本批不修改页面、CSS、API、数据库结构、行情、数据源、业务计算、LLM、权限或后台任务。
|
||||
|
||||
本批基线为`xiaobai-reduction-09-service-facade-20260801`;检查点为
|
||||
`xiaobai-reduction-10-repository-ownership-20260801`。
|
||||
|
||||
## 人工验收记录
|
||||
|
||||
- 2026-08-01:用户检查CR-02与CR-03运行结果,确认未发现明显异常。本记录仅表示本轮可见功能与
|
||||
|
||||
Reference in New Issue
Block a user