1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""「单据账户标识 → 平台交易账号」换算(`OffsiteFundService._platform_account`)的回归测试。
|
||
|
||
## 为什么必须守住这条缝
|
||
|
||
场外核对的 NL2SQL 查询计划按 `fin_holding.trade_account = <单据上的账户标识>` 过滤
|
||
(`nl2sql_yc.py:509-514`),而库里存的是平台交易账号 `FSA{客户号:06d}`。两者**不同值**时:
|
||
|
||
```
|
||
JOIN fin_holding h ... WHERE h.trade_account = '10002' → 0 行
|
||
→ 「申请前持有份额」= null → 规则判「无法判断」→ 单据状态 query_failed
|
||
```
|
||
|
||
2026-09-14 实测就是这样:客户 10002 **明明持有 15911 共 1000 份**,核对却报"查询无可用数据",
|
||
页面上"单据核对与运营动作"因此是空的 —— 看起来像"缺数据",实为**账户口径没桥接**。
|
||
本文件把换算规则钉死:命中就换算,认不出来就**原样返回(失败关闭,不猜不造)**。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
import pytest
|
||
|
||
from app.service.offsite_fund_service import OffsiteFundService
|
||
|
||
|
||
class _StubSession:
|
||
"""按调用顺序返回预置结果的 session 替身;记录被执行的查询,不发真实 SQL。"""
|
||
|
||
def __init__(self, results: list[Any]) -> None:
|
||
self._results = list(results)
|
||
self.queries: list[str] = []
|
||
|
||
async def scalar(self, statement: Any) -> Any:
|
||
self.queries.append(str(statement))
|
||
return self._results.pop(0) if self._results else None
|
||
|
||
|
||
def _service(results: list[Any]) -> tuple[OffsiteFundService, _StubSession]:
|
||
session = _StubSession(results)
|
||
return OffsiteFundService(session), session # type: ignore[arg-type]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_account_no_is_used_as_is() -> None:
|
||
"""已经是库里的交易账号:原样使用,且**只查一次**(不需要按客户号回退)。"""
|
||
service, session = _service(["FSA010002"])
|
||
|
||
assert await service._platform_account("FSA010002") == "FSA010002"
|
||
assert len(session.queries) == 1
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_numeric_customer_id_is_translated_to_trade_account() -> None:
|
||
"""单据上印的是客户号(演示单据就是 `10002`)⇒ 换算成该客户的交易账号。"""
|
||
service, session = _service([None, "FSA010002"])
|
||
|
||
assert await service._platform_account("10002") == "FSA010002"
|
||
assert len(session.queries) == 2 # 先按 account_no 找、再按 customer_id 找
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_unknown_identifier_is_returned_unchanged() -> None:
|
||
"""认不出来就原样返回:宁可继续查不到(暴露给人工),也不构造"看起来对"的账号。"""
|
||
service, _session = _service([None, None])
|
||
|
||
assert await service._platform_account("SL-UNKNOWN-001") == "SL-UNKNOWN-001"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_numeric_identifier_without_account_is_not_invented() -> None:
|
||
"""数字但是个不存在的客户号 ⇒ 不得凭空造出 `FSA099999` 这类账号。"""
|
||
service, _session = _service([None, None])
|
||
|
||
assert await service._platform_account("99999") == "99999"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_blank_identifier_needs_no_query() -> None:
|
||
"""空值不查库。"""
|
||
service, session = _service([])
|
||
|
||
assert await service._platform_account(None) is None
|
||
assert await service._platform_account("") is None
|
||
assert session.queries == []
|