Files
AgentLab/backend/tests/test_analysis_claims_lexicon.py

155 lines
9.3 KiB
Python

"""说法表的样本表(engine/analysis_claims,细案 01 §9.1):常见的周报写法逐句过 scan,每句期望认出的类(按出现的先后,
否定翻过之后的)或「不核」(空)。以后改说法表都过这张表:误报(正常的句子被打回)让报告多重写一次、strict 下降档,
漏报只是少核一处,宁可漏报不误报。
前半是第 1 波复核时的 70 句(复核第 4 节),后半是这次调整顺带看清楚的写法。和复核标记不一样的只有一句:「退款主要集中在
生鲜品类」复核标的是判断,可它说的是退款的构成、没说变化,按「来源、集中只在说了变化时才核」不核。
"""
from __future__ import annotations
import pytest
from app.engine import analysis_claims as ac
#: 不核
NO: tuple[str, ...] = ()
SAMPLES: list[tuple[str, tuple[str, ...]]] = [
# ---- 陈述数字和变化:不核 ----
("本周销售额 12.3 万元,环比下降 5.2%。", NO),
("整体平稳,各品类变化不大。", NO),
("退款率略有下降。", NO),
("订单数明显回升,客单价基本持平。", NO), # 「明显」只是程度,不收「明显 + 涨跌」
("环比基本持平。", NO),
("本周数据只到 10 月 16 日,周数据不完整。", NO),
("客单价相当于上周的 1.1 倍。", NO),
("订单量与上周相当。", NO), # 比上期:说的是持平,不是两组差异
# ---- 判断:核 ----
("销售额同比增长 8.1%,主要是因为国庆假期客流增加。", ("causal",)),
("受节假日影响,线下门店客流明显回升。", ("causal",)),
("华东销售额大幅增长 32%。", ("anomaly",)),
("线上渠道拉动整体增长。", ("source",)),
("华东下滑拖累了整体表现。", ("source",)),
("各地区普遍上涨。", ("dispersed",)),
("已连续三周下降。", ("trend",)),
("西南地区退款率显著高于其他地区。", ("differ",)),
("两个渠道的转化率相当。", ("no_diff",)),
("促销效果显著,带动订单量增长。", ("source",)),
("由于华东仓库搬迁,发货延迟导致退款增加。", ("causal", "causal")),
("线上销售额持续增长,已连续 5 周上升。", ("trend", "trend")),
("退款金额骤增,需重点关注。", ("anomaly",)), # 「需」在后一个分句,不管前面的判断
("新客贡献最大。", ("source",)),
("这主要是由于华东仓库搬迁。", ("causal",)),
("整体来看,各渠道均有所下降。", ("dispersed",)),
("销量下降主要由促销结束造成。", ("source", "causal")),
("下降主要来自客流减少。", ("source",)),
("退款主要集中在生鲜品类。", NO), # 复核标的是判断;说的是构成、没说变化,不核
# ---- 业务上的「异常」、数据质量:不核 ----
("本周异常订单 35 单,已全部处理。", NO),
("部分门店数据异常,已提交核查。", NO),
("部分订单异常,已退款。", NO),
("物流异常的订单已全部退款。", NO),
("支付异常的订单已全部退款。", NO),
("门店营业异常 2 家。", NO),
("整体运行平稳,未见异常。", NO), # 主语不是指标、波动
("系统运行正常,无异常。", NO),
# ---- 说的是构成,不是变化从哪来:不核 ----
("本周订单主要来自线上渠道,占 65%。", NO),
("销售额集中在周末两天。", NO),
("华东门店集中在上海、杭州。", NO),
("客户分散在各个城市。", NO),
("线上渠道占主导。", NO),
# ---- 建议、计划、预测:不核 ----
("下一步将全面提升门店服务水平。", NO),
("建议加大投放,拉动新客增长。", NO), # 「建议」管到后面的分句
("通过会员活动带动复购。", NO), # 带动的不是变化
("下周将持续提升转化率。", NO),
("建议持续降低退款率。", NO),
("计划逐步提高线上占比。", NO),
("下一步将大幅提升配送效率。", NO),
("下周重点主要是提升转化率。", NO),
("避免缺货导致客诉。", NO),
("预计下周将继续保持增长态势。", NO),
# ---- 别的误报:不核 ----
("订单来源于线上渠道。", NO), # 「来源于」不是「源于」
("数据来源于业务系统,金额含税。", NO),
("本周数据源表结构变化,部分字段暂缺。", NO), # 表结构不是结构变化
# ---- 漏报,补上:核 ----
("华东是本周下降的主要原因。", ("causal",)), # 原因点名的是拆解的段时按来源核
("近七成的降幅来自华东。", ("source",)),
("超过一半的下降来自华东。", ("source",)),
("增长主要靠线上。", ("source",)),
("降幅主要在华东。", ("source",)),
("下降主要体现在华东。", ("source",)),
("增长以线上为主。", ("source",)),
("销售额创年内新高。", ("record",)),
("销售额创近一年新高。", ("record",)),
("整体趋势向好。", ("trend",)),
("本周销售额波动较大。", ("anomaly",)),
("订单量三连降。", ("trend",)),
("退款率显著下降。", ("anomaly",)),
("销售额远高于往常。", ("anomaly",)),
("本周销售额出现异动。", ("anomaly",)),
("华东和华南一涨一跌。", ("offset",)),
# ---- 这次调整顺带看清楚的写法 ----
("订单大幅减少。", ("anomaly",)), # 「订单」只在「订单异常」里是业务状态
("不少门店大幅下滑。", ("anomaly",)),
("订单异常下降。", ("anomaly",)), # 「异常」后面跟着涨跌:说的是指标
("华东线上订单出现异常的下降。", ("anomaly",)),
("异常的门店已全部整改。", NO),
("两个渠道的转化率与上周相当。", NO),
("华东和华南的退款率水平相当。", ("no_diff",)),
("销售额并未出现明显异常。", ("normal",)),
("本周没有出现异常。", NO),
("退款率并未显著下降。", ("normal",)),
("下降主要来自华东,建议加大华东投放。", ("source",)), # 「建议」在后一个分句
("销售额\x00\x00,环比\x00\x00,主要来自华东。", ("source",)), # 引用渲染的数涂成 \x00,「环比」说的是变化
("订单主要来自线上渠道,占比较上周提升 5 个百分点。", NO), # 变化在后一个分句:前面说的是构成
("新增客户主要来自线上渠道。", NO),
("增量主要来自线上。", ("source",)),
("下降主要源于华东。", ("source",)),
("订单以线上为主。", NO),
("线上渠道表现突出,贡献最大。", ("source",)),
("下降的主要原因是华东门店客流减少。", ("causal",)),
("受促销结束影响,华东销售额下降。", ("causal",)),
("近 8 周销售额持续下降,已连续三周下降。", ("trend", "trend")),
("线上销售额十二连涨。", ("trend",)),
("整体走势向下。", ("trend",)),
("销售额创下了近一年来新高。", ("record",)),
("创新能力最高的门店是华东店。", NO),
("预计下周销售额将创新高。", NO),
]
@pytest.mark.parametrize("text, expected", SAMPLES, ids=[t for t, _ in SAMPLES])
def test_the_sample_table(text, expected):
found = ac.scan(text)
assert tuple(h.claim for h in found) == expected, [(h.claim, h.word) for h in found]
@pytest.mark.parametrize("text, expected", SAMPLES, ids=[t for t, _ in SAMPLES])
def test_a_sentence_is_reported_without_conclusions_exactly_when_it_carries_a_judgement(text, expected):
"""没有分析结论可挂时:认出判断的句子至少报一处(没挂、原因没依据),不核的一处都不报。"""
assert bool(ac.check_sentence(ac.Sentence(text=text), {})) == bool(expected)
def test_the_sample_table_is_not_shorter_than_the_review():
assert len(SAMPLES) >= 70 and len({t for t, _ in SAMPLES}) == len(SAMPLES)
@pytest.mark.parametrize("text, count", [("已连续三周下降。", 3), ("线上销售额持续增长,已连续 5 周上升。", 5),
("线上销售额十二连涨。", 12), ("连续两个月下滑。", 2), ("连续多周上升。", None),
("订单量三连降。", 3), ("连续10周上升。", 10)])
def test_runs_carry_the_number_of_periods_they_claim(text, count):
[run] = [h for h in ac.scan(text) if h.run]
assert run.count == count
@pytest.mark.parametrize("label, claims", [("主要来自华东的销售额", ["source"]), ("集中在线上的订单", ["source"]),
("建议持续降低的退款率", ["trend"]), ("部分订单异常", ["anomaly"]),
("销售额", [])])
def test_bare_scanning_finds_judgement_words_in_a_name(label, claims):
"""认叫法里夹带的判断词(bare=True)只按词认:叫法是一个名字不是一句话,构成、建议计划、业务状态这些整句话的规矩不用。"""
assert [h.claim for h in ac.scan(label, bare=True)] == claims
assert ac.scan(label) == []