"""逐个数标口径(docs/design/next-round/02-口径账本.md 做法 3):报告里每个引用了查询格、口径卡指标的数,来自哪一份口径。 标签四种(KINDS): - caliber:来自口径「X」vN。how 是怎么认的:independent「等价已证明」(这次 Agent 自己写的 SQL 被证明和口径的取数等价)、 composed「助手选用」(助手 use_caliber 按口径组合出来的取数,证明了和那一版一致:ledger/match 的 composed)。 - related:相关口径——同一个数据源、查了同样的表的已发布口径,但不等价;附差在哪(explain_sql_diff 的条目)。 - reasoning:这次的推理——没有相关的已发布口径。 - unknown:没比对、比对没做完(超时、解析失败)、快照读不出来。 怎么认: - 单元格([[v:]]、[[table:]] 里的格):按证据目录找到查询快照,取那条查询的比对状态;这条查询逐格比过(algo 4:整条按全部 引用列投影没证明时,被引用的每一列单独投影、和口径的每个指标列逐个比,ledger/match._cells)的,按这一格所在的那一列的 结论——同一条查询里一列等价、一列不等价各标各的。等价时按列映射写出口径那边的列名。 - 指标([[m:]]):指标集工件里这个指标的每个输入都要来自查询快照,而且这些快照都被证明等价于同一份口径的同一版、绑定一致; 指标式还要和那一版里某张口径卡上的某个指标相同——两边的式子把每个输入换成「口径的哪个取数节点、第几行、哪一列」之后 一字不差(caliber_from 转抄来的式子本来就是那一张卡的,同样按这个比)。输入有不来自查询的(运行输入、整形、代码)不算来自口径。 逐格比过的输入按它那一格的结论;同一条查询的好几格合起来用时,只在这条查询一定只出一行(one_row)时才按逐格的结论, 别的按整条查询:多行的结果里每一列各自对得上,不等于每一行的几列一起对得上。 整题(whole_of):主报告里所有带标签的数都来自同一口径的同一版、绑定一致,就记 equivalent,并写出还缺的入口字段(口径入口 节点声明了、这次的绑定里没有的;required 且没有缺省值的另列)。 分析结论里的数([[a:A3]]、[[a:A3.键]]、[[v:A3.r0.列]]、整表 [[table:A3]] 展开的格)不标:它们是从查询快照派生出来的, 不是口径(docs/design/analysis-round/00-总方案.md D11),不和口径比对、不进整题判断的计数,也不算成「没比对」。 这里只是纯函数:标签在比对时算好、存进比对工件(ledger/match.py);接口读的时候按这次运行查到过的遮罩值遮一遍(view_label): 绑定的值是遮罩值的不给值,差异条目里的 SQL 片段走证据面板同一套遮罩(api/evidence._Shown)。 """ from __future__ import annotations from collections.abc import Callable, Mapping from typing import Any from app.analysis.contract import LEDGER_KIND #: 标签的四种、认法的两种 KINDS = ("caliber", "related", "reasoning", "unknown") HOWS = ("independent", "composed") #: 界面上标签要写明的两句:比对在封存之后做,不在封存范围内;事后补比的是按现在的口径回看 POST_SEAL_NOTE = "封存后比对,不在封存范围内" RETRO_NOTE = "按现在的口径回看" #: 固化出的口径发布之后对来源运行补比的(store 的 trigger=certified) CERTIFIED_NOTE = "按之后发布的口径回看" #: 发布级别的叫法(同 blueprints/publish_plan.LEVEL_TEXT、前端 lib/terms.ts) LEVEL_TEXT = {"published": "已发布", "governed": "受管"} MASKED = "已遮罩" #: unknown 的几种原因 NO_RECORD = "这一格出自的查询没有比对记录" CARD_UNREADABLE = "指标集读不出来,没有比对" METRIC_GONE = "指标集里没有这个指标,没有比对" UNFINISHED = "比对没有做完" #: related 的几种说明 FORMULA_DIFFERS = "取数和口径等价,但指标的算法和口径卡上的不同" #: 指标按「同一行」认来自口径,可那一行重跑时不一定还是它:多行结果、查询又没有 ORDER BY(sql_semantics.row_stable) ROW_ORDER_UNSTABLE = ("指标取的是多行结果里按行号的一格,查询没有 ORDER BY,行序不定:重新执行时这一行可能是另一行," "不能认定对应口径的同一格") INPUTS_ELSEWHERE = "指标有的输入不来自查询(运行输入、整形或代码),不能算来自口径" MIXED_CALIBERS = "指标的几个输入分别对上了不同的口径" BINDING_CONFLICT = "指标的几个输入对上了同一份口径,但参数不一致" #: 能追到查询快照里那一格的输入(同 engine/lineage.SNAPSHOT_VIAS) _SNAPSHOT_VIAS = ("tool_cell", "agent_field") def caliber_key(workflow_id: str, version: int) -> str: """口径版本的写法「工作流 id@版本」:工件 meta、召回反查、统计都按它认。""" return f"{workflow_id}@{version}" def _unknown(reason: str) -> dict[str, Any]: return {"kind": "unknown", "reason": reason} def _related(query: Mapping[str, Any], *, reason: str | None = None) -> dict[str, Any]: """相关口径:这条查询比过、没等价的那几份(附差在哪)。一份相关的都没有时是这次的推理。""" related = [{k: cand[k] for k in ("caliber", "workflow_id", "version", "level", "node_id", "comparable", "reason", "diff", "binding", "via") if k in cand} for cand in query.get("candidates") or [] if cand.get("status") == "related"] if not related: return {"kind": "reasoning"} out: dict[str, Any] = {"kind": "related", "related": related} if reason: out["reason"] = reason return out def _caliber(cand: Mapping[str, Any], **extra: Any) -> dict[str, Any]: return {"kind": "caliber", "how": "composed" if cand.get("how") == "composed" else "independent", "caliber": cand.get("caliber"), "workflow_id": cand.get("workflow_id"), "version": cand.get("version"), "level": cand.get("level"), "binding": dict(cand.get("binding") or {}), **extra} def _proven(query: Mapping[str, Any]) -> list[Mapping[str, Any]]: return [c for c in query.get("candidates") or [] if c.get("status") == "proven"] def _caliber_column(cand: Mapping[str, Any], column: Any) -> str | None: """这次结果里的列 → 口径那边同一位置的列名(列映射,按位置);没有映射、对不上时为 None。""" for item in cand.get("columns") or []: if item.get("ours") == column: return item.get("caliber") return None def cells_of(query: Mapping[str, Any]) -> list[Mapping[str, Any]]: """这条查询逐格比出来的几格(algo 4 的 cells,见 ledger/match._cells);老工件、没逐格比的为空。""" cells = query.get("cells") return [c for c in cells.values() if isinstance(c, Mapping)] if isinstance(cells, Mapping) else [] def candidates_of(query: Mapping[str, Any], *, shown: bool = False) -> list[Mapping[str, Any]]: """一条查询的候选:整条比的,加上逐格比出来的。召回反查、使用次数、工件 meta 都要算上逐格证明了的那几份口径。shown 为真 时只要标签上给人看的那一份:逐格比过的只要逐格的(整条的那一份不在标签上),别的照旧是整条的(统计兜底说明的比例用)。""" cells = [c for cell in cells_of(query) for c in cell.get("candidates") or [] if isinstance(c, Mapping)] whole = [c for c in query.get("candidates") or [] if isinstance(c, Mapping)] if shown: return cells or whole return whole + cells def _view(query: Mapping[str, Any], column: Any, *, alone: bool = True) -> Mapping[str, Any]: """这一格按什么判:这条查询逐格比过这一列(cells 里有)的按那一格的结论,别的按整条查询的。alone 为假(一个指标用了 这条查询的好几格)时,只有这条查询一定只出一行(one_row)才按逐格的(见模块说明)。老工件没有 cells,照旧按整条。""" cells = query.get("cells") cell = cells.get(column) if isinstance(cells, Mapping) and isinstance(column, str) else None if isinstance(cell, Mapping) and (alone or query.get("one_row") is True): return cell return query def cell_label(artifact: str, column: Any, queries: Mapping[str, Mapping[str, Any]]) -> dict[str, Any]: """单元格的标签:它所在的那条查询的比对状态(逐格比过的按这一格,_view)。""" found = queries.get(artifact) if found is None: return _unknown(NO_RECORD) query = _view(found, column) status = query.get("status") if status in ("proven", "composed"): cand = _proven(query)[0] if _proven(query) else {} label = _caliber(cand, query=artifact, node_id=cand.get("node_id"), column=_caliber_column(cand, column) if column is not None else None) if status == "composed": label["how"] = "composed" return label if status == "related": return {**_related(query), "query": artifact} if status == "none": return {"kind": "reasoning", "query": artifact} return {**_unknown(str(query.get("reason") or UNFINISHED)), "query": artifact} def _row_stable(query: Mapping[str, Any], item: Mapping[str, Any]) -> bool: """这个输入取的那一行重跑时还认得住(match 记的 ordered / one_row,sql_semantics.row_stable 的认法):最外层带 ORDER BY, 或者一定只出一行、取的是第 0 行。老的比对记录没记 ordered 的照旧(当时不判)。""" if "ordered" not in query: return True where = item.get("locator") if isinstance(item.get("locator"), dict) else {} return bool(query.get("ordered")) or (where.get("row") in (0, None) and bool(query.get("one_row"))) def _leaf_token(node_id: Any, row: Any, column: Any) -> str: """把指标式里的一个输入换成「口径的哪个取数节点、第几行、哪一列」:两边的式子按这个换完再逐字比。""" return f"⟦{node_id}|{row}|{column}⟧" def _mapped_expression(expression: str, tokens: Mapping[str, str]) -> str | None: """指标式里的输入按 tokens(输入路径 → 记号)换掉之后的写法;解析不了、有输入没换到的为 None。""" from app.engine.expressions import ExpressionError, leaf_refs, parse_expression, substitute try: tree = parse_expression(expression)[0] except ExpressionError: return None leaves = leaf_refs(tree) if not leaves or any(leaf not in tokens for leaf in leaves): return None return substitute(tree, dict(tokens)) def card_expressions(cards: list[Mapping[str, Any]]) -> list[tuple[str, str, str]]: """口径版本里每张源头口径卡的每个指标:(卡的节点 id, 指标 id, 换成记号后的式子)。卡上的输入要是 cell(nodes.X, 行, '列') (look/calibers.card_inputs 认的那种),别的写法的指标不收。""" import ast from app.engine.expressions import ExpressionError, cell_parts, leaf_refs, parse_expression out: list[tuple[str, str, str]] = [] for card in cards: for metric in card.get("metrics") or []: expression = str((metric or {}).get("expression") or "") try: tree = parse_expression(expression)[0] except ExpressionError: continue tokens: dict[str, str] = {} for leaf in leaf_refs(tree): parts = cell_parts(leaf) if parts is None or not parts[0].startswith("nodes."): break try: row, column = ast.literal_eval(parts[1]), ast.literal_eval(parts[2]) except (ValueError, SyntaxError): break tokens[leaf] = _leaf_token(parts[0][len("nodes."):], row, column) else: if (mapped := _mapped_expression(expression, tokens)) is not None: out.append((str(card.get("node_id")), str(metric.get("id")), mapped)) return out def card_reads(cards: list[Mapping[str, Any]]) -> list[tuple[str, str, tuple[tuple[str, str], ...]]]: """口径版本里每张源头口径卡的每个指标读了哪几格的列:(卡的节点 id, 指标 id, ((取数节点, 列), …)),按式子里出现的先后、 去重。只认 cell(nodes.X, 行, '列') 这种输入(同 card_expressions);有别的写法的指标不收。比对时口径一侧按它投影到 「这个指标引用的列」(ledger/match 的 _projected)。""" import ast from app.engine.expressions import ExpressionError, cell_parts, leaf_refs, parse_expression out: list[tuple[str, str, tuple[tuple[str, str], ...]]] = [] for card in cards: for metric in card.get("metrics") or []: try: tree = parse_expression(str((metric or {}).get("expression") or ""))[0] except ExpressionError: continue reads: list[tuple[str, str]] = [] for leaf in leaf_refs(tree): parts = cell_parts(leaf) if parts is None or not parts[0].startswith("nodes."): break try: column = ast.literal_eval(parts[2]) except (ValueError, SyntaxError): break if not isinstance(column, str): break read = (parts[0][len("nodes."):], column) if read not in reads: reads.append(read) else: if reads: out.append((str(card.get("node_id")), str(metric.get("id")), tuple(reads))) return out def referenced_columns(docs: list[Mapping[str, Any]], load_card: Callable[[str], Mapping[str, Any] | None]) -> dict[str, set[str]]: """报告里引用了哪条查询的哪几列:{查询快照: {列名}}。和 label_doc 标的是同一批数——单元格([[v:]]、[[table:]] 里的格) 按证据目录找到的快照和列;指标([[m:]])按指标集里这个指标的输入(落在查询快照一格上的)。比对时按它投影:只比被 引用的那几列(ledger/match 的 _projected)。""" from app.engine.evidence import iter_units out: dict[str, set[str]] = {} cards: dict[str, Mapping[str, Any] | None] = {} for doc in docs: catalog = doc.get("catalog") if isinstance(doc.get("catalog"), dict) else {} for _, unit in iter_units(doc): for seg in unit.get("segments") or []: if not isinstance(seg, dict) or seg.get("kind") not in ("number", "value") or not seg.get("id"): continue cite = seg.get("cite") if isinstance(seg.get("cite"), dict) else {} if cite.get("status") != "resolved": continue entry = catalog.get(cite.get("alias")) if isinstance(catalog.get(cite.get("alias")), dict) else {} if entry.get("kind") == LEDGER_KIND: continue # 分析结论的结果表不是查询,不比对 locator = cite.get("locator") if isinstance(cite.get("locator"), dict) else {} artifact = str(entry.get("artifact") or "") if cite.get("kind") == "cell" and artifact and isinstance(locator.get("column"), str): out.setdefault(artifact, set()).add(locator["column"]) elif cite.get("kind") == "metric" and artifact: if artifact not in cards: cards[artifact] = load_card(artifact) card = cards[artifact] or {} metric = next((m for m in card.get("metrics") or [] if isinstance(m, dict) and m.get("id") == locator.get("metric")), None) for item in (metric or {}).get("inputs") or []: where = item.get("locator") if isinstance(item, dict) else None if isinstance(item, dict) and item.get("artifact") and item.get("via") in _SNAPSHOT_VIAS \ and isinstance(where, dict) and isinstance(where.get("column"), str): out.setdefault(str(item["artifact"]), set()).add(where["column"]) return out def metric_label(card: Mapping[str, Any] | None, metric_id: Any, queries: Mapping[str, Mapping[str, Any]], calibers: Mapping[str, Mapping[str, Any]]) -> dict[str, Any]: """指标的标签(见模块说明)。card 是封存的指标集工件(读不出为 None);calibers 是 {「工作流@版本」: {level, cards}}, cards 是那一版里源头口径卡的指标式(match 从版本快照取)。""" if card is None: return _unknown(CARD_UNREADABLE) metric = next((m for m in card.get("metrics") or [] if isinstance(m, dict) and m.get("id") == metric_id), None) if metric is None: return _unknown(METRIC_GONE) inputs = [i for i in metric.get("inputs") or [] if isinstance(i, dict)] from_queries = [i for i in inputs if i.get("artifact") and i.get("via") in _SNAPSHOT_VIAS] if not from_queries: return {"kind": "reasoning"} rows = [queries.get(str(i["artifact"])) for i in from_queries] if any(q is None for q in rows): return _unknown(NO_RECORD) # 每个输入按它那一格判(_view):同一条查询用了好几格的,那条查询一定只出一行才按逐格的结论 used: dict[str, set[Any]] = {} for item in from_queries: where = item.get("locator") if isinstance(item.get("locator"), dict) else {} used.setdefault(str(item["artifact"]), set()).add(where.get("column")) found: list[Mapping[str, Any] | None] = [ _view(q, (i.get("locator") if isinstance(i.get("locator"), dict) else {}).get("column"), alone=len(used[str(i["artifact"])]) == 1) if q is not None else None for q, i in zip(rows, from_queries)] if any(q.get("status") == "skipped" for q in found): # type: ignore[union-attr] return _unknown(next(str(q.get("reason") or UNFINISHED) for q in found if q.get("status") == "skipped")) # type: ignore[union-attr] queries_found: list[Mapping[str, Any]] = [q for q in found if q is not None] proven_keys = [{str(c.get("caliber")) for c in _proven(q)} for q in queries_found] common = set.intersection(*proven_keys) if proven_keys else set() nearby = _brief([c for q in queries_found for c in q.get("candidates") or [] if c.get("status") in ("proven", "related")]) queried = {"queries": [str(i["artifact"]) for i in from_queries]} if len(from_queries) < len(inputs): return {"kind": "related", "related": nearby, "reason": INPUTS_ELSEWHERE, **queried} if nearby \ else {"kind": "reasoning", **queried} if not common: if all(proven_keys): return {"kind": "related", "related": nearby, "reason": MIXED_CALIBERS, **queried} related = next((q for q in queries_found if q.get("status") == "related"), None) return {**_related(related), **queried} if related is not None else {"kind": "reasoning", **queried} if unstable := [i for i, q in zip(from_queries, queries_found) if not _row_stable(q, i)]: # 按「同一行」认来自口径(_leaf_token 记的是行号):多行结果又没有 ORDER BY 的,重跑时第几行不一定是哪一行, # 照实标相关、写明行序不定,不认成来自口径 return {"kind": "related", "related": nearby, "reason": ROW_ORDER_UNSTABLE, **queried} reason = FORMULA_DIFFERS for key in sorted(common): chosen = [next(c for c in _proven(q) if c.get("caliber") == key) for q in queries_found] binding: dict[str, Any] = {} conflict = False for cand in chosen: for name, value in (cand.get("binding") or {}).items(): if name in binding and binding[name] != value: conflict = True binding.setdefault(name, value) if conflict: reason = BINDING_CONFLICT continue tokens: dict[str, str] = {} for item, cand in zip(from_queries, chosen): locator = item.get("locator") if isinstance(item.get("locator"), dict) else {} mapped = _caliber_column(cand, locator.get("column")) if mapped is None or item.get("path") is None: tokens = {} break tokens[str(item["path"])] = _leaf_token(cand.get("node_id"), locator.get("row"), mapped) mine = _mapped_expression(str(metric.get("expression") or ""), tokens) if tokens else None if mine is None: continue hit = next(((node, mid) for node, mid, theirs in (calibers.get(key) or {}).get("expressions") or [] if theirs == mine), None) if hit is None: continue label = {**_caliber(chosen[0], card=hit[0], metric=hit[1]), "binding": binding, "queries": [str(i["artifact"]) for i in from_queries]} if any(c.get("how") == "composed" for c in chosen): # 几个输入里有一个是助手选用的,整个指标就按助手选用说(不能说成都是独立写出来的) label["how"] = "composed" return label return {"kind": "related", "related": _brief([c for q in queries_found for c in _proven(q) if c.get("caliber") in common]), "reason": reason, **queried} def _brief(cands: list[Mapping[str, Any]]) -> list[dict[str, Any]]: """指标上的相关口径:只写是哪一版、哪个取数节点(差在哪在各个输入的查询上),同一版只写一次,最多 5 个。""" out: list[dict[str, Any]] = [] for cand in cands: if any(o.get("caliber") == cand.get("caliber") for o in out): continue out.append({k: cand[k] for k in ("caliber", "workflow_id", "version", "level", "node_id") if k in cand}) return out[:5] def label_doc(doc: Mapping[str, Any], queries: Mapping[str, Mapping[str, Any]], calibers: Mapping[str, Mapping[str, Any]], load_card: Callable[[str], Mapping[str, Any] | None]) -> dict[str, dict[str, Any]]: """一份报告文档里每个数的标签:{片段 id: 标签}。只标引用解析了的数字、取值片段(单元格、指标);运行输入、表名、引文、 没有引用的数不标,分析结论里的数(具名标量、结果表的格)也不标(D11:派生的数,不是口径)。""" from app.engine.evidence import iter_units catalog = doc.get("catalog") if isinstance(doc.get("catalog"), dict) else {} cards: dict[str, Mapping[str, Any] | None] = {} out: dict[str, dict[str, Any]] = {} for _, unit in iter_units(doc): for seg in unit.get("segments") or []: if not isinstance(seg, dict) or seg.get("kind") not in ("number", "value") or not seg.get("id"): continue cite = seg.get("cite") if isinstance(seg.get("cite"), dict) else {} if cite.get("status") != "resolved": continue entry = catalog.get(cite.get("alias")) if isinstance(catalog.get(cite.get("alias")), dict) else {} if entry.get("kind") == LEDGER_KIND: continue locator = cite.get("locator") if isinstance(cite.get("locator"), dict) else {} artifact = str(entry.get("artifact") or "") if cite.get("kind") == "cell" and artifact: out[str(seg["id"])] = cell_label(artifact, locator.get("column"), queries) elif cite.get("kind") == "metric" and artifact: if artifact not in cards: cards[artifact] = load_card(artifact) out[str(seg["id"])] = metric_label(cards[artifact], locator.get("metric"), queries, calibers) return out def whole_of(labels: Mapping[str, Mapping[str, Any]], calibers: Mapping[str, Mapping[str, Any]]) -> dict[str, Any]: """整题判断(主报告):所有带标签的数都来自同一口径的同一版、绑定一致时 equivalent,附还缺的入口字段。""" counts = {kind: 0 for kind in KINDS} for label in labels.values(): counts[str(label.get("kind"))] = counts.get(str(label.get("kind")), 0) + 1 if not labels: return {"equivalent": False, "reason": "报告里没有可标口径的数", "counts": counts} keys = {label.get("caliber") for label in labels.values() if label.get("kind") == "caliber"} if counts["caliber"] != len(labels): # 逐格判之后常见「一部分数来自口径、另几个只是相关」:照实说是不一致,不说成这次的推理 reason = ("有的数没有比对" if counts["unknown"] else "没有数来自口径" if not counts["caliber"] else "有的数和相关口径不一致,不来自口径" if counts["related"] else "有的数是这次的推理") return {"equivalent": False, "reason": reason, "counts": counts} if len(keys) != 1: return {"equivalent": False, "reason": "数来自不同的口径", "counts": counts} binding: dict[str, Any] = {} for label in labels.values(): for name, value in (label.get("binding") or {}).items(): if name in binding and binding[name] != value: return {"equivalent": False, "reason": "各个数的参数不一致", "counts": counts} binding.setdefault(name, value) key = str(next(iter(keys))) info = calibers.get(key) or {} fields = [f for f in info.get("fields") or [] if isinstance(f, dict) and f.get("name")] first = next(iter(labels.values())) return { "equivalent": True, "caliber": key, "workflow_id": first.get("workflow_id"), "version": first.get("version"), "level": first.get("level"), "how": "composed" if any(label.get("how") == "composed" for label in labels.values()) else "independent", "binding": binding, "missing": [f["name"] for f in fields if f["name"] not in binding], "missing_required": [f["name"] for f in fields if f["name"] not in binding and f.get("required") and not f.get("has_default")], "counts": counts, } # -------------------------------------------------------------------------- # 读的时候遮:绑定的值、差异条目里的 SQL 片段 # -------------------------------------------------------------------------- def binding_view(binding: Mapping[str, Any] | None, hide: Callable[[Any], bool]) -> list[dict[str, Any]]: """绑定 → [{field, value} 或 {field, masked: true}]:值是这次运行查到过的遮罩值的不给值(也就不能拿它代填出具)。""" out = [] for name, value in (binding or {}).items(): out.append({"field": name, "masked": True, "value": MASKED} if hide(value) else {"field": name, "value": value}) return out def diff_view(diff: Mapping[str, Any] | None, shown: Callable[[Any], Any]) -> dict[str, Any] | None: """差在哪:条目的两边写法、说明和逐行对照里的 SQL 片段按遮罩值换掉。""" if not isinstance(diff, Mapping): return None items = [{**item, "a": shown(item.get("a")), "b": shown(item.get("b")), "text": shown(item.get("text"))} for item in diff.get("items") or [] if isinstance(item, Mapping)] return {"items": items, "fallback": bool(diff.get("fallback")), "text_diff": [shown(line) for line in diff.get("text_diff") or []]} def view_label(label: Mapping[str, Any], *, hide: Callable[[Any], bool], shown: Callable[[Any], Any], names: Mapping[str, str]) -> dict[str, Any]: """一个标签交给界面的样子:绑定按遮罩给、差在哪按遮罩换,口径写上工作流名和级别的叫法(名字按现在的)。""" out = {k: v for k, v in label.items() if k not in ("binding", "related")} if label.get("kind") == "caliber": out["binding"] = binding_view(label.get("binding"), hide) out["workflow_name"] = names.get(str(label.get("workflow_id") or "")) out["level_text"] = LEVEL_TEXT.get(str(label.get("level") or ""), "") if label.get("kind") == "related": out["related"] = [related_view(item, hide=hide, shown=shown, names=names) for item in label.get("related") or []] return out def related_view(item: Mapping[str, Any], *, hide: Callable[[Any], bool], shown: Callable[[Any], Any], names: Mapping[str, str]) -> dict[str, Any]: out = {k: item[k] for k in ("caliber", "workflow_id", "version", "level", "node_id", "comparable", "reason", "via") if k in item} out["workflow_name"] = names.get(str(item.get("workflow_id") or "")) out["level_text"] = LEVEL_TEXT.get(str(item.get("level") or ""), "") if "binding" in item: out["binding"] = binding_view(item.get("binding"), hide) if "diff" in item: out["diff"] = diff_view(item.get("diff"), shown) return out __all__ = ["CERTIFIED_NOTE", "HOWS", "KINDS", "LEVEL_TEXT", "POST_SEAL_NOTE", "RETRO_NOTE", "binding_view", "caliber_key", "candidates_of", "card_expressions", "card_reads", "cell_label", "cells_of", "diff_view", "label_doc", "metric_label", "referenced_columns", "related_view", "view_label", "whole_of"]