"""更正输入后的显式依赖失效和局部重算;仅合成当前研究视图。""" import copy import json from decimal import Decimal as D DEPS = { "A_growth": ["A_revenue", "A_prior"], "A_screen": ["A_growth"], "A_value": ["A_revenue", "margin", "discount"], "A_brief": ["A_screen", "A_value"], "B_growth": ["B_revenue", "B_prior"], "B_screen": ["B_growth"], "B_value": ["B_revenue", "margin", "discount"], } def fresh_state(): state = {"inputs": {"A_revenue": D(120), "A_prior": D(100), "B_revenue": D(200), "B_prior": D(180), "margin": D("0.10"), "discount": D("0.10")}, "outputs": {key: {"status": "pending", "value": None} for key in DEPS}, "events": {}, "history": [], "version": 1} run_pending(state) return state def read_current(state, node): row = state["outputs"][node] if row["status"] != "current": raise ValueError("result_not_current:" + node) return row["value"] def compute(state, node): inputs = state["inputs"] symbol = node[0] if node.endswith("growth"): return inputs[symbol + "_revenue"] / inputs[symbol + "_prior"] - 1 if node.endswith("screen"): return read_current(state, symbol + "_growth") >= D("0.10") if node.endswith("value"): # 明确自设恒定FCFF率、零增长永续模型,不把收入直接当真实自由现金流。 return inputs[symbol + "_revenue"] * inputs["margin"] / inputs["discount"] return {"screen": read_current(state, "A_screen"), "scenario_value": str(read_current(state, "A_value"))} def run_pending(state, fail=None): executed = [] for node, dependencies in DEPS.items(): row = state["outputs"][node] if row["status"] == "current": continue if any(dep in DEPS and state["outputs"][dep]["status"] != "current" for dep in dependencies): row.update(status="blocked_dependency", value=None) continue if node == fail: row.update(status="failed", value=None) continue row.update(status="current", value=compute(state, node), version=state["version"]) executed.append(node) return executed def correct(state, event): key, new = event["input"], D(event["new"]) if key not in state["inputs"] or not new.is_finite() or new <= 0 or not event.get("source"): raise ValueError("invalid_correction") if event["id"] in state["events"]: if state["events"][event["id"]] != event: raise ValueError("conflicting_correction_identity") return [] if D(event["old"]) != state["inputs"][key]: raise ValueError("stale_expected_old_value") affected, frontier = set(), {key} while frontier: newly = {node for node, deps in DEPS.items() if node not in affected and set(deps) & frontier} affected |= newly frontier = newly state["history"].append(copy.deepcopy(state["outputs"])) state["inputs"][key] = new state["version"] += 1 state["events"][event["id"]] = copy.deepcopy(event) for node in affected: state["outputs"][node] = {"status": "pending", "value": None} return [node for node in DEPS if node in affected] event = {"id": "correction-1", "input": "A_revenue", "old": "120", "new": "105", "source": "synthetic-corrected-filing-2026-10-26"} state = fresh_state() before = copy.deepcopy(state) unaffected = {k: copy.deepcopy(v) for k, v in state["outputs"].items() if k.startswith("B_")} affected = correct(state, event) assert affected == ["A_growth", "A_screen", "A_value", "A_brief"] assert all(state["outputs"][n]["status"] == "pending" for n in affected) try: read_current(state, "A_screen") except ValueError: pass else: raise AssertionError("已失效结果不能继续标当前") executed = run_pending(state) assert executed == affected and read_current(state, "A_growth") == D("0.05") assert read_current(state, "A_screen") is False and read_current(state, "A_value") == 105 assert before["outputs"]["A_value"]["value"] == 120 assert {k: v for k, v in state["outputs"].items() if k.startswith("B_")} == unaffected before_candidates = [s * 3 for s in ["A", "B"] if before["outputs"][s + "_screen"]["value"]] after_candidates = [s * 3 for s in ["A", "B"] if read_current(state, s + "_screen")] assert before_candidates == ["AAA", "BBB"] and after_candidates == ["BBB"] assert state["history"][0] == before["outputs"] assert correct(state, event) == [] and run_pending(state) == [] partial = fresh_state() correct(partial, event) run_pending(partial, fail="A_value") assert partial["outputs"]["A_value"] == {"status": "failed", "value": None} assert partial["outputs"]["A_brief"]["status"] == "blocked_dependency" assert read_current(partial, "A_screen") is False saved = copy.deepcopy(state) try: correct(state, {**event, "id": "bad", "old": "105", "new": "NaN"}) except ValueError: assert state == saved else: raise AssertionError("非有限更正不得发布") print(json.dumps({"affected": affected, "executed": executed, "before_candidates": before_candidates, "after_candidates": after_candidates, "before": before["outputs"], "after": state["outputs"], "partial": partial["outputs"]}, default=str, sort_keys=True))