Files
group_xinghuo_jinrong/app/config/settings.py
T
GaoYiYuan_0626 c5182f1910 feat(convert): 基金转换 T-0/T-0b 门禁 + T-1 数据层 + T-2/T-2b 纯函数与实算回填
T-0 / T-0b(门禁 · 2026-09-10)
- T-0:sqlite 与 MySQL 结构对齐 —— core_holding 统一为 qty/cost_amount/as_of/pnl_pct
  + PK + UNIQUE(customer_id, product_id);补 core_product_nav;新增建库自校验
  _assert_ddl_aligned()(R-g);test_db.py 增 3 条门禁用例(含反向验证门禁失效)
- T-0b:DB 账号分离(D20)—— 新增 scripts/core/00-grant.sql(三账号逐表授权);
  settings.py 增 3 组账号;db.py 改 get_engine(db, role),缓存键改为 (库名, 角色),
  账号未配置回退单账号;core_ro→ro / gateway_repository→rw / risk·session_repository→rw;
  tests/conftest.py 四处显式 role="admin"(R-e)

T-1(数据层)
- scripts/core/01-ddl.sql:新建 core_fee_rule / core_share_lot / core_convert_lot_detail;
  core_trade 加 convert_group_id + idx_convert_group;core_product 加 8 列 + fee_rate 补 COMMENT
- 新增 07-seed-fee-rule.sql(赎回费 5 档 × 14 产品,按 22 号文 §10)/ 08-seed-share-lot.sql
  (58 行持仓 → 61 行批次,Σ remain_qty 恒等于 qty)/ 09-seed-org.sql(管理人 + TA +
  申购费率 + 最低持有余额,v1.1 按「管理人全产品线」重排)
- reset.ps1 追加 07/08/09;02-mysql-agent专用.sql 追加 risk_convert_detail
- tests/_ddl.py 同步 4 表 + 新增 REQUIRED_CONVERT_TABLES 建库门禁
- 新增 scripts/dev/verify_convert_seed.py(pymysql 等价 reset 流程 + 8 条 DoD 断言,
  含断言 ⑧「费率档 ↔ product_type 匹配」,越档即 FAIL)

T-2 / T-2b(纯函数包 + 示例实算回填)
- 新增 app/service/convert/ 7 文件:__init__ / types / calc / fee / nav / lot_bootstrap / errors
  (纯函数,不查库、不碰 SQL;所有量化显式 ROUND_HALF_UP;lot_bootstrap 用 zlib.crc32
   保证 D18 跨进程同源)
- 新增 tests/test_convert_calc.py 93 用例(12 类:HALF_UP 反向自证 / 分档边界 /
  FIFO 含同 confirmed_at 兜底 / 双口径 / 强制全转与强制赎回 / PRD §5.3 全链自证 /
  纯函数零 IO 依赖断言)
- 重写 scripts/dev/calc_convert_demo.py:去掉脚本内公式副本,改为调用生产 calc.py,
  末尾与 PRD §5.3 逐项比对(不一致即退出码 1),兼作一致性门禁

验证
- pytest 609 passed / 3 skipped(516 → +93,零回归)
- verify_convert_seed.py 8/8 PASS;calc_convert_demo.py 15/15 与 PRD §5.3 一致

文档:PRD v0.9.1(费率分类修正)· 架构 §7 签名回填 / §8.3 错误码注 / §15 T-2 完成 ·
开发计划 §1.5 新增 R-h + §4.2·§4.3 执行记录 · AGENTS.md · docs/memory
2026-09-10 14:45:55 +08:00

95 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""环境配置(从 .env 读取,见 .env.example)。"""
from decimal import Decimal
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
app_env: str = "development"
mysql_host: str = "127.0.0.1"
mysql_port: int = 3306
mysql_database: str = "jinrong_agent"
mysql_core_database: str = "jinrong_core"
mysql_user: str = "root"
mysql_password: str = ""
# ===== DB 账号分离(D20 · 架构设计-基金转换交易 §11.1)=====
# 把「Core 只读」「审计不可删改」从代码约定升级为 DB 级强制。
# 账号由 scripts/core/00-grant.sql 创建(管理员执行一次,不进 reset.ps1)。
# 留空 = 回退 mysql_user,行为与单账号时代完全一致(渐进启用,不阻塞本地开发)。
mysql_core_ro_user: str = ""
mysql_core_ro_password: str = ""
mysql_core_rw_user: str = ""
mysql_core_rw_password: str = ""
mysql_agent_user: str = ""
mysql_agent_password: str = ""
redis_url: str = "redis://127.0.0.1:6379/0"
neo4j_uri: str = "bolt://localhost:7687"
neo4j_user: str = "neo4j"
neo4j_password: str = ""
milvus_uri: str = "./data/milvus.db"
ollama_base_url: str = "http://127.0.0.1:11434"
embed_model: str = "bge-m3"
# 向量维度(必须与 Ollama bge-m3 输出一致;Milvus Collection 建集合时同源引用)
embed_dim: int = 1024
# Ollama embedding 单次请求超时(秒);本地推理首次加载模型可能较慢
embed_timeout_seconds: float = 60.0
deepseek_api_key: str = ""
deepseek_base_url: str = "https://api.deepseek.com"
# ===== JWT(T-01 · JWT 手册 §4/§11)=====
# RS256 公钥路径(生产,私钥仅在 IdP);为空时用 HS256 + jwt_dev_secret(仅 development)
jwt_public_key_path: str = ""
jwt_dev_secret: str = "change-me-in-dev-only"
jwt_issuer: str = "https://idp.jinrong.internal"
jwt_audience: str = "agent-gateway"
# ===== Risk 阈值(默认值=冻结规则 · docs/PRD/附-风控规则表.md)=====
# 注:risk_assessment_valid_days 已随 AL-05(对齐 main 基准)退役——
# 风评有效期改由 core_customer_risk.expires_at 数据驱动(FM-03),不再可配。
risk_large_amount: Decimal = Decimal("500000")
risk_daily_total: Decimal = Decimal("500000")
risk_freq_count: int = 3
risk_probe_window_minutes: int = 5
risk_probe_count: int = 3
risk_probe_amount: Decimal = Decimal("400000")
risk_small_amount: Decimal = Decimal("10000")
risk_small_count: int = 3
risk_aml_default_threshold: Decimal = Decimal("0.85")
# ===== 风控追加 v1.1(FR-8/9/10 · PRD §4A · C4~C6 共用,一次性加齐)=====
# FR-8 RISK-006 集中度:R4+R5 市值占比阈值(≥ 即命中)
risk_concentration_threshold: float = 0.80
# FR-9 RISK-007 时效升级:扫描周期(脚本侧参考)与两级超时小时数
risk_escalation_scan_minutes: int = 15
risk_escalation_l1_hours: int = 4
risk_escalation_l2_hours: int = 24
# AML 单走短通道(1h/4h),与普通单分开计
risk_escalation_aml_l1_hours: int = 1
risk_escalation_aml_l2_hours: int = 4
# FR-10 RISK-008 代理人行为链:扫描周期与 A/B/C 三条件窗口与次数
risk_agent_behavior_scan_minutes: int = 30
risk_agent_behavior_a_window_hours: int = 24
risk_agent_behavior_a_count: int = 3
risk_agent_behavior_b_window_hours: int = 72
risk_agent_behavior_b_count: int = 5
risk_agent_behavior_c_window_hours: int = 24
risk_agent_behavior_c_count: int = 10
# ===== 输入防护(T-03 · F-03)=====
# 对话限流:actor 级固定窗口(拍板 2026-09-07:30 次/分钟,Redis 异常 fail-open)
guard_rate_limit_max: int = 30
guard_rate_limit_window_seconds: int = 60
settings = Settings()