- 新增 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,零回归
110 lines
5.0 KiB
Python
110 lines
5.0 KiB
Python
"""环境配置(从 .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()
|