同义词随定义一起留在口径卡上

文档里叫「月活」,卡上指标叫「月度活跃买家」;同义词丢了,以后按名字找这份口径就永远对不上。
合并时带上(大小写与空白不算新说法),并让口径描述把它交出去。
This commit is contained in:
ThearchyHelios
2026-11-03 23:08:28 +08:00
parent 43a38e926c
commit 1cd9cab7b4
4 changed files with 31 additions and 2 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ from app.tools.datasource import QUERY_PREFIX
#: 指标只写口径卡配置里真有的这几个键
METRIC_KEYS = ("id", "name", "unit", "decimals", "format", "kind", "definition", "params", "external", "rule",
"assessment")
"assessment", "synonyms")
async def list_calibers(session: AsyncSession, *, workflow_id: str | None = None) -> list[dict[str, Any]]:
+16 -1
View File
@@ -536,7 +536,9 @@ def _find_card(card_metrics: list[dict], draft: Mapping[str, Any]) -> dict | Non
def merge_definition(card_metrics: list[dict], draft_metrics: list[dict]) -> tuple[list[dict], list[str]]:
"""把草稿的 definition 并进同名 / 同义词的口径卡指标;不改 expression / id。
只并 definition:草稿不知道取数,改写式子或 id 会破坏已有口径的算法。匹配不上的进 warnings,由人决定新建还是忽略。
只并 definition(和卡上还没有的同义词):草稿不知道取数,改写式子或 id 会破坏已有口径的算法。匹配不上的进
warnings,由人决定新建还是忽略。同义词要带下去——文档里的叫法就是以后按名字找这份口径时的另一种说法,丢了它,
「月活」和「月度活跃买家」就永远对不上。
"""
merged = copy.deepcopy(list(card_metrics))
warnings: list[str] = []
@@ -549,6 +551,19 @@ def merge_definition(card_metrics: list[dict], draft_metrics: list[dict]) -> tup
if target is None:
warnings.append(f"草稿指标「{name or '(未命名)'}」在口径卡上找不到同名或同义词指标,定义未合并")
continue
synonyms = draft.get("synonyms")
if isinstance(synonyms, list):
kept = [s.strip() for s in synonyms if isinstance(s, str) and s.strip()]
if kept:
have = target.get("synonyms")
have = [s for s in have if isinstance(s, str)] if isinstance(have, list) else []
# 大小写和空白不算新说法:卡上已有「GMV」时不再加一条「gmv」
known = {s.strip().casefold() for s in have}
added = [s for s in kept if s.casefold() not in known]
for s in added:
known.add(s.casefold())
if added:
target["synonyms"] = have + added
definition = draft.get("definition")
if not isinstance(definition, Mapping) or not definition:
warnings.append(f"草稿指标「{name}」没有可合并的定义说明")
+12
View File
@@ -131,3 +131,15 @@ async def test_an_external_only_card_is_not_promised_automatic_issuance(client):
assert made["composable"] is True
assert made["issuance"]["auto_issue"] is False
assert any("没有取数节点" in r for r in made["issuance"]["reasons"])
async def test_metric_synonyms_reach_the_descriptor(client):
"""文档带来的同义词要出现在机器可读描述里:按名字找口径时,「月活」和指标名一样是入口。"""
graph = S.caliber_graph()
for node in graph["nodes"]:
if node["type"] == "metrics":
node["data"]["config"]["metrics"][0]["synonyms"] = ["月活", "MAU"]
wf = await S.publish(graph=graph)
[made] = await descriptors(client, wf)
[metric] = made["metrics"]
assert metric["synonyms"] == ["月活", "MAU"]
+2
View File
@@ -177,6 +177,8 @@ def test_merge_definition_matches_by_synonym_and_keeps_expression():
assert card["id"] == "gmv"
assert card["expression"] == "cell(nodes.q, 0, '金额')"
assert card["definition"] == {"text": "新说明", "owner": "财务", "scope": "全渠道"}
# 文档里的叫法要留在卡上:以后按同义词找这份口径,「成交额」才对得上
assert card["synonyms"] == ["GMV"]
assert len(warnings) == 1 and "客单价" in warnings[0]