Add core database support and enhance documentation

- Introduced `MYSQL_CORE_DATABASE` in `.env.example` and `settings.py` for core database configuration.
- Added `CoreReadOnlyRepository` for read-only access to the `jinrong_core` database.
- Updated `AGENTS.md`, `README.md`, and various documentation files to reflect new agent onboarding processes and project structure.
- Revised requirements in `requirements.txt` to include `langgraph` and `langchain-core`.
- Enhanced `FLOW.md` with local bootstrap instructions for setting up the core simulation environment.
- Added new scripts for database creation and seeding for the core simulation library.
- Improved overall documentation for clarity on project architecture and memory management.
- Updated `TODO.md` to reflect current development priorities and tasks.
This commit is contained in:
2026-09-05 17:39:16 +08:00
parent 0374010b37
commit 1ddd44a6cb
34 changed files with 1848 additions and 97 deletions
+5
View File
@@ -0,0 +1,5 @@
"""数据访问层。"""
from app.repository.core_ro import CoreReadOnlyRepository
__all__ = ["CoreReadOnlyRepository"]
+125
View File
@@ -0,0 +1,125 @@
"""Core 模拟库只读访问(jinrong_core · 无 HTTP API)。"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
from sqlalchemy import create_engine, text
from sqlalchemy.engine import Engine
from app.config.settings import settings
class CoreReadOnlyRepository:
"""仅 SELECT jinrong_core;禁止写操作。"""
def __init__(self, engine: Engine | None = None) -> None:
self._engine = engine or self._default_engine()
@staticmethod
def _default_engine() -> Engine:
pwd = settings.mysql_password
auth = f"{settings.mysql_user}:{pwd}" if pwd else settings.mysql_user
url = (
f"mysql+pymysql://{auth}@{settings.mysql_host}:{settings.mysql_port}"
f"/{settings.mysql_core_database}?charset=utf8mb4"
)
return create_engine(url, pool_pre_ping=True)
def get_customer_l0(self, customer_id: str) -> dict[str, Any] | None:
sql = text(
"""
SELECT c.customer_id, c.display_name, c.age, c.occupation, c.open_date,
r.risk_code, r.evaluated_at AS risk_evaluated_at
FROM core_customer c
LEFT JOIN core_customer_risk r ON r.customer_id = c.customer_id
WHERE c.customer_id = :cid AND c.is_active = 1
"""
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"cid": customer_id}).mappings().first()
return dict(row) if row else None
def list_holdings(self, customer_id: str) -> list[dict[str, Any]]:
sql = text(
"""
SELECT h.*, p.product_name, p.min_risk_code, p.product_type
FROM core_holding h
JOIN core_product p ON p.product_id = h.product_id
WHERE h.customer_id = :cid
ORDER BY h.market_value DESC
"""
)
with self._engine.connect() as conn:
return [dict(r) for r in conn.execute(sql, {"cid": customer_id}).mappings()]
def list_trades(
self, customer_id: str, since: date | None = None, limit: int = 50
) -> list[dict[str, Any]]:
sql = text(
"""
SELECT t.*, p.product_name
FROM core_trade t
JOIN core_product p ON p.product_id = t.product_id
WHERE t.customer_id = :cid
AND (:since IS NULL OR t.traded_at >= :since)
ORDER BY t.traded_at DESC
LIMIT :lim
"""
)
with self._engine.connect() as conn:
return [
dict(r)
for r in conn.execute(
sql, {"cid": customer_id, "since": since, "lim": limit}
).mappings()
]
def get_product(self, product_id: str) -> dict[str, Any] | None:
sql = text("SELECT * FROM core_product WHERE product_id = :pid")
with self._engine.connect() as conn:
row = conn.execute(sql, {"pid": product_id}).mappings().first()
return dict(row) if row else None
def get_latest_nav(self, product_id: str) -> dict[str, Any] | None:
sql = text(
"""
SELECT * FROM core_product_nav
WHERE product_id = :pid
ORDER BY nav_date DESC LIMIT 1
"""
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"pid": product_id}).mappings().first()
return dict(row) if row else None
def list_customers_by_advisor(self, advisor_id: str) -> list[str]:
sql = text(
"""
SELECT customer_id FROM core_customer_advisor
WHERE advisor_id = :aid AND rel_status = 'active'
"""
)
with self._engine.connect() as conn:
return [r[0] for r in conn.execute(sql, {"aid": advisor_id})]
def get_staff(self, staff_id: str) -> dict[str, Any] | None:
"""RBAC 联调:查员工角色种子。"""
sql = text(
"SELECT staff_id, display_name, staff_type, roles FROM core_staff WHERE staff_id = :sid AND is_active = 1"
)
with self._engine.connect() as conn:
row = conn.execute(sql, {"sid": staff_id}).mappings().first()
return dict(row) if row else None
def is_advisor_assigned(self, advisor_id: str, customer_id: str) -> bool:
sql = text(
"""
SELECT 1 FROM core_customer_advisor
WHERE advisor_id = :aid AND customer_id = :cid AND rel_status = 'active'
LIMIT 1
"""
)
with self._engine.connect() as conn:
return conn.execute(sql, {"aid": advisor_id, "cid": customer_id}).first() is not None