Files
group_xinghuo_jinrong/app/config/settings.py
T
GaoYiYuan_0626 318cb39a1f 基金转换 T-7:convert_service 八步编排(关键路径 · 幂等前置 + 三阶段)
- 新增 app/service/convert/convert_service.py:八步编排(①-④ 校验不落库 / ⑤ 占位 /
  ⑥ apply_convert / ⑦ 阶段 1.5 引擎不阻断 / ⑧ 回写 + 审计),幂等判定前置到校验之前
  以修复同键重试误报 InsufficientShares
- 新增 tests/test_convert_service.py(17 用例)
- 新增 scripts/dev/verify_convert_service.py(真 MySQL 验证 35/35)
- 改 app/repository/convert_repository.py:complete_convert 由纯 UPDATE 改三步法
  upsert(无占位直跑也能落完成行,R-a 口径)
- 改 app/repository/core_ro.py:新增 has_convert_trades / list_convert_trades /
  list_convert_lot_details 三个只读方法
- 改 app/config/settings.py:新增 6 个 convert 配置项
- 修 tests/test_convert_calc.py:TestPurity 排除编排层 convert_service.py

基线 639 → 656 passed / 3 skipped,零回归
2026-09-10 17:12:01 +08:00

110 lines
5.0 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
# ===== 基金转换(convert · 架构设计-基金转换交易 §11)=====
# 净值新鲜度:`nav_date` 距交易日超过该天数 → 额外落 `nav_stale` 副审计(PRD §2.2)。
# 一期全局单阈值(模拟库 14 只产品均日频净值);真实 Core 接入后按产品类型分档(评审 S5)。
convert_nav_stale_days: int = 3
# 幂等执行权锁 TTL(try_lock(convert:idem:{client_request_id}))
convert_lock_ttl_seconds: int = 30
# 单次转换最多跨越的批次数,超限 400 TOO_MANY_LOTS(不自动分拆,§1 原则 12)
convert_batch_max_lots: int = 200
# 补差费口径(D13):amount_diff = 价外法两端差(默认 B)· rate_diff = 费率差法(A)
convert_diff_fee_mode: str = "amount_diff"
# T+N 确认偏移(D14):真实为 T+1 **工作日**,模拟库无交易日历故用自然日近似
convert_confirm_offset_days: int = 1
# 阶段二失败补偿 SLA(小时):cleanup_pending_convert.py 据此把孤儿占位置 expired
convert_compensate_sla_hours: int = 24
# ===== 输入防护(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()