"""环境配置(从 .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()