From a88fd93376bef9daa61064b7163bcaa8ca5ecbd3 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 14 Oct 2026 16:11:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=96=E6=9E=90=E4=B8=8D=E6=94=B9=E4=BA=BA?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E8=BF=87=E7=9A=84=E5=A4=96=E9=94=AE=E5=85=B3?= =?UTF-8?q?=E7=B3=BB=E7=9A=84=E5=9F=BA=E6=95=B0=EF=BC=9A=E5=80=BC=E4=B8=8D?= =?UTF-8?q?=E5=8F=98=E3=80=81=E4=BF=9D=E5=AD=98=E5=8D=B3=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E7=9A=84=E5=A4=96=E9=94=AE=E5=85=B3=E7=B3=BB=E6=9D=A5=E6=BA=90?= =?UTF-8?q?=E4=BB=8D=E6=98=AF=20fk=EF=BC=8C=E4=BB=A5=E5=89=8D=20=5Fpatch?= =?UTF-8?q?=5Ffk=20=E4=B8=8D=E7=9C=8B=E7=8A=B6=E6=80=81=EF=BC=8C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A0=B8=E5=AE=9E=E5=87=BA=E7=9A=84=E5=9F=BA=E6=95=B0?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E5=B0=B1=E7=9B=B4=E6=8E=A5=E6=94=B9=E5=86=99?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E8=AE=A4=E6=97=B6=E7=9A=84=E4=BE=9D=E6=8D=AE?= =?UTF-8?q?=E8=BF=98=E6=8C=82=E7=9D=80=EF=BC=8C=E5=89=8D=E5=90=8E=E7=9F=9B?= =?UTF-8?q?=E7=9B=BE=EF=BC=9B=E7=8E=B0=E5=9C=A8=E5=90=8C=20=5Fpatch=5Fconf?= =?UTF-8?q?irmed=EF=BC=8C=E4=B8=8D=E6=94=B9=E4=BA=BA=E5=AE=9A=E7=9A=84?= =?UTF-8?q?=E5=9F=BA=E6=95=B0=E3=80=81=E4=B9=9F=E4=B8=8D=E6=A0=87=E7=94=A8?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=A0=B8=E5=AE=9E=E8=BF=87=EF=BC=8C=E6=B5=8B?= =?UTF-8?q?=E9=87=8F=E7=9A=84=E8=AF=B4=E6=B3=95=E7=85=A7=E5=B8=B8=E5=86=99?= =?UTF-8?q?=E8=BF=9B=E5=A4=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/data/catalog_profile.py | 7 +++++-- backend/tests/test_catalog_reason.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/backend/app/data/catalog_profile.py b/backend/app/data/catalog_profile.py index 9d7fb51b..59dd6aa5 100644 --- a/backend/app/data/catalog_profile.py +++ b/backend/app/data/catalog_profile.py @@ -1021,9 +1021,12 @@ def _patch_confirmed(rel: dict[str, Any], o: _RelationOutcome, at: str) -> bool: def _patch_fk(rel: dict[str, Any], o: _RelationOutcome, at: str) -> bool: """外键关系只补基数和「用数据核实过」:外键结构推的多对一可能其实是一对一,数据说了算。来源、状态、 - 覆盖率都不动;备注同 _patch_confirmed(只是旧备注的日期不同不换)。返回改没改。""" + 覆盖率都不动;备注同 _patch_confirmed(只是旧备注的日期不同不换)。人工确认过的(值不变、保存即确认的外键关系, + 来源仍是 fk)同 _patch_confirmed:不改人定的基数,数据核实出的不一样时也不标「用数据核实过」,测量的说法照常写进 + 备注。返回改没改。""" changed = False - if o.cardinality_checked: + overruled = rel.get("status") == "confirmed" and rel.get("cardinality") not in (None, o.cardinality) + if o.cardinality_checked and not overruled: if rel.get("cardinality") != o.cardinality: rel["cardinality"] = o.cardinality changed = True diff --git a/backend/tests/test_catalog_reason.py b/backend/tests/test_catalog_reason.py index 44a926be..0a51d3c8 100644 --- a/backend/tests/test_catalog_reason.py +++ b/backend/tests/test_catalog_reason.py @@ -234,3 +234,27 @@ def test_importing_carries_the_reason_of_confirmed_and_rejected_items_only(): assert writes["label"]["reason"] == "使用者说:就叫入园记录" assert writes["grain"]["reason"] == "使用者说:一张票一行" assert writes["description"]["value"] == "每次刷闸记一行" and "reason" not in writes["description"] + + +def test_profiling_keeps_the_cardinality_of_a_confirmed_foreign_key(): + """外键来的关联关系经「值不变、保存即确认」带依据确认之后,来源仍是 fk。再剖析时数据核实出的基数不同:以前 _patch_fk + 不看状态,直接改写人确认过的基数,依据还挂着,前后矛盾。现在同 _patch_confirmed,不改人定的基数、也不标「用数据核实过」, + 依据留着;测量的说法照常写进备注。没确认过的外键关系照旧按数据改基数、标核实过。""" + fk = {k: v for k, v in _rel(source="fk", status="verified").items() if k not in ("coverage", "note")} + saved = _patch({"relations": [fk]}, [ + {"path": f"relations.{PARKS}", "value": {**PARK_ENDS, "cardinality": "many_to_one"}, + "reason": "使用者说:一个景区有多条入园记录"}, + ]) + rel = saved["relations"][0] + assert (rel["source"], rel["status"], rel["reason"]) == ("fk", "confirmed", "使用者说:一个景区有多条入园记录") + outcome = catalog_profile._TableOutcome(relations=[catalog_profile._RelationOutcome( + rid=PARKS, target="parks.id", coverage=None, cardinality="one_to_one", cardinality_checked=True, holds=True, + sample=1000, matched=1000, note="数据剖析:子表一侧每个键都只有一行。")]) + rel = catalog_profile._apply(saved, outcome, at=LATER)[0]["relations"][0] + assert rel["cardinality"] == "many_to_one" and rel.get("cardinality_checked") is not True + assert (rel["source"], rel["status"], rel["reason"]) == ("fk", "confirmed", "使用者说:一个景区有多条入园记录") + assert rel["note"] == "数据剖析:子表一侧每个键都只有一行。" + + plain = catalog_profile._apply({"relations": [fk]}, outcome, at=LATER)[0]["relations"][0] + assert plain["cardinality"] == "one_to_one" and plain["cardinality_checked"] is True + assert (plain["source"], plain["status"]) == ("fk", "verified")