Files
xsl 76a0502ea3 feat(billing): 任务换额度定稿闭环——claim 凭据校验(40311)+ 定稿 4 任务目录
- claim_task_bonus 在幂等快捷路径前做完成凭据校验:report_feedback 查当日 rp_feedback、first_report 查 succeeded rp_report,无凭据 40311 且不打流水;sign_in/share_report 不校验(产品决策:签到行为即领取,分享内测期接受端侧自报,hard_cap 锁死刷量收益)
- task_bonus_rules 默认值与 .env.example 同步产品定稿(产品方案 §3.2,2026-09-11):sign_in 对话+10/day、report_feedback 发型+2/day、share_report 换装+3/day、first_report 重生成+1/lifetime;下架 invite_friend/profile_complete
- billing/repo 新增只读凭据探测 has_feedback_between/has_completed_report(import-linter 禁 billing→report,直查 shared.models 的 rp_* 表,永远只读)
- 测试:test_task_bonus_eligibility 5 用例(40311 两分支/凭据后 200/40910/40410/零流水)

偏差说明:test_task_bonus 与 test_task_bonus_rules 断言数值随定稿更新(发放/幂等/封顶语义未动);quota.py docstring「占位值」说法顺手更正。
2026-09-12 00:23:43 +08:00

164 lines
10 KiB
Bash
Executable File
Raw Permalink 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 已在 .gitignore 中,不要提交。
# dev/staging/prod 由部署平台注入环境变量,不使用 .env(架构文档 §11.2)。
APP_ENV=local
APP_NAME=meida-backend
API_PREFIX=/api/v1
DEBUG=true
LOG_LEVEL=INFO
LOG_JSON=true
# 需要额外脱敏的字段名,逗号分隔;默认清单见 packages/shared/logging.py
LOG_EXTRA_REDACT_KEYS=
# ── 数据库 / Redis ─────────────────────────────────────────────────────────
# 端口刻意用 55432 / 56379,避免与本机已有的 PostgreSQL、Redis 抢端口
DATABASE_URL=postgresql+asyncpg://meida:meida@localhost:55432/meida
DB_POOL_SIZE=10
DB_MAX_OVERFLOW=20
DB_ECHO=false
REDIS_URL=redis://localhost:56379/0
CELERY_BROKER_URL=redis://localhost:56379/1
CELERY_RESULT_BACKEND=redis://localhost:56379/2
# ── 鉴权 ──────────────────────────────────────────────────────────────────
# 生成:openssl rand -hex 32 。staging/prod 若沿用占位值会启动失败
JWT_SECRET=change-me-local-only-do-not-use-outside-local
JWT_ALGORITHM=HS256
JWT_ACCESS_TTL_SECONDS=900
JWT_ISSUER=meida
# 免登录换 token 的调试入口,仅本地/联调可开
DEV_AUTH_ENABLED=true
# ── LLM 路由 ──────────────────────────────────────────────────────────────
LLM_PRIMARY_PROVIDER=deepseek
LLM_BACKUP_PROVIDER=zhipu
LLM_STRUCTURED_MAX_RETRIES=2
LLM_CIRCUIT_FAILURE_THRESHOLD=3
LLM_CIRCUIT_COOLDOWN_SECONDS=30
# 按场景覆盖模型与采样参数,如 {"agent_chat":{"temperature":0.5}}
LLM_SCENE_OVERRIDES_JSON={}
DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_MODEL=deepseek-v4-flash
DEEPSEEK_TIMEOUT_SECONDS=30
ZHIPU_API_KEY=
ZHIPU_BASE_URL=https://open.bigmodel.cn/api/paas/v4
ZHIPU_MODEL=glm-4-flash
ZHIPU_TIMEOUT_SECONDS=30
# ── Prompt 缓存纪律 ───────────────────────────────────────────────────────
# strict:固定前缀里出现时间戳/随机值等易变 token 直接启动失败。
# 命中缓存的输入价格只有未命中的 1/30,前缀被污染会让输入成本翻数十倍。
LLM_CACHE_PREFIX_MODE=strict
PROMPTS_DIR=prompts
# ── 成本单价(单位:分 / 百万 token)──────────────────────────────────────
# 取自 DeepSeek 官方价目表。2026-08-17 00:00 起改为峰谷定价:
# 高峰(北京时间 09:00-12:00、14:00-18:00)单价是空闲时段的 2 倍。
# 因此这里要配两套,由 packages/llm/cost.py 按调用时刻选择。
# flash 空闲:缓存命中 ¥0.05 / 输入 ¥1.5 / 输出 ¥4.5 每百万 token
LLM_COST_RATES_JSON={"deepseek-v4-flash":{"input_cents_per_mtok":"150","cached_input_cents_per_mtok":"5","output_cents_per_mtok":"450"},"deepseek-v4-pro":{"input_cents_per_mtok":"450","cached_input_cents_per_mtok":"15","output_cents_per_mtok":"1350"},"glm-4-flash":{"input_cents_per_mtok":"10","cached_input_cents_per_mtok":"10","output_cents_per_mtok":"10"}}
# flash 高峰:缓存命中 ¥0.10 / 输入 ¥3 / 输出 ¥9 每百万 token
LLM_COST_RATES_PEAK_JSON={"deepseek-v4-flash":{"input_cents_per_mtok":"300","cached_input_cents_per_mtok":"10","output_cents_per_mtok":"900"},"deepseek-v4-pro":{"input_cents_per_mtok":"900","cached_input_cents_per_mtok":"30","output_cents_per_mtok":"2700"},"glm-4-flash":{"input_cents_per_mtok":"10","cached_input_cents_per_mtok":"10","output_cents_per_mtok":"10"}}
# 未登记的模型按这个价算。刻意取高档:宁可高估触发熔断,也不要低估成本形成盲区
LLM_DEFAULT_COST_RATE_JSON={"input_cents_per_mtok":"300","cached_input_cents_per_mtok":"10","output_cents_per_mtok":"900"}
LLM_PEAK_PRICING_ENABLED=true
# ── 全局日成本熔断(NFR-COST-02)─────────────────────────────────────────
# 按用户配额封不住总成本(总成本 = 用户数 × 人均,用户数无上限),
# 真正封顶的只有这个全局开关。
# 100000 分 = ¥1000/天,是按 8-17 之前旧单价推的占位,不是验收值。
# 峰谷生效后须用实测 token 分布重算(T-P1-25b),否则熔断会在错误位置触发。
LLM_DAILY_BUDGET_ENABLED=true
LLM_DAILY_BUDGET_CENTS=100000
LLM_BUDGET_TIMEZONE_OFFSET_HOURS=8
# ── 可观测性 ──────────────────────────────────────────────────────────────
OTEL_ENABLED=false
OTEL_SERVICE_NAME=meida-api
OTEL_EXPORTER_OTLP_ENDPOINT=
PROMETHEUS_ENABLED=true
# ── 四档分级降级(T-P1-25b,架构 §13.3)──────────────────────────────────
# JSON 为列表;blocked 支持 "*";allow 仅 block_all 档有意义(纯文本对话放行)。
# 留空则使用内置默认四档(与下面示例一致)。
LLM_BUDGET_TIERS_JSON=[{"threshold":0.6,"name":"warn","blocked":[]},{"threshold":0.8,"name":"degrade_secondary","blocked":["hd_export","report_regen"]},{"threshold":0.9,"name":"degrade_generation","blocked":["hairstyle","tryon"]},{"threshold":1.0,"name":"block_all","blocked":"*","allow":["agent_chat"]}]
# ── 会话(FR-ACC-05)───────────────────────────────────────────────────────
REFRESH_TOKEN_TTL_SECONDS=2592000
# ── 任务奖励(FR-BILL-03/04)───────────────────────────────────────────────
# 产品方案 §3.2 定稿目录(2026-09-11):签到+10 对话/日、反馈+2 发型/日、
# 分享+3 换装/日、首次速测+1 重生成/终身。invite_friend/profile_complete 下架
# (无可信完成信号,不下发即下架,claim 返回 40410)。
# claim_period 决定幂等粒度(同一任务同周期只能领一次)。
TASK_BONUS_RULES_JSON={"sign_in":{"quota_key":"agent_chat","amount":10,"claim_period":"day"},"report_feedback":{"quota_key":"hairstyle","amount":2,"claim_period":"day"},"share_report":{"quota_key":"tryon","amount":3,"claim_period":"day"},"first_report":{"quota_key":"report_regen","amount":1,"claim_period":"lifetime"}}
# ── 客户端埋点(FR-SYS-06)─────────────────────────────────────────────────
EVENTS_MAX_BATCH=50
EVENTS_PROPS_MAX_BYTES=8192
# ── 腾讯云 COS 直传(FR-SYS-03 / T-P1-27)──────────────────────────────────
# 密钥只在此文件对应的真实 .env 里出现;四桶按《前置开通清单》创建后填名称。
COS_REGION=ap-shanghai
COS_SECRET_ID=
COS_SECRET_KEY=
COS_BUCKET_TEMP=meida-temp
COS_BUCKET_USER=meida-user
COS_BUCKET_KB=meida-kb
COS_BUCKET_REPORT=meida-report
COS_PRESIGN_EXPIRE_SECONDS=600
UPLOAD_MAX_BYTES=20971520
UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/webp
UPLOAD_SCENES_JSON={"report_photo":{"bucket":"temp"},"avatar":{"bucket":"temp"}}
# ── 知识库与检索(架构 §8.3,TD-08)────────────────────────────────────────
# 当前 embedding 为确定性假实现(dev/test);真实 BGE-M3 ONNX 接入时只换这里。
KB_VECTOR_DIMENSIONS=1024
KB_EMBEDDING_MODEL=deterministic-v1
KB_EMBEDDING_DEVICE=cpu
RETRIEVAL_TOP_K=50
RETRIEVAL_RERANK_WEIGHTS_JSON={"vector":0.45,"keyword":0.2,"rule":0.25,"freshness":0.1}
# ── 华为账号登录(FR-ACC-01;真实联调待 AGC 配置,见《前置开通清单》§3)────
HUAWEI_JWKS_URL=https://oauth-login.cloud.huawei.com/oauth2/v3/certs
HUAWEI_AUDIENCE=
HUAWEI_ISSUER=https://accounts.huawei.com
HUAWEI_JWKS_CACHE_SECONDS=86400
# ── 手机号验证码登录(FR-ACC-04/14,TD-32)─────────────────────────────────
# fake = 验证码打日志(dev);tencent = 真实发送(签名审核通过后切换并填下方凭证)
SMS_PROVIDER=fake
TENCENT_SMS_SECRET_ID=
TENCENT_SMS_SECRET_KEY=
TENCENT_SMS_SDK_APP_ID=
TENCENT_SMS_SIGN_NAME=
TENCENT_SMS_TEMPLATE_ID=
SMS_CODE_TTL_SECONDS=300
SMS_CODE_LENGTH=6
SMS_VERIFY_MAX_ATTEMPTS=5
SMS_RESEND_COOLDOWN_SECONDS=60
SMS_HOURLY_LIMIT=5
SMS_DAILY_LIMIT=10
SMS_IP_DAILY_LIMIT=20
SMS_DEVICE_DAILY_LIMIT=10
# ── 极简运营后台(T-P2-09)────────────────────────────────────────────────
# 空字符串 = 后台全部端点 404。令牌只进 .env / SSM,绝不进代码。
ADMIN_TOKEN=
# ── 检索结果缓存(T-P2-10)────────────────────────────────────────────────
RETRIEVAL_CACHE_ENABLED=true
RETRIEVAL_CACHE_TTL_SECONDS=60
RETRIEVAL_CACHE_MAX_ENTRIES=512
# ── 美学报告流水线(T-P3,第四批)──────────────────────────────────────────
# 阶段 dotted path 列表(importlib 动态装配;缺失即 CONFIG_ERROR)
REPORT_PIPELINE_STAGES_JSON=["packages.report.vision_stage:VisionStage","packages.report.diagnosis_stage:DiagnosisStage","packages.report.copy_stage:CopyStage","packages.report.validation_stage:ValidationStage"]
# deterministic = V1 骨架假实现;真实 RTMPose/BiRefNet 为 Sprint 1 结论后的替换项
REPORT_VISION_PROVIDER=deterministic
REPORT_TIER=basic