Files
meida_backend/packages/domain
xsl f46ecc5eb0 fix(llm): price by call time and stop paying for thinking tokens
Two defects that only surface against the real DeepSeek API, both of which
break the cost model rather than the code:

deepseek-v4-flash enables thinking mode by default. The same single-turn
question took 13.5s with it on and 1.8s with it off, and the thinking tokens
are billed at the output rate. That misses NFR-PERF-02b (15s per turn) and
inflates every conversational call. SceneConfig now carries an explicit
`thinking` flag, all four V1 scenes set it to false, and the provider always
sends the parameter rather than relying on the upstream default. Reasoning
tokens are recorded separately in Usage.tokens_reasoning so the cost of ever
turning it back on is visible.

DeepSeek moves to peak/off-peak pricing on 2026-08-17, with peak (Beijing
09:00-12:00 and 14:00-18:00) at twice the off-peak rate. Reports are generated
mostly during peak hours, so a single blended rate would leave ob_ai_call --
the only input to V2 pricing -- off by nearly a factor of two.
PeakAwareRateTable resolves the rate at call time; cost is computed and stored
then, never recomputed later at whatever the current rate happens to be.

Also here:
- Replace the live API key in the redaction test with a synthetic one. The
  test that proves we redact secrets was itself a secret leak waiting for its
  first commit.
- Correct LLM_COST_RATES_JSON, which held placeholder rates.
- Make ids.SnowflakeGenerator take an injectable clock so rollover and
  clock-rollback are testable without patching a slots class.
- Rename exceptions to the -Error suffix (N818) and clear the remaining ruff,
  mypy and import-linter findings, including two dead lines: a duplicate
  pytestmark assignment and a no-op assert on a None return.

Gates: ruff, mypy (96 files incl. tests), 4/4 import contracts,
detect-secrets, 166 unit + 3 integration tests, migration up/down round-trip.
2026-08-14 00:54:40 +08:00
..

packages/domain/ — 纯领域逻辑

架构 §3.2 硬性规则 2:领域逻辑放在这里,不依赖 FastAPI、不依赖数据库会话, 可纯单元测试。

能放什么

  • 数据类与值对象(配额状态、任务状态、诊断维度…)
  • 判定与计算(配额是否够、状态能否流转、置信度冲突消解、色差计算…)
  • 纯函数、不可变数据结构

绝对不能放什么

  • import fastapi / sqlalchemy / httpx / redis / celery
  • import packages.shared(连日志和配置都不行)
  • 任何 IO:读文件、发请求、查库、读环境变量
  • async def 里真的 await 了 IO 的东西

违反会被 import-linter 的 packages.domain is pure 契约在 CI 拦下。

为什么这么严

这是 AI 大量写代码时唯一还能快速验证正确性的地方 —— 没有 mock、没有 fixture、没有容器,几毫秒跑完几百个用例。一旦让 IO 渗进来,这个性质就没了, 后面所有业务规则的测试都会退化成集成测试。

mypy strict

本目录与 packages/llm/ 一起,是 mypy strict 模式覆盖的范围 (架构 §16.2)。所有函数必须有完整类型注解。

现有内容

模块 内容
ids.py 雪花 ID 生成(§6.1 原则 2)
errors.py 纯领域异常,适配层负责翻译成 AppError
billing/quota.py 配额三段式的算术与周期键(§7.4)
task/state.py tk_task 状态机(§6.2)