docs: inventory current application architecture
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"captured_from": "323c734",
|
||||
"runtime": {
|
||||
"http_server": "http.server.ThreadingHTTPServer",
|
||||
"application_processes": 1,
|
||||
"database": "SQLite WAL",
|
||||
"frontend": "build-free HTML/CSS/JavaScript",
|
||||
"container_port": 8765
|
||||
},
|
||||
"counts": {
|
||||
"primary_pages": 16,
|
||||
"api_exact_paths": 53,
|
||||
"api_prefixes": 0,
|
||||
"api_patterns": 11,
|
||||
"database_tables": 34
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"id": "sentimentCycleView",
|
||||
"title": "情绪周期"
|
||||
},
|
||||
{
|
||||
"id": "limitPool",
|
||||
"title": "涨停池"
|
||||
},
|
||||
{
|
||||
"id": "brokenView",
|
||||
"title": "炸板池"
|
||||
},
|
||||
{
|
||||
"id": "downView",
|
||||
"title": "跌停板"
|
||||
},
|
||||
{
|
||||
"id": "yesterdayView",
|
||||
"title": "昨日涨停"
|
||||
},
|
||||
{
|
||||
"id": "performanceView",
|
||||
"title": "涨停表现"
|
||||
},
|
||||
{
|
||||
"id": "ladderView",
|
||||
"title": "市场天梯"
|
||||
},
|
||||
{
|
||||
"id": "rotationView",
|
||||
"title": "板块轮动"
|
||||
},
|
||||
{
|
||||
"id": "auctionView",
|
||||
"title": "集合竞价"
|
||||
},
|
||||
{
|
||||
"id": "themeLibraryView",
|
||||
"title": "题材库"
|
||||
},
|
||||
{
|
||||
"id": "popularityView",
|
||||
"title": "人气热榜"
|
||||
},
|
||||
{
|
||||
"id": "dragonView",
|
||||
"title": "龙虎榜"
|
||||
},
|
||||
{
|
||||
"id": "screenerView",
|
||||
"title": "智能选股"
|
||||
},
|
||||
{
|
||||
"id": "mentorView",
|
||||
"title": "问师"
|
||||
},
|
||||
{
|
||||
"id": "heavenView",
|
||||
"title": "问天"
|
||||
},
|
||||
{
|
||||
"id": "reviewWorkspaceView",
|
||||
"title": "我的复盘"
|
||||
}
|
||||
],
|
||||
"api": {
|
||||
"exact": [
|
||||
"/api/account/birth-profile",
|
||||
"/api/account/password",
|
||||
"/api/account/status",
|
||||
"/api/admin/membership",
|
||||
"/api/admin/refresh",
|
||||
"/api/admin/settings",
|
||||
"/api/admin/settings/test",
|
||||
"/api/alerts",
|
||||
"/api/alerts/read-all",
|
||||
"/api/assistant/chat",
|
||||
"/api/assistant/messages",
|
||||
"/api/auction",
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/api/auth/me",
|
||||
"/api/auth/register",
|
||||
"/api/backfill",
|
||||
"/api/chart/intraday",
|
||||
"/api/dashboard",
|
||||
"/api/dragon-tiger",
|
||||
"/api/dragon-tiger/profiles",
|
||||
"/api/health",
|
||||
"/api/heaven/hexagram",
|
||||
"/api/heaven/interpret",
|
||||
"/api/heaven/personal",
|
||||
"/api/heaven/readings",
|
||||
"/api/heaven/sector-phases",
|
||||
"/api/heaven/setup",
|
||||
"/api/mentors/chat",
|
||||
"/api/mentors/messages",
|
||||
"/api/mentors/preferences",
|
||||
"/api/mentors/setup",
|
||||
"/api/notes",
|
||||
"/api/popularity",
|
||||
"/api/realtime-aggregate/health",
|
||||
"/api/reasons",
|
||||
"/api/rotation/history",
|
||||
"/api/rotation/members",
|
||||
"/api/screener/compile",
|
||||
"/api/screener/run",
|
||||
"/api/screener/setup",
|
||||
"/api/screener/strategies",
|
||||
"/api/screener/sync",
|
||||
"/api/screener/tracking",
|
||||
"/api/screener/tracking/refresh",
|
||||
"/api/search",
|
||||
"/api/search/detail",
|
||||
"/api/seat-aliases",
|
||||
"/api/sentiment/history",
|
||||
"/api/themes",
|
||||
"/api/themes/detail",
|
||||
"/api/trades",
|
||||
"/api/watchlist"
|
||||
],
|
||||
"prefixes": [],
|
||||
"patterns": [
|
||||
"/api/alerts/(\\d+)",
|
||||
"/api/alerts/(\\d+)/read",
|
||||
"/api/heaven/readings/(\\d+)",
|
||||
"/api/heaven/sector-phases/(.+)",
|
||||
"/api/notes/(\\d+)",
|
||||
"/api/screener/strategies/(\\d+)",
|
||||
"/api/screener/tracking/(\\d+)",
|
||||
"/api/stock/(\\d{6})",
|
||||
"/api/stock/(\\d{6})/preview",
|
||||
"/api/trades/(\\d+)",
|
||||
"/api/watchlist/(\\d{6})"
|
||||
]
|
||||
},
|
||||
"database_tables": [
|
||||
"users",
|
||||
"user_sessions",
|
||||
"user_credentials",
|
||||
"user_birth_profiles",
|
||||
"system_settings",
|
||||
"llm_usage",
|
||||
"dashboard_snapshots",
|
||||
"sync_runs",
|
||||
"data_snapshots",
|
||||
"watchlist",
|
||||
"review_notes",
|
||||
"reason_overrides",
|
||||
"seat_aliases",
|
||||
"sector_phase_overrides",
|
||||
"stock_master",
|
||||
"daily_bars",
|
||||
"benchmark_bars",
|
||||
"daily_indicators",
|
||||
"fundamental_indicators",
|
||||
"moneyflow_daily",
|
||||
"auction_factors",
|
||||
"earnings_events",
|
||||
"popularity_factors",
|
||||
"lhb_institution_daily",
|
||||
"screener_strategies",
|
||||
"screener_runs",
|
||||
"mentor_messages",
|
||||
"mentor_preferences",
|
||||
"wencai_saved_queries",
|
||||
"strategy_tracks",
|
||||
"alerts",
|
||||
"trade_entries",
|
||||
"assistant_messages",
|
||||
"heaven_readings"
|
||||
],
|
||||
"background_job_methods": [
|
||||
"_background_refresh_loop",
|
||||
"_run_background_sync",
|
||||
"_schedule_automatic_screeners",
|
||||
"_schedule_ifind_event_enrichment",
|
||||
"run_automatic_screeners"
|
||||
],
|
||||
"external_data_adapters": [
|
||||
{
|
||||
"provider": "tushare",
|
||||
"path": "tushare_client.py",
|
||||
"runtime_role": "primary deterministic market data"
|
||||
},
|
||||
{
|
||||
"provider": "ifind",
|
||||
"path": "ifind_client.py",
|
||||
"runtime_role": "realtime, charts, snapshots, enrichment"
|
||||
},
|
||||
{
|
||||
"provider": "eastmoney",
|
||||
"path": "chart_data_provider.py",
|
||||
"runtime_role": "display chart fallback"
|
||||
},
|
||||
{
|
||||
"provider": "eastmoney",
|
||||
"path": "realtime_aggregator.py",
|
||||
"runtime_role": "isolated realtime observation"
|
||||
},
|
||||
{
|
||||
"provider": "tencent",
|
||||
"path": "realtime_aggregator.py",
|
||||
"runtime_role": "index observation fallback"
|
||||
}
|
||||
],
|
||||
"llm_entrypoints": [
|
||||
{
|
||||
"function": "stream_with_mentor",
|
||||
"path": "mentor_agent.py"
|
||||
},
|
||||
{
|
||||
"function": "interpret_heaven",
|
||||
"path": "heaven_agent.py"
|
||||
},
|
||||
{
|
||||
"function": "stream_review_assistant",
|
||||
"path": "assistant_agent.py"
|
||||
},
|
||||
{
|
||||
"function": "compile_strategy_with_llm",
|
||||
"path": "llm_strategy.py"
|
||||
},
|
||||
{
|
||||
"function": "test_llm_connection",
|
||||
"path": "llm_strategy.py"
|
||||
}
|
||||
],
|
||||
"css_layers": [
|
||||
"/styles.css",
|
||||
"/renovation.css?v=20260725-5",
|
||||
"/redesign-v2.css?v=20260728-1",
|
||||
"/design-system.css?v=20260728-4",
|
||||
"/theme.css?v=20260728-2",
|
||||
"/wentian-v2.css?v=20260728-7"
|
||||
],
|
||||
"code_hotspots": [
|
||||
{
|
||||
"path": "static/app.js",
|
||||
"bytes": 449457,
|
||||
"lines": 9447
|
||||
},
|
||||
{
|
||||
"path": "static/styles.css",
|
||||
"bytes": 364385,
|
||||
"lines": 15549
|
||||
},
|
||||
{
|
||||
"path": "server.py",
|
||||
"bytes": 269167,
|
||||
"lines": 5965
|
||||
},
|
||||
{
|
||||
"path": "static/redesign-v2.css",
|
||||
"bytes": 264960,
|
||||
"lines": 8616
|
||||
},
|
||||
{
|
||||
"path": "static/index.html",
|
||||
"bytes": 133569,
|
||||
"lines": 1871
|
||||
},
|
||||
{
|
||||
"path": "database.py",
|
||||
"bytes": 121468,
|
||||
"lines": 2839
|
||||
},
|
||||
{
|
||||
"path": "screener.py",
|
||||
"bytes": 108535,
|
||||
"lines": 2213
|
||||
},
|
||||
{
|
||||
"path": "tushare_client.py",
|
||||
"bytes": 94312,
|
||||
"lines": 2175
|
||||
},
|
||||
{
|
||||
"path": "static/renovation.css",
|
||||
"bytes": 86245,
|
||||
"lines": 1636
|
||||
},
|
||||
{
|
||||
"path": "static/wentian-v2.css",
|
||||
"bytes": 73222,
|
||||
"lines": 1084
|
||||
},
|
||||
{
|
||||
"path": "market_insights.py",
|
||||
"bytes": 58066,
|
||||
"lines": 1312
|
||||
},
|
||||
{
|
||||
"path": "static/theme.css",
|
||||
"bytes": 41279,
|
||||
"lines": 1407
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Stage 02: Architecture Inventory
|
||||
|
||||
Date: 2026-07-29
|
||||
|
||||
The machine-readable inventory is `architecture-inventory.json`. Regenerate it with:
|
||||
|
||||
```shell
|
||||
python tools/build_architecture_inventory.py
|
||||
python tools/build_architecture_inventory.py --check
|
||||
```
|
||||
|
||||
## Current Shape
|
||||
|
||||
- The product is a single-container modular monolith with one Python process and SQLite WAL.
|
||||
- The browser is a build-free single-page application served by the same process.
|
||||
- The primary navigation exposes 16 workspaces.
|
||||
- The current database bootstrap defines 34 tables.
|
||||
- HTTP routing and service orchestration are concentrated in `server.py`.
|
||||
- Schema creation, inline migrations, and all persistence methods are concentrated in
|
||||
`database.py`.
|
||||
- The frontend loads six ordered CSS layers and one global application script.
|
||||
- Background refresh and automatic screener work execute as daemon threads in the HTTP
|
||||
process.
|
||||
|
||||
## Ownership Inventory
|
||||
|
||||
### Shared market and system data
|
||||
|
||||
Dashboard snapshots, synchronization runs, stock master data, daily bars, benchmark bars,
|
||||
daily indicators, fundamental indicators, money flow, auction factors, earnings events,
|
||||
popularity factors, institutional list data, public screener strategies, reason overrides,
|
||||
seat aliases, sector phase overrides, and encrypted system settings are shared.
|
||||
|
||||
### User-owned data
|
||||
|
||||
Sessions, personal credentials, birth profiles, watchlists, review notes, custom strategies,
|
||||
private screener runs, mentor messages and preferences, saved queries, tracking entries,
|
||||
alerts, trades, assistant messages, and heaven readings are user-scoped. Every repository
|
||||
extracted in later stages must preserve this boundary explicitly.
|
||||
|
||||
## Runtime Entrypoints
|
||||
|
||||
- HTTP and application bootstrap: `server.py`
|
||||
- Persistence and migrations: `database.py`
|
||||
- Access policy: `api_access.py`
|
||||
- Market providers: `tushare_client.py`, `ifind_client.py`, `chart_data_provider.py`,
|
||||
`realtime_aggregator.py`
|
||||
- Deterministic computation: `sentiment_engine.py`, `screener.py`, `advanced_strategies.py`,
|
||||
`market_insights.py`, `heaven_engine.py`
|
||||
- LLM calls: `mentor_agent.py`, `heaven_agent.py`, `assistant_agent.py`, `llm_strategy.py`
|
||||
- Browser shell and pages: `static/index.html`, `static/app.js`, and the ordered CSS files
|
||||
|
||||
## Primary Structural Risks
|
||||
|
||||
1. Request routing, use cases, scheduling, provider selection, serialization, and account
|
||||
context coexist in one service module.
|
||||
2. Database migrations cannot be reviewed independently from repository behavior.
|
||||
3. Several business paths instantiate provider clients directly, so source policy is not
|
||||
centrally enforceable.
|
||||
4. LLM authorization, quota accounting, model selection, streaming, and error handling are
|
||||
distributed across multiple adapters.
|
||||
5. Global DOM state and page behavior share one JavaScript file, increasing cross-page
|
||||
regression risk.
|
||||
6. CSS correctness depends on load order and late overrides rather than explicit layer
|
||||
ownership.
|
||||
7. In-process daemon jobs have no durable queue and some failure paths are not observable.
|
||||
|
||||
## Inventory Limits
|
||||
|
||||
The JSON inventory records source-level declarations and references. It does not claim that
|
||||
every declared endpoint is exercised or every table is populated. Runtime coverage and data
|
||||
quality are separate contracts introduced in later governance stages.
|
||||
@@ -0,0 +1,165 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUTPUT = ROOT / "docs" / "governance" / "architecture-inventory.json"
|
||||
|
||||
|
||||
def relative(path: Path) -> str:
|
||||
return path.relative_to(ROOT).as_posix()
|
||||
|
||||
|
||||
def source(path: str) -> str:
|
||||
return (ROOT / path).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def page_inventory(html: str) -> list[dict[str, str]]:
|
||||
items: list[dict[str, str]] = []
|
||||
seen: set[str] = set()
|
||||
pattern = re.compile(
|
||||
r'<button[^>]+data-view="(?P<id>[^"]+)"[^>]+title="(?P<title>[^"]+)"',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for match in pattern.finditer(html):
|
||||
page_id = match.group("id")
|
||||
if page_id in seen:
|
||||
continue
|
||||
seen.add(page_id)
|
||||
items.append({"id": page_id, "title": match.group("title")})
|
||||
return items
|
||||
|
||||
|
||||
def api_inventory(server: str) -> dict[str, list[str]]:
|
||||
exact = sorted(set(re.findall(r'parsed\.path\s*==\s*"(/api/[^"]+)"', server)))
|
||||
prefixes = sorted(
|
||||
set(re.findall(r'parsed\.path\.startswith\(\s*"(/api/[^"]+)"', server))
|
||||
)
|
||||
patterns = sorted(
|
||||
set(
|
||||
item
|
||||
for item in re.findall(r'r?["\']([^"\']*?/api/[^"\']+)["\']', server)
|
||||
if "\\d" in item or ".+" in item or "(?P" in item
|
||||
)
|
||||
)
|
||||
return {"exact": exact, "prefixes": prefixes, "patterns": patterns}
|
||||
|
||||
|
||||
def database_inventory(database: str) -> list[str]:
|
||||
return re.findall(r"CREATE TABLE IF NOT EXISTS\s+([a-zA-Z0-9_]+)", database)
|
||||
|
||||
|
||||
def python_functions(path: str, prefixes: tuple[str, ...]) -> list[str]:
|
||||
tree = ast.parse(source(path), filename=path)
|
||||
result: list[str] = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith(prefixes):
|
||||
result.append(node.name)
|
||||
return sorted(set(result))
|
||||
|
||||
|
||||
def css_layers(html: str) -> list[str]:
|
||||
return re.findall(r'<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"', html)
|
||||
|
||||
|
||||
def code_hotspots() -> list[dict[str, Any]]:
|
||||
candidates = [
|
||||
"server.py",
|
||||
"database.py",
|
||||
"screener.py",
|
||||
"market_insights.py",
|
||||
"tushare_client.py",
|
||||
"static/index.html",
|
||||
"static/app.js",
|
||||
"static/styles.css",
|
||||
"static/redesign-v2.css",
|
||||
"static/renovation.css",
|
||||
"static/theme.css",
|
||||
"static/wentian-v2.css",
|
||||
]
|
||||
rows = []
|
||||
for name in candidates:
|
||||
path = ROOT / name
|
||||
rows.append(
|
||||
{
|
||||
"path": name,
|
||||
"bytes": path.stat().st_size,
|
||||
"lines": len(path.read_text(encoding="utf-8").splitlines()),
|
||||
}
|
||||
)
|
||||
return sorted(rows, key=lambda item: item["bytes"], reverse=True)
|
||||
|
||||
|
||||
def build() -> dict[str, Any]:
|
||||
html = source("static/index.html")
|
||||
server = source("server.py")
|
||||
database = source("database.py")
|
||||
pages = page_inventory(html)
|
||||
api = api_inventory(server)
|
||||
tables = database_inventory(database)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"captured_from": "323c734",
|
||||
"runtime": {
|
||||
"http_server": "http.server.ThreadingHTTPServer",
|
||||
"application_processes": 1,
|
||||
"database": "SQLite WAL",
|
||||
"frontend": "build-free HTML/CSS/JavaScript",
|
||||
"container_port": 8765,
|
||||
},
|
||||
"counts": {
|
||||
"primary_pages": len(pages),
|
||||
"api_exact_paths": len(api["exact"]),
|
||||
"api_prefixes": len(api["prefixes"]),
|
||||
"api_patterns": len(api["patterns"]),
|
||||
"database_tables": len(tables),
|
||||
},
|
||||
"pages": pages,
|
||||
"api": api,
|
||||
"database_tables": tables,
|
||||
"background_job_methods": python_functions(
|
||||
"server.py", ("_background", "_run_background", "_schedule_", "run_automatic")
|
||||
),
|
||||
"external_data_adapters": [
|
||||
{"provider": "tushare", "path": "tushare_client.py", "runtime_role": "primary deterministic market data"},
|
||||
{"provider": "ifind", "path": "ifind_client.py", "runtime_role": "realtime, charts, snapshots, enrichment"},
|
||||
{"provider": "eastmoney", "path": "chart_data_provider.py", "runtime_role": "display chart fallback"},
|
||||
{"provider": "eastmoney", "path": "realtime_aggregator.py", "runtime_role": "isolated realtime observation"},
|
||||
{"provider": "tencent", "path": "realtime_aggregator.py", "runtime_role": "index observation fallback"},
|
||||
],
|
||||
"llm_entrypoints": [
|
||||
{"function": "stream_with_mentor", "path": "mentor_agent.py"},
|
||||
{"function": "interpret_heaven", "path": "heaven_agent.py"},
|
||||
{"function": "stream_review_assistant", "path": "assistant_agent.py"},
|
||||
{"function": "compile_strategy_with_llm", "path": "llm_strategy.py"},
|
||||
{"function": "test_llm_connection", "path": "llm_strategy.py"},
|
||||
],
|
||||
"css_layers": css_layers(html),
|
||||
"code_hotspots": code_hotspots(),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build the current architecture inventory")
|
||||
parser.add_argument("--check", action="store_true", help="fail when the committed inventory is stale")
|
||||
args = parser.parse_args()
|
||||
rendered = json.dumps(build(), ensure_ascii=False, indent=2) + "\n"
|
||||
if args.check:
|
||||
if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != rendered:
|
||||
raise SystemExit("architecture inventory is stale; run tools/build_architecture_inventory.py")
|
||||
print("Architecture inventory is current.")
|
||||
return 0
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_text(rendered, encoding="utf-8")
|
||||
print(relative(OUTPUT))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user