From 96ab4bc19ed1d7ec0173b9725d4d60f0111faf80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=A0=E6=AC=A3?= <115392788+lizhangxin666@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:20:58 +0800 Subject: [PATCH 1/3] feat: add autonomous organization behavior engine --- src/insidegov/agents.py | 820 +++++++++++++++++++++++++ src/insidegov/engine.py | 448 ++++++++++++-- src/insidegov/experiments.py | 55 ++ src/insidegov/models.py | 179 ++++++ src/insidegov/organization.py | 650 ++++++++++++++++++++ src/insidegov/organization_dynamics.py | 510 +++++++++++++++ src/insidegov/scenarios.py | 19 + src/insidegov/serde.py | 60 ++ tests/test_engine.py | 15 +- tests/test_organization.py | 290 +++++++++ 10 files changed, 2983 insertions(+), 63 deletions(-) create mode 100644 src/insidegov/organization.py create mode 100644 src/insidegov/organization_dynamics.py create mode 100644 tests/test_organization.py diff --git a/src/insidegov/agents.py b/src/insidegov/agents.py index 295e5b2..a9e4049 100644 --- a/src/insidegov/agents.py +++ b/src/insidegov/agents.py @@ -107,6 +107,148 @@ def _repair_structured_payload(schema: type[BaseModel], content: str) -> dict[st data.setdefault("concerns", ["财政风险需控制"]) data.setdefault("conditions", ["分期兑现"]) data.setdefault("rationale", "财政仅表达审核态度,数值上限由规则引擎填充") + if schema is OrganizationInitiativeChoice: + decision_aliases = { + "行动": "act", "发起": "act", "执行": "act", + "等待": "wait", "暂缓": "wait", "不行动": "wait", + } + data["decision"] = decision_aliases.get( + str(data.get("decision", "")).strip(), data.get("decision", "wait") + ) + if data["decision"] not in {"act", "wait"}: + data["decision"] = "wait" + if data["decision"] == "wait": + data["action_id"] = "wait" + data["urgency"] = min(1.0, max(0.0, float(data.get("urgency") or 0.5))) + data.setdefault("rationale", "根据当前组织注意力与职责边界作出时点判断") + if schema is EnterpriseResponseAction: + response_aliases = { + "接受": "accept", "同意": "accept", "accept_offer": "accept", + "还价": "counter", "反提案": "counter", "counteroffer": "counter", + "退出": "terminate", "拒绝": "terminate", "reject": "terminate", + } + data["response"] = response_aliases.get( + str(data.get("response", "")).strip(), data.get("response", "counter") + ) + if data["response"] not in {"accept", "counter", "terminate"}: + data["response"] = "counter" + if not isinstance(data.get("counter_terms"), dict): + data["counter_terms"] = {} + else: + data["counter_terms"] = { + str(key): float(value) + for key, value in data["counter_terms"].items() + if isinstance(value, (int, float)) + } + data["confidence"] = min(1.0, max(0.0, float(data.get("confidence") or 0.6))) + data.setdefault("rationale", "基于效用、信息充分度和等待成本作出回应") + if schema is NegotiationTimingAction: + timing_aliases = { + "立即选址": "select_now", "现在选择": "select_now", "select": "select_now", + "继续谈判": "continue_negotiating", "等待": "continue_negotiating", + "全部退出": "exit_all", "退出": "exit_all", "terminate_all": "exit_all", + } + data["decision"] = timing_aliases.get( + str(data.get("decision", "")).strip(), + data.get("decision", "continue_negotiating"), + ) + if data["decision"] not in {"select_now", "continue_negotiating", "exit_all"}: + data["decision"] = "continue_negotiating" + data["confidence"] = min(1.0, max(0.0, float(data.get("confidence") or 0.6))) + data.setdefault("rationale", "权衡等待的信息价值与选址机会成本") + if schema is OrganizationPlanAction: + alternatives = data.get("alternatives") + if not isinstance(alternatives, list): + alternatives = [] + alternatives = [item for item in alternatives if isinstance(item, dict)][:3] + while len(alternatives) < 2: + index = len(alternatives) + 1 + alternatives.append({ + "id": f"strategy_{index}", "name": f"备选路径{index}", + "approach": "在职责与硬约束内分阶段推进", + "benefits": ["保持可执行性"], "risks": ["需要后续验证"], + "score": 0.5, + }) + for index, item in enumerate(alternatives, start=1): + item.setdefault("id", f"strategy_{index}") + item.setdefault("name", f"备选路径{index}") + item.setdefault("approach", "分阶段推进") + item.setdefault("benefits", []) + item.setdefault("risks", []) + item["score"] = min(1.0, max(0.0, float(item.get("score") or 0.5))) + data["alternatives"] = alternatives + steps = data.get("steps") + if not isinstance(steps, list): + steps = [] + steps = [item for item in steps if isinstance(item, dict)][:5] + while len(steps) < 2: + index = len(steps) + 1 + steps.append({ + "id": f"s{index}", "title": f"计划节点{index}", + "action_id": "propose_open_action", "earliest_offset": index - 1, + "latest_offset": index + 1, "preconditions": [], + "expected_effects": {}, + }) + for index, item in enumerate(steps, start=1): + item.setdefault("id", f"s{index}") + item.setdefault("title", f"计划节点{index}") + item.setdefault("action_id", "propose_open_action") + item["earliest_offset"] = min(8, max(0, int(item.get("earliest_offset") or index - 1))) + item["latest_offset"] = min( + 12, max(item["earliest_offset"], int(item.get("latest_offset") or index + 1)) + ) + item.setdefault("preconditions", []) + item.setdefault("expected_effects", {}) + data["steps"] = steps + data.setdefault("objective", "推进当前组织议题") + data.setdefault("selected_strategy", alternatives[0]["id"]) + data.setdefault("assumptions", []) + data.setdefault("rationale", "比较多条路径后选择可执行方案") + if schema is CompactOrganizationPlanAction: + alternatives = data.get("alternatives") + alternatives = [item for item in alternatives if isinstance(item, dict)][:3] \ + if isinstance(alternatives, list) else [] + while len(alternatives) < 2: + index = len(alternatives) + 1 + alternatives.append({ + "id": f"strategy_{index}", "approach": "分阶段推进", + "risk": "需要后续验证", "score": 0.5, + }) + for index, item in enumerate(alternatives, start=1): + item.setdefault("id", f"strategy_{index}") + item.setdefault("approach", "分阶段推进") + item.setdefault("risk", "需要后续验证") + item["score"] = min(1.0, max(0.0, float(item.get("score") or 0.5))) + data["alternatives"] = alternatives + sequence = data.get("action_sequence") + sequence = [str(item) for item in sequence][:4] if isinstance(sequence, list) else [] + while len(sequence) < 2: + sequence.append("propose_open_action") + data["action_sequence"] = sequence + data.setdefault("objective", "推进当前组织议题") + data.setdefault("selected_strategy", alternatives[0]["id"]) + data.setdefault("branch_condition", "关键假设失效时重新规划") + data.setdefault("assumptions", []) + data.setdefault("rationale", "比较两条路径后选择可执行序列") + if schema is NovelOrganizationAction: + aliases = { + "邀请上级背书": "upward_endorsement", "上级背书": "upward_endorsement", + "行业协会联盟": "association_coalition", "协会联盟": "association_coalition", + "示范项目": "demonstration_project", "会议窗口": "meeting_window", + "战略等待": "strategic_delay", "跨部门工作组": "cross_department_taskforce", + "专家论证": "expert_consultation", + } + mechanism = str(data.get("mechanism", "strategic_delay")).strip() + data["mechanism"] = aliases.get(mechanism, mechanism) + data.setdefault("title", "提出新的组织协调手段") + for key in ("requested_effects", "resource_request"): + value = data.get(key) + data[key] = { + str(item_key): float(item_value) + for item_key, item_value in value.items() + if isinstance(item_value, (int, float)) + } if isinstance(value, dict) else {} + data.setdefault("rationale", "在职责边界内利用当前机会窗口") return data @@ -117,6 +259,113 @@ class LocationAction(BaseModel): evidence: list[str] = Field(default_factory=list) +class OrganizationActionChoice(BaseModel): + action_id: str = Field(min_length=2, max_length=80) + confidence: float = Field(default=0.6, ge=0, le=1) + rationale: str = Field(max_length=140) + + +class OrganizationInitiativeChoice(BaseModel): + decision: Literal["act", "wait"] + action_id: str = Field(default="wait", min_length=2, max_length=80) + urgency: float = Field(default=0.5, ge=0, le=1) + target_actor_id: str | None = Field(default=None, max_length=100) + rationale: str = Field(max_length=160) + + +class ProcedureTransitionChoice(BaseModel): + transition: Literal[ + "start_formal_process", + "continue_informal_coordination", + "pause_formal_process", + "return_for_revision", + "re_agenda", + "resume_formal_process", + "keep_closed", + "abandon_proposal", + "proceed_formal_review", + "authorize_informal_offer", + ] + rationale: str = Field(max_length=180) + + +class EnterpriseResponseAction(BaseModel): + response: Literal["accept", "counter", "terminate"] + counter_terms: dict[str, float] = Field(default_factory=dict) + confidence: float = Field(default=0.6, ge=0, le=1) + rationale: str = Field(max_length=180) + + +class NegotiationTimingAction(BaseModel): + decision: Literal["select_now", "continue_negotiating", "exit_all"] + confidence: float = Field(default=0.6, ge=0, le=1) + rationale: str = Field(max_length=180) + + +class PlanStrategyOptionAction(BaseModel): + id: str = Field(min_length=1, max_length=40) + name: str = Field(max_length=80) + approach: str = Field(max_length=180) + benefits: list[str] = Field(default_factory=list, max_length=3) + risks: list[str] = Field(default_factory=list, max_length=3) + score: float = Field(default=0.5, ge=0, le=1) + + +class OrganizationPlanNodeAction(BaseModel): + id: str = Field(min_length=1, max_length=40) + title: str = Field(max_length=100) + action_id: str = Field(max_length=80) + earliest_offset: int = Field(default=0, ge=0, le=8) + latest_offset: int = Field(default=2, ge=0, le=12) + preconditions: list[str] = Field(default_factory=list, max_length=4) + on_success: str | None = Field(default=None, max_length=40) + on_failure: str | None = Field(default=None, max_length=40) + expected_effects: dict[str, float] = Field(default_factory=dict) + + +class OrganizationPlanAction(BaseModel): + objective: str = Field(max_length=160) + alternatives: list[PlanStrategyOptionAction] = Field(min_length=2, max_length=3) + selected_strategy: str = Field(max_length=40) + steps: list[OrganizationPlanNodeAction] = Field(min_length=2, max_length=5) + assumptions: list[str] = Field(default_factory=list, max_length=5) + rationale: str = Field(max_length=200) + + +class CompactPlanOptionAction(BaseModel): + id: str = Field(min_length=1, max_length=32) + approach: str = Field(max_length=100) + risk: str = Field(max_length=80) + score: float = Field(default=0.5, ge=0, le=1) + + +class CompactOrganizationPlanAction(BaseModel): + objective: str = Field(max_length=100) + alternatives: list[CompactPlanOptionAction] = Field(min_length=2, max_length=3) + selected_strategy: str = Field(max_length=32) + action_sequence: list[str] = Field(min_length=2, max_length=4) + branch_condition: str = Field(max_length=100) + assumptions: list[str] = Field(default_factory=list, max_length=3) + rationale: str = Field(max_length=120) + + +class NovelOrganizationAction(BaseModel): + title: str = Field(max_length=100) + mechanism: Literal[ + "upward_endorsement", + "association_coalition", + "demonstration_project", + "meeting_window", + "strategic_delay", + "cross_department_taskforce", + "expert_consultation", + ] + target_actor_id: str | None = Field(default=None, max_length=100) + requested_effects: dict[str, float] = Field(default_factory=dict) + resource_request: dict[str, float] = Field(default_factory=dict) + rationale: str = Field(max_length=180) + + class CognitiveProvider(ABC): mode = "deterministic" model_name: str | None = None @@ -170,6 +419,132 @@ def select_location( def reflect(self, agent: AgentState, action: str, outcome: str) -> str: return f"{agent.name}复盘:{action}已执行;{outcome}。下轮将根据新证据调整。" + def choose_organization_action( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationActionChoice: + return OrganizationActionChoice( + action_id=candidates[0], + confidence=0.55, + rationale="在当前权限和可见信息下选择优先行动", + ) + + def choose_organization_initiative( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationInitiativeChoice: + planned = str(observation.get("plan_current_action", "")) + if planned in candidates: + choice = OrganizationActionChoice( + action_id=planned, + confidence=0.8, + rationale=f"按已保存的多步计划执行当前节点 {planned}", + ) + else: + choice = self.choose_organization_action(agent, candidates, observation, memories) + return OrganizationInitiativeChoice( + decision="act", + action_id=choice.action_id, + urgency=0.6, + rationale=choice.rationale, + ) + + def choose_procedure_transition( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> ProcedureTransitionChoice: + return ProcedureTransitionChoice( + transition=candidates[0], + rationale="按当前程序状态进入下一合法节点", + ) + + def respond_to_offer( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> EnterpriseResponseAction: + utility = float(observation["utility"]) + minimum = float(observation["minimum_utility"]) + if utility >= minimum: + return EnterpriseResponseAction( + response="accept", confidence=0.7, rationale="方案达到最低投资门槛" + ) + return EnterpriseResponseAction( + response="terminate", confidence=0.7, rationale="方案未达到最低投资门槛" + ) + + def decide_negotiation_timing( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NegotiationTimingAction: + return NegotiationTimingAction( + decision="continue_negotiating", + confidence=0.55, + rationale="继续收集竞争城市报价与履约信息", + ) + + def create_organization_plan( + self, + agent: AgentState, + allowed_actions: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationPlanAction: + return OrganizationPlanAction( + objective=agent.goals[0] if agent.goals else "推进当前议题", + alternatives=[ + PlanStrategyOptionAction( + id="formal_first", name="正式程序优先", approach="补齐材料后进入正式审查", + benefits=["程序完整"], risks=["耗时较长"], score=0.62, + ), + PlanStrategyOptionAction( + id="coordinate_first", name="协调优先", approach="先降低信息与部门冲突再送审", + benefits=["减少否决"], risks=["可能错过窗口"], score=0.68, + ), + ], + selected_strategy="coordinate_first", + steps=[ + OrganizationPlanNodeAction( + id="s1", title="先形成可执行边界", + action_id=allowed_actions[0], earliest_offset=0, latest_offset=1, + ), + OrganizationPlanNodeAction( + id="s2", title="根据反馈推进下一阶段", + action_id=allowed_actions[-1], earliest_offset=1, latest_offset=3, + preconditions=["前一步已完成"], + ), + ], + assumptions=["财政与领导注意力可能变化"], + rationale="先比较程序推进与协调推进两条路径,再选择更可执行的组合", + ) + + def propose_novel_organization_action( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NovelOrganizationAction: + return NovelOrganizationAction( + title="建立跨部门项目工作组", + mechanism="cross_department_taskforce", + target_actor_id=f"{agent.owner_id}_leader", + requested_effects={"coalition_support_delta": 0.08}, + resource_request={}, + rationale="用临时协调结构降低跨部门信息损耗", + ) + class DeterministicCognition(CognitiveProvider): """Reproducible heterogeneous baseline with the same structured action protocol.""" @@ -205,6 +580,263 @@ def propose_offer( ], ) + def choose_organization_action( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationActionChoice: + preferred: list[str] + if agent.role.value == "investment": + preferred = [ + "preconsult_finance" if not observation.get("finance_preconsulted") else "frame_strategic_project", + "clarify_need", "request_materials", "mobilize_park_coalition", + ] + elif agent.role.value == "finance": + preferred = [ + "disclose_fiscal_boundary" if observation.get("fiscal_pressure", 0) > 0.2 else "negotiate_phasing", + "negotiate_phasing", "risk_assessment", "fiscal_guardrail", + ] + elif agent.role.value == "city_leader": + preferred = [ + "broker_compromise" if observation.get("coalition_support", 0) < 0.5 else "authorize_pilot", + "authorize_pilot", "collective_deliberation", + ] + else: + preferred = ["mobilize_park_coalition", *candidates] + selected = next((item for item in preferred if item in candidates), candidates[0]) + return OrganizationActionChoice( + action_id=selected, + confidence=0.72, + rationale=f"{agent.name}依据本部门目标、当前财政压力与协作状态,在授权动作中选择 {selected}", + ) + + def choose_organization_initiative( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationInitiativeChoice: + planned = str(observation.get("plan_current_action", "")) + if planned in candidates: + choice = OrganizationActionChoice( + action_id=planned, + confidence=0.8, + rationale=f"按已保存的多步计划执行当前节点 {planned}", + ) + else: + choice = self.choose_organization_action(agent, candidates, observation, memories) + urgency = 0.52 + if agent.role.value == "investment": + urgency += 0.18 + float(observation.get("negotiation_pressure", 0)) * 0.12 + elif agent.role.value == "finance": + urgency += float(observation.get("fiscal_pressure", 0)) * 0.55 + elif agent.role.value == "city_leader": + urgency += abs( + float(observation.get("agenda_priority", 0.5)) + - float(observation.get("coalition_support", 0.35)) + ) * 0.4 + elif agent.role.value == "park": + urgency += (1 - float(observation.get("coalition_support", 0.35))) * 0.25 + decision = "wait" if urgency < 0.56 else "act" + return OrganizationInitiativeChoice( + decision=decision, + action_id=choice.action_id if decision == "act" else "wait", + urgency=min(0.95, urgency), + target_actor_id=( + f"{agent.owner_id}_finance" + if choice.action_id == "preconsult_finance" else None + ), + rationale=choice.rationale if decision == "act" else "当前议题紧迫度不足,暂不占用组织注意力", + ) + + def choose_procedure_transition( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> ProcedureTransitionChoice: + status = str(observation.get("formal_status", "dormant")) + preferred = { + "dormant": "start_formal_process", + "completed": "re_agenda", + "paused": "resume_formal_process", + "returned": "resume_formal_process", + "active": "proceed_formal_review", + }.get(status, candidates[0]) + transition = preferred if preferred in candidates else candidates[0] + return ProcedureTransitionChoice( + transition=transition, + rationale=f"{agent.name}根据程序状态{status}、议程压力和部门协调结果推进至{transition}", + ) + + def respond_to_offer( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> EnterpriseResponseAction: + utility = float(observation["utility"]) + minimum = float(observation["minimum_utility"]) + gap = utility - minimum + round_number = int(observation.get("negotiation_round", 1)) + if gap < -8: + return EnterpriseResponseAction( + response="terminate", confidence=0.82, + rationale="报价与最低门槛差距过大,继续协商的机会成本高", + ) + if gap >= 10 and round_number >= 3: + return EnterpriseResponseAction( + response="accept", confidence=min(0.94, 0.65 + gap / 100), + rationale=( + "连续多轮报价明显超过最低门槛,等待的边际价值已经不足," + "可以进入跨城市择优" + ), + ) + offer = observation.get("offer", {}) + increment = 1.0 if observation.get("prior_counter") else 1.5 + return EnterpriseResponseAction( + response="counter", + counter_terms={ + "subsidy_floor": round(float(offer.get("subsidy", 0)) + increment, 2), + "equity_floor": round(float(offer.get("equity", 0)) + 0.8, 2), + "require_phased_delivery": 1.0, + }, + confidence=0.7, + rationale="信息或履约保障尚不足,继续还价比立即接受或退出更有价值", + ) + + def decide_negotiation_timing( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NegotiationTimingAction: + accepted = observation.get("accepted_city_ids", []) + round_number = int(observation.get("negotiation_round", 1)) + active = int(observation.get("active_negotiations", 0)) + if accepted and (round_number >= 3 or active == 0): + return NegotiationTimingAction( + decision="select_now", confidence=0.84, + rationale="已有可接受报价,继续等待的边际信息价值低于选址机会成本", + ) + if not accepted and active == 0: + return NegotiationTimingAction( + decision="exit_all", confidence=0.8, + rationale="所有城市均已终止且没有可接受报价", + ) + return NegotiationTimingAction( + decision="continue_negotiating", confidence=0.68, + rationale="仍有城市处于还价或程序调整阶段,继续等待下一轮报价", + ) + + def create_organization_plan( + self, + agent: AgentState, + allowed_actions: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationPlanAction: + role = agent.role.value + veto_count = int(observation.get("veto_count", 0)) + active_windows = observation.get("active_windows", []) + templates = { + "investment": ["preconsult_finance", "mobilize_park_coalition", "frame_strategic_project"], + "finance": ["disclose_fiscal_boundary", "negotiate_phasing", "risk_assessment"], + "park": ["mobilize_park_coalition", "propose_open_action", "clarify_need"], + "city_leader": ["broker_compromise", "authorize_pilot", "collective_deliberation"], + } + sequence = [item for item in templates.get(role, allowed_actions) if item in allowed_actions] + if not sequence: + sequence = allowed_actions[:2] + if veto_count >= 2 and "propose_open_action" in allowed_actions: + sequence = [sequence[0], "propose_open_action", *sequence[1:]] + sequence = sequence[:4] + while len(sequence) < 2: + sequence.append(sequence[-1]) + strategy = ( + "phased_adaptation" if veto_count >= 2 + else "window_acceleration" if active_windows + else "coordinate_then_formalize" + ) + return OrganizationPlanAction( + objective=agent.goals[0] if agent.goals else "推进当前组织议题", + alternatives=[ + PlanStrategyOptionAction( + id="formal_first", name="直接正式推进", + approach="优先补齐程序并进入审查", benefits=["责任清晰"], + risks=["可能遭遇早期否决"], score=0.56, + ), + PlanStrategyOptionAction( + id="coordinate_then_formalize", name="先协调后正式化", + approach="先探测边界和组织联盟,再进入正式程序", + benefits=["降低否决概率"], risks=["增加时间成本"], score=0.72, + ), + PlanStrategyOptionAction( + id="phased_adaptation", name="分阶段适应", + approach="在重复否决后改用试点、分期或新型组织手段", + benefits=["可逆且可学习"], risks=["初期规模较小"], + score=0.82 if veto_count >= 2 else 0.58, + ), + ], + selected_strategy=strategy, + steps=[ + OrganizationPlanNodeAction( + id=f"s{index}", title=f"执行 {action_id}", action_id=action_id, + earliest_offset=index - 1, latest_offset=index + 1, + preconditions=[] if index == 1 else [f"s{index-1}已完成或窗口发生"], + on_success=f"s{index+1}" if index < len(sequence) else None, + on_failure="replan", + expected_effects={"coordination": round(0.08 + index * 0.02, 2)}, + ) + for index, action_id in enumerate(sequence, start=1) + ], + assumptions=["财政硬约束不会被非正式行动绕过", "机会窗口可能改变行动时点"], + rationale="比较直接送审、先协调和分阶段适应三条路径,并根据否决历史与机会窗口选择", + ) + + def propose_novel_organization_action( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NovelOrganizationAction: + windows = {item.get("kind") for item in observation.get("active_windows", [])} + if agent.role.value == "investment" and "major_meeting" in windows: + return NovelOrganizationAction( + title="借重大会议设置项目专场", + mechanism="meeting_window", + target_actor_id=f"{agent.owner_id}_leader", + requested_effects={"agenda_priority_delta": 0.12, "attention_budget_delta": 1}, + resource_request={}, rationale="会议窗口可集中领导与部门注意力", + ) + if agent.role.value in {"investment", "park"}: + return NovelOrganizationAction( + title="邀请行业协会参与供应链论证", + mechanism="association_coalition", + target_actor_id=f"{agent.owner_id}_leader", + requested_effects={"coalition_support_delta": 0.1}, + resource_request={}, rationale="用外部产业证据扩大同级支持", + ) + if agent.role.value == "city_leader": + return NovelOrganizationAction( + title="设立跨部门示范项目工作组", + mechanism="cross_department_taskforce", + target_actor_id=f"{agent.owner_id}_investment", + requested_effects={"coalition_support_delta": 0.1, "procedural_completeness_delta": 0.06}, + resource_request={}, rationale="以临时组织承接试点并降低协调摩擦", + ) + return NovelOrganizationAction( + title="邀请独立专家评估分期方案", + mechanism="expert_consultation", + target_actor_id=f"{agent.owner_id}_leader", + requested_effects={"procedural_completeness_delta": 0.08}, + resource_request={}, rationale="用外部专业判断降低信息不对称", + ) + def review_offer( self, agent: AgentState, @@ -505,6 +1137,194 @@ def propose_offer( }, ) # type: ignore[return-value] + def choose_organization_initiative( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationInitiativeChoice: + return self._ask( + f"{agent.role.value}组织:决定此刻是否主动占用组织注意力并发起行动", + OrganizationInitiativeChoice, + { + **self._context(agent, observation, memories), + "allowed_action_ids": candidates, + "hard_rules": [ + "decision只能是act或wait", + "若decision为act,action_id必须严格来自allowed_action_ids", + "若decision为wait,action_id必须填写wait", + "不得代替其他部门行动,也不得绕过财政、合同和生产规则", + ], + "output_contract": { + "decision": "act | wait", + "action_id": "act时选一个allowed_action_ids;wait时填wait", + "urgency": "0到1,代表本部门此刻争夺组织注意力的紧迫度", + "target_actor_id": "可选,本行动希望影响或沟通的主体", + "rationale": "不超过70字,解释为何现在行动或为何等待", + }, + }, + ) # type: ignore[return-value] + + def choose_procedure_transition( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> ProcedureTransitionChoice: + return self._ask( + "市领导:根据议程、部门协调和当前程序状态决定程序下一步", + ProcedureTransitionChoice, + { + **self._context(agent, observation, memories), + "allowed_transitions": candidates, + "hard_rule": "transition必须严格来自allowed_transitions;暂停、退回和重新议程化都是真实可选结果。", + "output_contract": { + "transition": "allowed_transitions中的一个", + "rationale": "不超过80字,说明议程压力、风险和协调依据", + }, + }, + ) # type: ignore[return-value] + + def respond_to_offer( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> EnterpriseResponseAction: + return self._ask( + "企业董事会:决定现在接受、还价还是退出该城市谈判", + EnterpriseResponseAction, + { + **self._context(agent, observation, memories), + "hard_rules": [ + "response只能是accept、counter或terminate", + "counter时只填写可量化的counter_terms", + "规则引擎会阻止低于企业最低效用门槛的accept", + ], + "output_contract": { + "response": "accept | counter | terminate", + "counter_terms": "还价时给出subsidy_floor、equity_floor和require_phased_delivery等数值", + "confidence": "0到1", + "rationale": "不超过80字,说明信息充分度、效用差距与等待成本", + }, + }, + ) # type: ignore[return-value] + + def decide_negotiation_timing( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NegotiationTimingAction: + return self._ask( + "企业董事会:决定本轮是否立即选址、继续等待竞争报价或全部退出", + NegotiationTimingAction, + { + **self._context(agent, observation, memories), + "hard_rules": [ + "decision只能是select_now、continue_negotiating或exit_all", + "没有已接受城市时不能select_now", + "必须权衡信息价值、机会成本和剩余活跃谈判", + ], + "output_contract": { + "decision": "select_now | continue_negotiating | exit_all", + "confidence": "0到1", + "rationale": "不超过80字", + }, + }, + ) # type: ignore[return-value] + + def create_organization_plan( + self, + agent: AgentState, + allowed_actions: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationPlanAction: + return self._ask( + f"{agent.role.value}组织:比较策略路径并给出紧凑的跨季度行动序列", + CompactOrganizationPlanAction, + { + **self._context(agent, observation, memories), + "allowed_action_ids": allowed_actions, + "hard_rules": [ + "只比较2条不同策略,selected_strategy必须等于某个alternatives.id", + "action_sequence只填2到4个allowed_action_ids", + "不得创造预算或保证其他主体配合", + ], + "output_contract": { + "objective": "一句话目标", + "alternatives": "正好2项;每项只含id、approach、risk、score", + "selected_strategy": "一个alternatives.id", + "action_sequence": "2到4个allowed_action_ids", + "branch_condition": "一句话说明何时偏离原序列并重规划", + "assumptions": "最多3条短句", + "rationale": "不超过60字", + }, + }, + ) # type: ignore[return-value] + + def propose_novel_organization_action( + self, + agent: AgentState, + observation: dict[str, Any], + memories: list[str], + ) -> NovelOrganizationAction: + return self._ask( + f"{agent.role.value}组织:提出动作目录之外但职责范围以内的新组织手段", + NovelOrganizationAction, + { + **self._context(agent, observation, memories), + "allowed_mechanisms": [ + "upward_endorsement", "association_coalition", + "demonstration_project", "meeting_window", "strategic_delay", + "cross_department_taskforce", "expert_consultation", + ], + "allowed_effect_dimensions": [ + "agenda_priority_delta", "coalition_support_delta", + "procedural_completeness_delta", "approval_speed_bonus", + "credibility_delta", "attention_budget_delta", + ], + "hard_rules": [ + "不得申请或创造现金、股权、土地、信贷或基金资本", + "不得代替财政局、司法审查机构、企业或产业基金作决定", + "requested_effects只是申请,权限和幅度由规则引擎复核", + ], + "output_contract": { + "title": "具体且可观察的新组织行动", + "mechanism": "allowed_mechanisms中的一种", + "target_actor_id": "可选", + "requested_effects": "仅使用allowed_effect_dimensions", + "resource_request": "通常为空;不得含财政工具", + "rationale": "不超过80字", + }, + }, + ) # type: ignore[return-value] + + def choose_organization_action( + self, + agent: AgentState, + candidates: list[str], + observation: dict[str, Any], + memories: list[str], + ) -> OrganizationActionChoice: + return self._ask( + f"{agent.role.value}组织:从本部门权限内选择下一步组织行动", + OrganizationActionChoice, + { + **self._context(agent, observation, memories), + "allowed_action_ids": candidates, + "hard_rule": "action_id必须严格来自allowed_action_ids;不得代替其他部门行动或绕过财政、合同和生产规则。", + "output_contract": { + "action_id": "allowed_action_ids中的一个", + "confidence": "0到1", + "rationale": "不超过60字,解释目标、信息和关系权衡", + }, + }, + ) # type: ignore[return-value] + def review_offer( self, agent: AgentState, diff --git a/src/insidegov/engine.py b/src/insidegov/engine.py index 4e2e502..d99ac26 100644 --- a/src/insidegov/engine.py +++ b/src/insidegov/engine.py @@ -24,6 +24,7 @@ MemoryRecord, MetricsSnapshot, NegotiationRound, + OrganizationActionRecord, PaymentTranche, Phase, PolicyPackage, @@ -31,6 +32,7 @@ PromiseStatus, WorldState, ) +from .organization import OrganizationProcessEngine from .policies import DecisionPolicy @@ -51,6 +53,15 @@ def __init__( self.random.setstate(self._tuple_state(world.random_state)) self.world.random_state = self.random.getstate() self._last_cognition_meta: dict = {} + self.organization = OrganizationProcessEngine( + self.world, + self.random, + self._select_organization_initiative, + self._select_procedure_transition, + self._select_organization_plan, + self._select_novel_organization_action, + ) + self._organization_effects: dict[str, object] = {} def step(self) -> WorldState: self.world.quarter += 1 @@ -58,6 +69,7 @@ def step(self) -> WorldState: self._update_phase() self._update_market() if self.world.phase == Phase.RECRUITMENT: + self.organization.advance_opportunity_windows() self._run_recruitment() else: self._advance_anchor_project() @@ -137,7 +149,31 @@ def _apply_interventions(self) -> None: def _run_recruitment(self) -> None: anchor = self.world.firms["firm_nova"] + if self.world.recruitment_status == "exited": + return for city in self.world.cities.values(): + action_start = len(self.world.organization_actions) + organization_effects = self.organization.run_round(city) + self._organization_effects[city.id] = organization_effects + for record in self.world.organization_actions[action_start:]: + actor = self.world.agents[record.actor_id] + outcome = ( + record.blocked_reason + if not record.authorized and record.blocked_reason + else f"动作已执行;状态影响 {record.effects or '由后续程序决定'}" + ) + record.reflection = self.cognition.reflect(actor, record.action_name, outcome) + actor.last_reflection = record.reflection + if not organization_effects.offer_authorized: + state = self.world.organization_processes[city.id] + self._event( + "procedure_transition", + f"{city.name}本轮未形成对外报价", + f"程序选择 {organization_effects.procedure_transition};" + f"当前状态 {state.formal_status}。该结果由组织Agent实时决定。", + f"{city.id}_leader", city.id, "warning", + ) + continue leader = self.world.agents[f"{city.id}_leader"] finance = self.world.agents[f"{city.id}_finance"] investment = self.world.agents[f"{city.id}_investment"] @@ -155,6 +191,22 @@ def _run_recruitment(self) -> None: self._retrieve_memories(investment, "招商 政策包 竞争"), ) proposal = proposal_action.to_package(city.id) + proposal.subsidy = round( + proposal.subsidy * organization_effects.proposal_aggressiveness, 2 + ) + if organization_effects.equity_shift > 0: + shifted = proposal.subsidy * min(0.28, organization_effects.equity_shift) + proposal.subsidy = round(max(0.0, proposal.subsidy - shifted), 2) + proposal.equity = round(proposal.equity + shifted, 2) + proposal.approval_speed = min( + 1.0, + max(0.0, proposal.approval_speed + organization_effects.approval_speed_bonus), + ) + if organization_effects.credibility_delta: + anchor.perceived_credibility[city.id] = min( + 1.0, + max(0.1, anchor.perceived_credibility[city.id] + organization_effects.credibility_delta), + ) proposal_meta = self._last_cognition_meta.copy() prior_counter = next(( item for item in reversed(self.world.external_negotiations) @@ -193,6 +245,8 @@ def _run_recruitment(self) -> None: review.maximum_credit_support = hard_limits["credit_support"] review.maximum_first_period_payment = hard_limits["first_period_payment"] review.approved = review.approved and self._offer_within_review(proposal, review) + if not review.approved: + self.organization.record_veto(city.id, review.rationale) resolution = self._cognitive_call( "resolve_offer", leader, @@ -239,6 +293,8 @@ def _run_recruitment(self) -> None: )) city.active_offer = offer anchor.observed_offers[city.id] = offer + if offer.fiscal_cost > 0: + self.organization.record_successful_coordination(city.id) self.world.negotiations.append(NegotiationRound( id=f"negotiation-{len(self.world.negotiations)+1:04d}", quarter=self.world.quarter, city_id=city.id, firm_id=anchor.id, @@ -323,6 +379,13 @@ def _run_recruitment(self) -> None: ) self._trace_offer(city.id, anchor.id, offer) review_label = "通过" if review.approved else "工具级否决后重组" + self._event( + "organization_process", f"{city.name}组织程序完成", + f"{self.world.process_mode} 模式自主选择 " + f"{' → '.join(organization_effects.action_ids or [])};" + f"财政风险政策为 {self.world.organization_processes[city.id].risk_posture}", + city.id, + ) self._event( "negotiation", f"{city.name}完成内部协调", f"招商局提案现金 {proposal.subsidy:.1f};财政审核{review_label};最终现金 {offer.subsidy:.1f} + 股权 {offer.equity:.1f},分 {len(offer.payment_schedule)} 期", @@ -330,9 +393,15 @@ def _run_recruitment(self) -> None: ) self._event("offer", f"{city.name}提交政策包", f"财政成本 {offer.fiscal_cost:.1f} 亿元,工业用地折让 {offer.land_discount:.0%}", city.id, anchor.id) utility = self._firm_city_utility(anchor, city.id) - response, counter_terms, enterprise_rationale = self._enterprise_offer_response( + response, counter_terms, enterprise_rationale, response_suggestion, response_meta = self._enterprise_offer_response( anchor, city.id, utility ) + board = self.world.agents["firm_nova_board"] + board.last_reflection = self.cognition.reflect( + board, + f"对{city.name}报价作出{response}", + f"效用{utility:.1f}、门槛{anchor.minimum_utility:.1f}", + ) external = ExternalNegotiationRound( id=f"external-{len(self.world.external_negotiations)+1:04d}", quarter=self.world.quarter, city_id=city.id, firm_id=anchor.id, @@ -353,17 +422,17 @@ def _run_recruitment(self) -> None: outcome="continue" if response == "counter" else response, ) self.world.external_negotiations.append(external) + self.organization.record_enterprise_response(city.id, response) self._append_action_audit( - self.world.agents["firm_nova_board"], "evaluate_city_offer", - self._enterprise_observation(anchor, {city.id: utility}), [], - {"response": response, "counter_terms": counter_terms, - "rationale": enterprise_rationale}, + board, "evaluate_city_offer", + self._enterprise_observation(anchor, {city.id: utility}), + response_meta.get("memories", []), response_suggestion, {"response": response, "counter_terms": counter_terms}, {"source": "enterprise_acceptance_guard", "utility": utility, "minimum_utility": anchor.minimum_utility}, enterprise_rationale, - f"Q{self.world.quarter}对{city.name}报价作出{response}回应", - {"fallback": False, "diagnostics": []}, + board.last_reflection, + response_meta, outcome="responded", ) self._event( @@ -371,47 +440,7 @@ def _run_recruitment(self) -> None: f"企业{response};效用 {utility:.1f} / 门槛 {anchor.minimum_utility:.1f};{enterprise_rationale}", anchor.id, city.id, "warning" if response == "counter" else "info", ) - if self.world.quarter >= 3: - accepted_cities = { - item.city_id for item in self.world.external_negotiations - if item.quarter == self.world.quarter and item.enterprise_response == "accept" - } - scores = { - city_id: self._firm_city_utility(anchor, city_id) - for city_id in accepted_cities - } - if not scores: - self._event( - "no_deal", "企业未接受任何城市最终报价", - "三座城市方案均未达到最低接受门槛,本轮招商终止", - anchor.id, severity="warning", - ) - return - board = self.world.agents["firm_nova_board"] - decision = self._cognitive_call( - "select_location", board, anchor, scores, - self._enterprise_observation(anchor, scores), - self._retrieve_memories(board, "选址 履约 风险"), - ) - decision_meta = self._last_cognition_meta.copy() - selected_id = decision.city_id if decision.city_id in scores else max(scores, key=scores.get) # type: ignore[arg-type] - selected_score = scores[selected_id] - if selected_score >= anchor.minimum_utility: - self._select_city(anchor, selected_id, scores) - self._remember( - board, "decision", f"选择 {self.world.cities[selected_id].name}:{decision.rationale}", - 0.95, [selected_id], valence=0.4, - ) - self._append_action_audit( - board, "select_location", self._enterprise_observation(anchor, scores), - decision_meta.get("memories", []), self._action_dict(decision), - {"city_id": selected_id, "utility": selected_score}, { - "source": "location_utility_and_allowed_city_guard", - "utility_scores": scores, - "suggested_city_valid": decision.city_id in scores, - }, decision.rationale, board.last_reflection, decision_meta, - outcome="executed", - ) + self._decide_recruitment_timing(anchor) def _firm_city_utility(self, firm: FirmState, city_id: str) -> float: city = self.world.cities[city_id] @@ -496,26 +525,168 @@ def _clarify_anchor_need( def _enterprise_offer_response( self, firm: FirmState, city_id: str, utility: float, - ) -> tuple[str, dict[str, float], str]: + ) -> tuple[str, dict[str, float], str, dict, dict]: gap = utility - firm.minimum_utility - if self.world.quarter >= 3: - if gap >= 0: - return "accept", {}, "最终方案达到最低接受门槛,进入跨城市择优" - return "terminate", {}, "最终方案未达到最低接受门槛,退出该城市谈判" - if gap >= 10 and self.world.quarter >= 2: - return "accept", {}, "方案已覆盖核心需求,保留至最终跨城市比较" latest = self.world.external_negotiations prior_counter = next(( item for item in reversed(latest) if item.city_id == city_id and item.firm_id == firm.id and item.counter_terms ), None) - increment = 1.0 if prior_counter else 1.5 - counter = { - "subsidy_floor": round(firm.observed_offers[city_id].subsidy + increment, 2), - "equity_floor": round(firm.observed_offers[city_id].equity + 0.8, 2), - "require_phased_delivery": 1.0, + round_number = 1 + sum( + 1 for item in latest if item.city_id == city_id and item.firm_id == firm.id + ) + belief = self.world.gov_beliefs.get(city_id) + observation = { + **self._enterprise_observation(firm, {city_id: utility}), + "city_id": city_id, + "utility": utility, + "minimum_utility": firm.minimum_utility, + "utility_gap": round(gap, 3), + "belief_confidence": belief.confidence if belief else 0.0, + "negotiation_round": round_number, + "prior_counter": bool(prior_counter), + "offer": self._offer_tools(firm.observed_offers[city_id]), + "wait_cost": round(0.08 * round_number, 3), + } + board = self.world.agents["firm_nova_board"] + action = self._cognitive_call( + "respond_to_offer", board, observation, + self._retrieve_memories(board, f"{city_id} 报价 还价 退出"), + ) + meta = self._last_cognition_meta.copy() + suggestion = self._action_dict(action) + response = action.response + rationale = action.rationale + counter_terms = { + key: max(0.0, float(value)) + for key, value in action.counter_terms.items() + if key in {"subsidy_floor", "equity_floor", "require_phased_delivery"} } - return "counter", counter, "政策强度或履约保障仍不足,要求提高支持并绑定分期节点" + if response == "accept" and gap < 0: + response = "terminate" if gap < -8 else "counter" + rationale = f"规则引擎阻止低于最低效用门槛的接受;{rationale}" + if response == "counter": + increment = 1.0 if prior_counter else 1.5 + counter_terms.setdefault( + "subsidy_floor", + round(firm.observed_offers[city_id].subsidy + increment, 2), + ) + counter_terms.setdefault( + "equity_floor", round(firm.observed_offers[city_id].equity + 0.8, 2) + ) + counter_terms.setdefault("require_phased_delivery", 1.0) + else: + counter_terms = {} + return response, counter_terms, rationale, suggestion, meta + + def _decide_recruitment_timing(self, anchor: FirmState) -> None: + latest_by_city: dict[str, ExternalNegotiationRound] = {} + for item in reversed(self.world.external_negotiations): + if item.firm_id == anchor.id and item.city_id not in latest_by_city: + latest_by_city[item.city_id] = item + accepted = [ + city_id for city_id, item in latest_by_city.items() + if item.enterprise_response == "accept" + ] + active = [ + city_id for city_id, item in latest_by_city.items() + if item.enterprise_response == "counter" + ] + terminated = [ + city_id for city_id, item in latest_by_city.items() + if item.enterprise_response == "terminate" + ] + procedural_pending = [ + city_id for city_id, state in self.world.organization_processes.items() + if state.formal_status in {"dormant", "active", "paused", "returned"} + and city_id not in terminated + ] + active = sorted(set(active) | set(procedural_pending)) + scores = { + city_id: self._firm_city_utility(anchor, city_id) + for city_id in accepted if city_id in anchor.observed_offers + } + board = self.world.agents["firm_nova_board"] + observation = { + **self._enterprise_observation(anchor, scores), + "accepted_city_ids": accepted, + "active_city_ids": active, + "terminated_city_ids": terminated, + "active_negotiations": len(active), + "negotiation_round": self.world.quarter, + "round_limit": self.world.negotiation_round_limit, + "wait_cost": round(0.08 * self.world.quarter, 3), + "procedure_statuses": { + city_id: state.formal_status + for city_id, state in self.world.organization_processes.items() + }, + } + timing = self._cognitive_call( + "decide_negotiation_timing", board, observation, + self._retrieve_memories(board, "选址 等待 退出 机会成本"), + ) + timing_meta = self._last_cognition_meta.copy() + decision = timing.decision + guard = "enterprise_timing_guard" + if decision == "select_now" and not scores: + decision = "continue_negotiating" if active else "exit_all" + guard += ":no_accepted_offer" + if self.world.quarter >= self.world.negotiation_round_limit: + decision = "select_now" if scores else "exit_all" + guard += ":round_limit" + board.last_reflection = self.cognition.reflect( + board, + "决定谈判时点", + f"最终执行{decision};已接受{len(accepted)}城、仍活跃{len(active)}城", + ) + self._append_action_audit( + board, "decide_negotiation_timing", observation, + timing_meta.get("memories", []), self._action_dict(timing), + {"decision": decision}, {"source": guard}, timing.rationale, + board.last_reflection, timing_meta, + outcome="executed", + ) + self._event( + "enterprise_timing", "企业决定谈判时点", + f"董事会选择 {decision};已接受{len(accepted)}城、还价中{len(active)}城、退出{len(terminated)}城。{timing.rationale}", + board.id, severity="warning" if decision == "exit_all" else "info", + ) + if decision == "continue_negotiating": + return + if decision == "exit_all": + self.world.recruitment_status = "exited" + self._event( + "no_deal", "企业退出全部城市谈判", + "退出时点由企业Agent决定;规则引擎仅确认没有可执行签约。", + anchor.id, severity="warning", + ) + return + location = self._cognitive_call( + "select_location", board, anchor, scores, + self._enterprise_observation(anchor, scores), + self._retrieve_memories(board, "选址 履约 风险"), + ) + decision_meta = self._last_cognition_meta.copy() + selected_id = location.city_id if location.city_id in scores else max(scores, key=scores.get) + selected_score = scores[selected_id] + if selected_score < anchor.minimum_utility: + return + self._select_city(anchor, selected_id, scores) + self.world.recruitment_status = "selected" + self._remember( + board, "decision", f"选择 {self.world.cities[selected_id].name}:{location.rationale}", + 0.95, [selected_id], valence=0.4, + ) + self._append_action_audit( + board, "select_location", self._enterprise_observation(anchor, scores), + decision_meta.get("memories", []), self._action_dict(location), + {"city_id": selected_id, "utility": selected_score}, { + "source": "location_utility_and_allowed_city_guard", + "utility_scores": scores, + "suggested_city_valid": location.city_id in scores, + }, location.rationale, board.last_reflection, decision_meta, + outcome="executed", + ) def _select_city(self, anchor: FirmState, city_id: str, scores: dict[str, float]) -> None: city = self.world.cities[city_id] @@ -845,6 +1016,9 @@ def _hard_finance_constraints(self, city, finance) -> dict[str, float]: reserve = float(finance.private_facts.get("reserve_floor", city.available_budget * 0.34)) spendable = max(0.0, city.available_budget - reserve) risk_discount = max(0.3, 1 - city.fiscal_pressure * finance.traits["risk_aversion"]) + effects = self._organization_effects.get(city.id) + policy_multiplier = float(getattr(effects, "finance_multiplier", 1.0)) + risk_discount = min(1.0, max(0.25, risk_discount * policy_multiplier)) return { "fiscal_cost": round(max(0.0, min(spendable * 0.34, city.available_budget * 0.19) * risk_discount), 2), "subsidy": round(min(spendable * 0.11, city.available_budget * 0.065) * risk_discount, 2), @@ -878,11 +1052,35 @@ def _review_joint_investment(self, city, anchor, investment_agent) -> dict: allocations: dict[str, float] = {} remaining = target for fund in sorted(funds, key=lambda item: item.risk_tolerance, reverse=True): + authorized = project_score >= fund.due_diligence_threshold + self.world.organization_actions.append(OrganizationActionRecord( + id=f"org-action-{len(self.world.organization_actions)+1:05d}", + quarter=self.world.quarter, + city_id=city.id, + actor_id=fund.id, + actor_role="fund", + action_id="fund_due_diligence", + action_name="产业基金独立尽调", + arena="formal", + process_mode=self.world.process_mode, + candidates=["fund_due_diligence", "decline_investment"], + observations={ + "project_score": round(project_score, 3), + "due_diligence_threshold": fund.due_diligence_threshold, + "available_capital": fund.available_capital, + }, + rationale="基金依据项目质量、资本余额与自身阈值独立决策,政府不得代为承诺", + effects={"approved_capital": 0.0}, + authorized=authorized, + blocked_reason=None if authorized else "项目质量未达到基金独立尽调阈值", + evidence_ids=["fund_independence"], + )) if project_score < fund.due_diligence_threshold: continue allocation = min(fund.available_capital, remaining) if allocation > 0: allocations[fund.id] = round(allocation, 2) + self.world.organization_actions[-1].effects["approved_capital"] = round(allocation, 2) remaining -= allocation if remaining <= 0.005: break @@ -986,6 +1184,7 @@ def _cognitive_call(self, method: str, *args): "memories": memories, } return result + except (httpx.HTTPError, ValueError, KeyError, TypeError) as exc: if isinstance(self.cognition, DeterministicCognition): raise @@ -1008,6 +1207,115 @@ def _cognitive_call(self, method: str, *args): } return result + def _select_organization_action( + self, + actor_id: str, + candidates: list[str], + observation: dict[str, float | bool], + ) -> tuple[str, str, str, bool]: + actor = self.world.agents[actor_id] + choice = self._cognitive_call( + "choose_organization_action", + actor, + candidates, + observation, + self._retrieve_memories(actor, "组织 协调 议程 风险"), + ) + meta = self._last_cognition_meta.copy() + return ( + choice.action_id, + choice.rationale, + self.cognition.model_name or self.cognition.mode, + bool(meta.get("fallback")), + ) + + def _select_organization_initiative( + self, + actor_id: str, + candidates: list[str], + observation: dict[str, float | bool], + ) -> tuple[str, str, float, str | None, str, str, bool]: + actor = self.world.agents[actor_id] + choice = self._cognitive_call( + "choose_organization_initiative", + actor, + candidates, + observation, + self._retrieve_memories(actor, "组织 发起 议程 风险 等待"), + ) + meta = self._last_cognition_meta.copy() + return ( + choice.decision, + choice.action_id, + choice.urgency, + choice.target_actor_id, + choice.rationale, + self.cognition.model_name or self.cognition.mode, + bool(meta.get("fallback")), + ) + + def _select_organization_plan( + self, + actor_id: str, + allowed_actions: list[str], + observation: dict[str, object], + ) -> tuple[dict[str, object], str, bool]: + actor = self.world.agents[actor_id] + plan = self._cognitive_call( + "create_organization_plan", + actor, + allowed_actions, + observation, + self._retrieve_memories(actor, "计划 否决 窗口 联盟 经验"), + ) + meta = self._last_cognition_meta.copy() + return ( + self._action_dict(plan), + self.cognition.model_name or self.cognition.mode, + bool(meta.get("fallback")), + ) + + def _select_novel_organization_action( + self, + actor_id: str, + observation: dict[str, object], + ) -> tuple[dict[str, object], str, bool]: + actor = self.world.agents[actor_id] + proposal = self._cognitive_call( + "propose_novel_organization_action", + actor, + observation, + self._retrieve_memories(actor, "新行动 权限 联盟 试点 窗口"), + ) + meta = self._last_cognition_meta.copy() + return ( + self._action_dict(proposal), + self.cognition.model_name or self.cognition.mode, + bool(meta.get("fallback")), + ) + + def _select_procedure_transition( + self, + actor_id: str, + candidates: list[str], + observation: dict[str, float | bool | str], + ) -> tuple[str, str, str, bool]: + actor = self.world.agents[actor_id] + choice = self._cognitive_call( + "choose_procedure_transition", + actor, + candidates, + observation, + self._retrieve_memories(actor, "程序 启动 暂停 退回 议程"), + ) + meta = self._last_cognition_meta.copy() + return ( + choice.transition, + choice.rationale, + self.cognition.model_name or self.cognition.mode, + bool(meta.get("fallback")), + ) + @staticmethod def _action_dict(action) -> dict: if hasattr(action, "model_dump"): @@ -1052,7 +1360,23 @@ def _retrieve_memories(self, agent, query: str, limit: int = 4) -> list[str]: overlap = sum(term in memory.content for term in terms) recency = 1 / (1 + max(0, self.world.quarter - memory.quarter)) scored.append((memory.importance * 0.65 + recency * 0.2 + overlap * 0.15, memory)) - return [item.content for _, item in sorted(scored, key=lambda x: x[0], reverse=True)[:limit]] + memories = [ + item.content for _, item in sorted(scored, key=lambda x: x[0], reverse=True)[:limit] + ] + learning = self.world.organization_learning.get(agent.id) + if learning: + institutional = [ + *learning.transferable_lessons[-2:], + *learning.lessons[-2:], + *( + [f"已形成组织惯例:{', '.join(sorted(learning.routines))}"] + if learning.routines else [] + ), + ] + for item in institutional: + if item not in memories: + memories.append(item) + return memories[: limit + 3] def _remember( self, agent, kind: str, content: str, importance: float, diff --git a/src/insidegov/experiments.py b/src/insidegov/experiments.py index 7d00622..d86c4fd 100644 --- a/src/insidegov/experiments.py +++ b/src/insidegov/experiments.py @@ -21,6 +21,61 @@ ) from .talent_engine import TalentSimulationEngine + +def run_organization_mode_comparison( + seed: int = 42, quarters: int = 16, +) -> list[dict]: + """Run the same policy world under formal, informal and hybrid processes.""" + from collections import Counter + + rows: list[dict] = [] + for mode in ("formal", "informal", "hybrid"): + world = create_full_lifecycle_world(seed, f"organization-{mode}-{seed}") + world.process_mode = mode + engine = SimulationEngine(world) + engine.run(quarters) + action_counts = Counter(item.action_id for item in world.organization_actions) + arena_counts = Counter(item.arena for item in world.organization_actions) + selected = world.cities.get(world.selected_city_id) if world.selected_city_id else None + selected_process = ( + world.organization_processes.get(world.selected_city_id) + if world.selected_city_id else None + ) + rows.append({ + "process_mode": mode, + "seed": seed, + "selected_city": selected.name if selected else None, + "selected_city_id": world.selected_city_id, + "organization_actions": len(world.organization_actions), + "action_counts": dict(action_counts), + "arena_counts": dict(arena_counts), + "procedural_completeness": round( + selected_process.procedural_completeness if selected_process else 0.0, 3 + ), + "coalition_support": round( + selected_process.coalition_support if selected_process else 0.0, 3 + ), + "risk_posture": selected_process.risk_posture if selected_process else None, + "final_employment": world.history[-1].total_employment, + "average_credibility": world.history[-1].average_credibility, + "fulfilled_promises": sum(item.status.value == "fulfilled" for item in world.promises), + "delayed_promises": sum(item.status.value == "delayed" for item in world.promises), + "selected_offer": ( + { + "subsidy": selected.active_offer.subsidy, + "equity": selected.active_offer.equity, + "external_equity": selected.active_offer.external_equity, + "fiscal_cost": round(selected.active_offer.fiscal_cost, 3), + } + if selected and selected.active_offer else None + ), + "action_sequence": [ + item.action_id for item in world.organization_actions + if item.city_id == world.selected_city_id and item.quarter <= 3 + ], + }) + return rows + DEFAULT_SEEDS = [11, 23, 42, 57, 89] # 人才场景 2x2 反事实:语言模式(官话/人话)× 中介平台(关/开) diff --git a/src/insidegov/models.py b/src/insidegov/models.py index b0ec9ab..c3c6ba4 100644 --- a/src/insidegov/models.py +++ b/src/insidegov/models.py @@ -28,6 +28,17 @@ class AgentRole(StrEnum): TALENT = "talent" UNIVERSITY = "university" PLATFORM = "platform" + LEGAL = "legal" + PARK = "park" + FUND = "fund" + + +class ProcessMode(StrEnum): + """How organizations are allowed to organize the policy process.""" + + FORMAL = "formal" + INFORMAL = "informal" + HYBRID = "hybrid" class TalentType(StrEnum): @@ -230,6 +241,165 @@ class AgentActionAudit: outcome: str = "proposed" +@dataclass(slots=True) +class OrganizationActionRecord: + """One organization-level move selected from a role-specific action set.""" + + id: str + quarter: int + city_id: str + actor_id: str + actor_role: str + action_id: str + action_name: str + arena: str + process_mode: str + candidates: list[str] + observations: dict[str, Any] + rationale: str + effects: dict[str, float] + required: bool = False + authorized: bool = True + blocked_reason: str | None = None + evidence_ids: list[str] = field(default_factory=list) + selection_provider: str = "deterministic" + selection_rationale: str = "" + fallback: bool = False + stage: str = "coordination" + sequence: int = 0 + decision: str = "act" + target_actor_id: str | None = None + urgency: float = 0.5 + reflection: str = "" + plan_id: str | None = None + plan_node_id: str | None = None + deviation_reason: str | None = None + open_action_proposal_id: str | None = None + + +@dataclass(slots=True) +class PlanStrategyOption: + id: str + name: str + approach: str + benefits: list[str] = field(default_factory=list) + risks: list[str] = field(default_factory=list) + score: float = 0.5 + + +@dataclass(slots=True) +class OrganizationPlanNode: + id: str + title: str + action_id: str + earliest_quarter: int + latest_quarter: int + preconditions: list[str] = field(default_factory=list) + on_success: str | None = None + on_failure: str | None = None + expected_effects: dict[str, float] = field(default_factory=dict) + status: str = "pending" + executed_quarter: int | None = None + actual_action_id: str | None = None + deviation_reason: str | None = None + + +@dataclass(slots=True) +class OrganizationPlan: + id: str + city_id: str + actor_id: str + created_quarter: int + horizon_quarter: int + objective: str + alternatives: list[PlanStrategyOption] + selected_strategy: str + nodes: list[OrganizationPlanNode] + assumptions: list[str] = field(default_factory=list) + rationale: str = "" + status: str = "active" + provider: str = "deterministic" + fallback: bool = False + review_history: list[dict[str, Any]] = field(default_factory=list) + + +@dataclass(slots=True) +class OpenActionProposal: + id: str + quarter: int + city_id: str + actor_id: str + title: str + mechanism: str + target_actor_id: str | None + requested_effects: dict[str, float] + resource_request: dict[str, float] + rationale: str + status: str = "proposed" + validation_reason: str = "" + executed_effects: dict[str, float] = field(default_factory=dict) + provider: str = "deterministic" + fallback: bool = False + + +@dataclass(slots=True) +class OpportunityWindow: + id: str + city_id: str | None + kind: str + title: str + start_quarter: int + end_quarter: int + magnitude: float + source: str + trigger: str + effects: dict[str, float] + observed_by: list[str] = field(default_factory=list) + status: str = "active" + applied: bool = False + + +@dataclass(slots=True) +class OrganizationLearningState: + agent_id: str + trust_by_actor: dict[str, float] = field(default_factory=dict) + firm_type_beliefs: dict[str, float] = field(default_factory=dict) + action_attempts: dict[str, int] = field(default_factory=dict) + action_successes: dict[str, int] = field(default_factory=dict) + strategy_preferences: dict[str, float] = field(default_factory=dict) + routines: dict[str, float] = field(default_factory=dict) + veto_count: int = 0 + successful_coordination_count: int = 0 + leadership_generation: int = 1 + inherited_memory_ratio: float = 1.0 + lessons: list[str] = field(default_factory=list) + transferable_lessons: list[str] = field(default_factory=list) + + +@dataclass(slots=True) +class OrganizationProcessState: + """Evolving organizational relations, agenda and procedural state per city.""" + + city_id: str + agenda_priority: float = 0.5 + procedural_completeness: float = 0.0 + coalition_support: float = 0.35 + legitimacy: float = 0.65 + risk_posture: str = "balanced" + finance_preconsulted: bool = False + legal_reviewed: bool = False + collective_deliberated: bool = False + pilot_authorized: bool = False + relationships: dict[str, float] = field(default_factory=dict) + action_sequence: list[str] = field(default_factory=list) + formal_status: str = "dormant" + return_count: int = 0 + last_transition: str = "not_started" + last_transition_quarter: int = 0 + paused_reason: str | None = None + attention_budget: int = 2 + + @dataclass(slots=True) class Event: quarter: int @@ -388,8 +558,13 @@ class WorldState: events: list[Event] traces: list[DecisionTrace] action_audits: list[AgentActionAudit] + organization_actions: list[OrganizationActionRecord] history: list[MetricsSnapshot] interventions: list[Intervention] + organization_plans: list[OrganizationPlan] = field(default_factory=list) + open_action_proposals: list[OpenActionProposal] = field(default_factory=list) + opportunity_windows: list[OpportunityWindow] = field(default_factory=list) + organization_learning: dict[str, OrganizationLearningState] = field(default_factory=dict) investment_funds: dict[str, InvestmentFundState] = field(default_factory=dict) intervention_plans: list[InterventionPlan] = field(default_factory=list) random_state: Any | None = None @@ -399,9 +574,13 @@ class WorldState: demand_multiplier: float = 1.0 market_price: float = 1.0 selected_city_id: str | None = None + recruitment_status: str = "active" + negotiation_round_limit: int = 6 parent_id: str | None = None policy_mode: str = "deterministic" model_name: str | None = None + process_mode: str = ProcessMode.HYBRID + organization_processes: dict[str, OrganizationProcessState] = field(default_factory=dict) mechanisms: dict[str, bool] = field(default_factory=lambda: { "private_information": True, "internal_governance": True, diff --git a/src/insidegov/organization.py b/src/insidegov/organization.py new file mode 100644 index 0000000..eba3fb5 --- /dev/null +++ b/src/insidegov/organization.py @@ -0,0 +1,650 @@ +from __future__ import annotations + +import random +from collections.abc import Callable +from dataclasses import dataclass + +from .models import ( + AgentRole, + AgentState, + CityState, + OrganizationActionRecord, + OrganizationPlan, + OrganizationPlanNode, + OrganizationProcessState, + ProcessMode, + WorldState, +) +from .organization_dynamics import OrganizationDynamics + +BEHAVIOR_EVIDENCE = { + "formal_procedure": { + "title": "《重大行政决策程序暂行条例》答记者问", + "url": "https://www.moj.gov.cn/pub/sfbgw/zcjd/201905/t20190516_390231.html", + "claim": "重大行政决策以公众参与、专家论证、风险评估、合法性审查和集体讨论构成程序链;合法性审查与集体讨论是刚性程序。", + }, + "fund_independence": { + "title": "国务院办公厅关于促进政府投资基金高质量发展的指导意见", + "url": "https://www.mee.gov.cn/zcwj/gwywj/202501/t20250108_1100235.shtml", + "claim": "政府投资基金应市场化、法治化、专业化运作,建立独立投资决策和尽职调查机制,行政部门不得干预具体项目投资决策。", + }, + "informal_networks": { + "title": "Policy Experimentation within Bureaucratic Power Networks", + "url": "https://www.cambridge.org/core/journals/china-quarterly/article/policy-experimentation-within-bureaucratic-power-networks-the-policebusiness-cooperation-scheme-in-urban-china/6E7E2E1536065118E626082F0CFF58E8", + "claim": "政策企业家通过对上议程建议、对同级修辞联盟和对下支持性指导,弥补正式权力网络在议程、协调和执行上的不足。", + }, + "attention": { + "title": "Competition for attention in the Chinese bureaucracy", + "url": "https://link.springer.com/article/10.1186/s40711-018-0071-z", + "claim": "科层组织中的政策推进受到有限注意力与议题竞争影响,议程优先级不是固定常量。", + }, +} + + +@dataclass(frozen=True, slots=True) +class ActionDefinition: + id: str + name: str + roles: tuple[AgentRole, ...] + arenas: tuple[str, ...] + rationale: str + evidence_ids: tuple[str, ...] + + +ACTION_CATALOG: dict[str, ActionDefinition] = { + item.id: item for item in ( + ActionDefinition("request_materials", "要求补充项目材料", (AgentRole.INVESTMENT,), ("formal",), "先降低信息缺口,再形成正式方案", ("formal_procedure",)), + ActionDefinition("clarify_need", "澄清企业真实需求", (AgentRole.INVESTMENT,), ("formal", "informal"), "避免把表面诉求直接翻译为补贴", ("formal_procedure",)), + ActionDefinition("preconsult_finance", "与财政局会前沟通", (AgentRole.INVESTMENT,), ("informal",), "在正式送审前探测可执行边界", ("informal_networks",)), + ActionDefinition("frame_strategic_project", "将项目上推为战略议题", (AgentRole.INVESTMENT,), ("informal",), "争取有限的领导注意力和跨部门资源", ("informal_networks", "attention")), + ActionDefinition("mobilize_park_coalition", "联合园区形成同级联盟", (AgentRole.INVESTMENT, AgentRole.PARK), ("informal",), "用产业与执行证据扩大同级支持", ("informal_networks",)), + ActionDefinition("verify_site_readiness", "核验园区承载条件", (AgentRole.PARK,), ("formal",), "核验土地、建设和配套条件是否满足正式承诺", ("formal_procedure",)), + ActionDefinition("risk_assessment", "开展财政风险评估", (AgentRole.FINANCE,), ("formal",), "根据真实财政状态选择风险政策", ("formal_procedure",)), + ActionDefinition("disclose_fiscal_boundary", "非正式披露财政边界", (AgentRole.FINANCE,), ("informal",), "减少后续正式否决的协调成本", ("informal_networks",)), + ActionDefinition("negotiate_phasing", "协商分期与条件", (AgentRole.FINANCE,), ("informal",), "把总额冲突转化为现金流和条件设计", ("informal_networks",)), + ActionDefinition("legality_review", "进行合法性审查", (AgentRole.LEGAL,), ("formal",), "检查权限、程序和承诺可执行性", ("formal_procedure",)), + ActionDefinition("collective_deliberation", "提交集体讨论", (AgentRole.CITY_LEADER,), ("formal",), "由集体讨论形成可追责的最终意见", ("formal_procedure",)), + ActionDefinition("broker_compromise", "领导居中协调工具组合", (AgentRole.CITY_LEADER,), ("informal",), "在招商目标与财政底线之间重组政策工具", ("informal_networks",)), + ActionDefinition("authorize_pilot", "授权小规模试点", (AgentRole.CITY_LEADER,), ("informal",), "用可逆试点替代一次性全面承诺", ("informal_networks",)), + ActionDefinition("propose_open_action", "提出目录外组织手段", (AgentRole.INVESTMENT, AgentRole.FINANCE, AgentRole.PARK, AgentRole.CITY_LEADER), ("informal",), "提出新的组织手段,并接受职责、资源与状态效果审查", ("informal_networks", "attention")), + ActionDefinition("fiscal_guardrail", "执行财政硬约束检查", (AgentRole.FINANCE,), ("control",), "无论程序模式如何,均不得突破财政与现金流硬边界", ("formal_procedure",)), + ActionDefinition("fund_due_diligence", "产业基金独立尽调", (AgentRole.FUND,), ("formal", "informal"), "基金依据项目质量和自身风控独立决定", ("fund_independence",)), + ) +} + + +ROLE_ACTIONS: dict[AgentRole, tuple[str, ...]] = { + role: tuple(item.id for item in ACTION_CATALOG.values() if role in item.roles) + for role in AgentRole +} + + +@dataclass(slots=True) +class OrganizationRoundEffects: + proposal_aggressiveness: float = 1.0 + equity_shift: float = 0.0 + finance_multiplier: float = 1.0 + approval_speed_bonus: float = 0.0 + credibility_delta: float = 0.0 + action_ids: list[str] | None = None + offer_authorized: bool = False + procedure_transition: str = "not_started" + procedure_status: str = "dormant" + + +class OrganizationProcessEngine: + """Selects organization moves while leaving state settlement to hard rules.""" + + def __init__( + self, + world: WorldState, + random_source: random.Random, + initiative_selector: Callable[ + [str, list[str], dict[str, object]], + tuple[str, str, float, str | None, str, str, bool], + ] | None = None, + transition_selector: Callable[ + [str, list[str], dict[str, float | bool | str]], + tuple[str, str, str, bool], + ] | None = None, + plan_selector: Callable[ + [str, list[str], dict[str, object]], tuple[dict[str, object], str, bool] + ] | None = None, + novel_action_selector: Callable[ + [str, dict[str, object]], tuple[dict[str, object], str, bool] + ] | None = None, + ): + self.world = world + self.random = random_source + self.initiative_selector = initiative_selector + self.transition_selector = transition_selector + self.plan_selector = plan_selector + self.novel_action_selector = novel_action_selector + self.dynamics = OrganizationDynamics(world) + self._round_plan_context: dict[ + str, tuple[OrganizationPlan | None, OrganizationPlanNode | None, dict[str, object]] + ] = {} + + def advance_opportunity_windows(self): + return self.dynamics.advance_opportunity_windows() + + def record_veto(self, city_id: str, rationale: str) -> None: + self.dynamics.record_veto(city_id, rationale) + + def record_successful_coordination(self, city_id: str) -> None: + self.dynamics.record_successful_coordination(city_id) + + def record_enterprise_response(self, city_id: str, response: str) -> None: + self.dynamics.record_enterprise_response(city_id, response) + + def run_round(self, city: CityState) -> OrganizationRoundEffects: + mode = ProcessMode(self.world.process_mode) + state = self.world.organization_processes.setdefault( + city.id, OrganizationProcessState(city_id=city.id) + ) + self._round_plan_context = {} + effects = OrganizationRoundEffects(action_ids=[]) + nominations = self._collect_initiatives(city, state, mode) + active = [item for item in nominations if item[1] == "act"] + active.sort(key=lambda item: item[3], reverse=True) + selected_keys = { + (item[0], item[2]) for item in active[: max(1, state.attention_budget)] + } + sequence = 0 + for actor_id, decision, action_id, urgency, target_id, selection_meta, candidates in nominations: + plan, plan_node, actor_observation = self._round_plan_context.get( + actor_id, (None, None, {}) + ) + if decision == "wait": + record = self._initiative_record( + city, state, actor_id, "wait", "wait", urgency, target_id, + selection_meta, candidates, 0, "本轮主动等待", + ) + self.dynamics.review_plan_action(plan, plan_node, record) + self.dynamics.learn_from_action(record) + self.world.organization_actions.append(record) + continue + if (actor_id, action_id) not in selected_keys: + record = self._initiative_record( + city, state, actor_id, action_id, "deferred", urgency, target_id, + selection_meta, candidates, 0, "组织注意力预算不足,顺延至后续轮次", + ) + self.dynamics.review_plan_action(plan, plan_node, record) + self.dynamics.learn_from_action(record) + self.world.organization_actions.append(record) + continue + sequence += 1 + if action_id == "propose_open_action": + record = self._execute_open_action( + city, state, actor_id, candidates, actor_observation, + selection_meta, sequence, urgency, target_id, + ) + else: + record = self._execute( + city, state, actor_id, action_id, False, selection_meta, + stage="initiative", sequence=sequence, urgency=urgency, + target_actor_id=target_id, + ) + self.dynamics.review_plan_action(plan, plan_node, record) + self.dynamics.learn_from_action(record) + self.world.organization_actions.append(record) + effects.action_ids.append(record.action_id) + self._merge_effects(effects, record.effects) + + transition, transition_meta = self._choose_transition(city, state, mode) + sequence += 1 + transition_record = self._transition_record( + city, state, transition, transition_meta, sequence + ) + self.world.organization_actions.append(transition_record) + self.dynamics.learn_from_action(transition_record) + effects.action_ids.append(transition) + effects.procedure_transition = transition + effects.offer_authorized = self._apply_transition(state, transition) + state.last_transition_quarter = self.world.quarter + + required: list[tuple[str, str]] = [] + if effects.offer_authorized and mode in {ProcessMode.FORMAL, ProcessMode.HYBRID}: + required = [ + (f"{city.id}_finance", "risk_assessment"), + (f"{city.id}_legal", "legality_review"), + (f"{city.id}_leader", "collective_deliberation"), + ] + elif effects.offer_authorized: + required = [(f"{city.id}_finance", "fiscal_guardrail")] + for actor_id, action_id in required: + sequence += 1 + record = self._execute( + city, state, actor_id, action_id, True, + ("程序一旦获准推进即进入不可跳过的硬检查", "rule_engine", False), + stage="formal_gate", sequence=sequence, + ) + self.world.organization_actions.append(record) + self.dynamics.learn_from_action(record) + effects.action_ids.append(action_id) + self._merge_effects(effects, record.effects) + if effects.offer_authorized: + state.formal_status = "completed" + state.paused_reason = None + effects.procedure_status = state.formal_status + + posture_factor = {"conservative": 0.86, "balanced": 1.0, "developmental": 1.08} + effects.finance_multiplier = posture_factor[state.risk_posture] + if mode == ProcessMode.INFORMAL and state.procedural_completeness < 0.45: + effects.credibility_delta -= 0.018 + elif state.procedural_completeness >= 0.7: + effects.credibility_delta += 0.012 + return effects + + def _collect_initiatives( + self, city: CityState, state: OrganizationProcessState, mode: ProcessMode, + ) -> list[tuple[str, str, str, float, str | None, tuple[str, str, bool], list[str]]]: + pools = { + f"{city.id}_investment": ["request_materials", "clarify_need"] if mode == ProcessMode.FORMAL else ["preconsult_finance", "frame_strategic_project", "clarify_need"], + f"{city.id}_park": ["verify_site_readiness"] if mode == ProcessMode.FORMAL else ["mobilize_park_coalition"], + f"{city.id}_finance": ["risk_assessment"] if mode == ProcessMode.FORMAL else ["disclose_fiscal_boundary", "negotiate_phasing"], + f"{city.id}_leader": ["collective_deliberation"] if mode == ProcessMode.FORMAL else ["broker_compromise", "authorize_pilot"], + } + base_observation: dict[str, object] = { + "quarter": float(self.world.quarter), + "fiscal_pressure": round(city.fiscal_pressure, 3), + "agenda_priority": round(state.agenda_priority, 3), + "coalition_support": round(state.coalition_support, 3), + "procedural_completeness": round(state.procedural_completeness, 3), + "finance_preconsulted": state.finance_preconsulted, + "negotiation_pressure": min(1.0, self.world.quarter / 4), + } + result = [] + for actor_id, base_candidates in pools.items(): + actor = self.world.agents[actor_id] + allowed_actions = [ + action_id for action_id in ROLE_ACTIONS[actor.role] + if action_id != "fund_due_diligence" and ( + mode == ProcessMode.HYBRID + or mode.value in ACTION_CATALOG[action_id].arenas + ) + ] + if mode != ProcessMode.FORMAL and "propose_open_action" not in allowed_actions: + allowed_actions.append("propose_open_action") + if not allowed_actions: + allowed_actions = list(base_candidates) + windows = [ + { + "id": item.id, "kind": item.kind, "title": item.title, + "magnitude": item.magnitude, "effects": dict(item.effects), + } + for item in self.dynamics.active_windows(city.id, actor_id) + ] + learning = self.dynamics.learning_context(actor_id) + planning_observation = { + **base_observation, + "active_windows": windows, + **learning, + } + plan = self._ensure_plan(actor_id, city.id, allowed_actions, planning_observation) + plan_node = self.dynamics.current_plan_node(plan) if plan else None + candidates = [item for item in base_candidates if item in allowed_actions] + if mode != ProcessMode.FORMAL and "propose_open_action" not in candidates: + candidates.append("propose_open_action") + if plan_node and plan_node.action_id in allowed_actions and plan_node.action_id not in candidates: + candidates.append(plan_node.action_id) + observation = { + **planning_observation, + "plan_id": plan.id if plan else None, + "plan_objective": plan.objective if plan else None, + "plan_selected_strategy": plan.selected_strategy if plan else None, + "plan_current_node": plan_node.id if plan_node else None, + "plan_current_action": plan_node.action_id if plan_node else None, + "plan_preconditions": plan_node.preconditions if plan_node else [], + } + self._round_plan_context[actor_id] = (plan, plan_node, observation) + if self.initiative_selector: + decision, action_id, urgency, target_id, rationale, provider, fallback = ( + self.initiative_selector(actor_id, candidates, observation) + ) + else: + decision, action_id, urgency, target_id = "act", candidates[0], 0.6, None + rationale, provider, fallback = "按角色目标主动发起", "scheduler", False + if decision not in {"act", "wait"}: + decision = "wait" + if decision == "act" and action_id not in candidates: + decision, action_id = "wait", "wait" + rationale = f"模型动作不在角色权限内,规则引擎改为等待;{rationale}" + if decision == "wait": + action_id = "wait" + result.append(( + actor_id, decision, action_id, min(1.0, max(0.0, urgency)), + target_id, (rationale, provider, fallback), candidates, + )) + return result + + def _ensure_plan( + self, actor_id: str, city_id: str, allowed_actions: list[str], + observation: dict[str, object], + ) -> OrganizationPlan | None: + plan = self.dynamics.active_plan(actor_id) + if plan or not self.plan_selector: + return plan + payload, provider, fallback = self.plan_selector(actor_id, allowed_actions, observation) + return self.dynamics.create_plan( + actor_id, city_id, payload, allowed_actions, provider, fallback + ) + + def _transition_candidates( + self, state: OrganizationProcessState, mode: ProcessMode, + ) -> list[str]: + status = state.formal_status + if mode == ProcessMode.INFORMAL: + return ["authorize_informal_offer", "continue_informal_coordination", "abandon_proposal"] + if status == "dormant": + values = ["start_formal_process", "pause_formal_process", "return_for_revision"] + elif status == "completed": + values = ["re_agenda", "keep_closed"] + elif status in {"paused", "returned"}: + values = ["resume_formal_process", "re_agenda", "abandon_proposal"] + else: + values = ["proceed_formal_review", "pause_formal_process", "return_for_revision"] + if mode == ProcessMode.HYBRID: + values.append("continue_informal_coordination") + return values + + def _choose_transition( + self, city: CityState, state: OrganizationProcessState, mode: ProcessMode, + ) -> tuple[str, tuple[str, str, bool]]: + candidates = self._transition_candidates(state, mode) + observation: dict[str, float | bool | str] = { + "quarter": float(self.world.quarter), + "formal_status": state.formal_status, + "agenda_priority": round(state.agenda_priority, 3), + "coalition_support": round(state.coalition_support, 3), + "procedural_completeness": round(state.procedural_completeness, 3), + "fiscal_pressure": round(city.fiscal_pressure, 3), + "return_count": float(state.return_count), + } + if self.transition_selector: + selected, rationale, provider, fallback = self.transition_selector( + f"{city.id}_leader", candidates, observation + ) + if selected in candidates: + return selected, (rationale, provider, fallback) + return candidates[0], ("按当前程序状态进入下一合法节点", "scheduler", False) + + @staticmethod + def _apply_transition(state: OrganizationProcessState, transition: str) -> bool: + state.last_transition = transition + if transition in { + "start_formal_process", "resume_formal_process", "re_agenda", + "proceed_formal_review", "authorize_informal_offer", + }: + state.formal_status = "active" + return True + if transition == "pause_formal_process": + state.formal_status = "paused" + state.paused_reason = "领导基于风险或协调成熟度主动暂停" + elif transition == "return_for_revision": + state.formal_status = "returned" + state.return_count += 1 + state.paused_reason = "退回补充材料或重组方案" + elif transition == "continue_informal_coordination": + state.formal_status = "paused" + state.paused_reason = "正式程序暂缓,继续非正式协调" + elif transition == "abandon_proposal": + state.formal_status = "abandoned" + state.paused_reason = "本轮议题退出议程" + return False + + def _initiative_record( + self, city: CityState, state: OrganizationProcessState, actor_id: str, + action_id: str, decision: str, urgency: float, target_actor_id: str | None, + selection_meta: tuple[str, str, bool], candidates: list[str], sequence: int, + blocked_reason: str, + ) -> OrganizationActionRecord: + definition = ACTION_CATALOG.get(action_id) + actor = self.world.agents[actor_id] + return OrganizationActionRecord( + id=f"org-action-{len(self.world.organization_actions)+1:05d}", + quarter=self.world.quarter, city_id=city.id, actor_id=actor_id, + actor_role=actor.role.value, action_id=action_id, + action_name=definition.name if definition else "暂不发起行动", + arena="initiative", process_mode=self.world.process_mode, + candidates=candidates, observations=self._state_observation(city, state), + rationale=selection_meta[0], effects={}, required=False, + authorized=False, blocked_reason=blocked_reason, + evidence_ids=list(definition.evidence_ids) if definition else ["attention"], + selection_provider=selection_meta[1], selection_rationale=selection_meta[0], + fallback=selection_meta[2], stage="initiative", sequence=sequence, + decision=decision, target_actor_id=target_actor_id, urgency=urgency, + ) + + def _transition_record( + self, city: CityState, state: OrganizationProcessState, transition: str, + selection_meta: tuple[str, str, bool], sequence: int, + ) -> OrganizationActionRecord: + names = { + "start_formal_process": "启动正式程序", "pause_formal_process": "暂停正式程序", + "return_for_revision": "退回修改", "re_agenda": "重新议程化", + "resume_formal_process": "恢复正式程序", "keep_closed": "维持结案", + "abandon_proposal": "退出本轮议程", "proceed_formal_review": "进入正式审议", + "authorize_informal_offer": "授权非正式方案进入报价", + "continue_informal_coordination": "继续非正式协调", + } + return OrganizationActionRecord( + id=f"org-action-{len(self.world.organization_actions)+1:05d}", + quarter=self.world.quarter, city_id=city.id, + actor_id=f"{city.id}_leader", actor_role=AgentRole.CITY_LEADER.value, + action_id=transition, action_name=names[transition], arena="procedure", + process_mode=self.world.process_mode, + candidates=self._transition_candidates(state, ProcessMode(self.world.process_mode)), + observations=self._state_observation(city, state), rationale=selection_meta[0], + effects={}, required=False, evidence_ids=["formal_procedure", "attention"], + selection_provider=selection_meta[1], selection_rationale=selection_meta[0], + fallback=selection_meta[2], stage="procedure_transition", sequence=sequence, + decision=transition, urgency=state.agenda_priority, + ) + + @staticmethod + def _state_observation( + city: CityState, state: OrganizationProcessState, + ) -> dict[str, float | bool | str]: + return { + "fiscal_pressure": round(city.fiscal_pressure, 3), + "agenda_priority": round(state.agenda_priority, 3), + "coalition_support": round(state.coalition_support, 3), + "procedural_completeness": round(state.procedural_completeness, 3), + "formal_status": state.formal_status, + } + + def _execute_open_action( + self, city: CityState, state: OrganizationProcessState, actor_id: str, + candidates: list[str], observation: dict[str, object], + selection_meta: tuple[str, str, bool], sequence: int, urgency: float, + target_actor_id: str | None, + ) -> OrganizationActionRecord: + if self.novel_action_selector: + payload, provider, fallback = self.novel_action_selector(actor_id, observation) + else: + payload = { + "title": "建立跨部门临时工作组", + "mechanism": "cross_department_taskforce", + "target_actor_id": f"{city.id}_leader", + "requested_effects": {"coalition_support_delta": 0.08}, + "resource_request": {}, + "rationale": "用临时协调结构降低信息损耗", + } + provider, fallback = "scheduler", False + proposal = self.dynamics.validate_open_action( + actor_id, city.id, payload, provider, fallback + ) + actor = self.world.agents[actor_id] + state.action_sequence.append(f"novel:{proposal.mechanism}") + return OrganizationActionRecord( + id=f"org-action-{len(self.world.organization_actions)+1:05d}", + quarter=self.world.quarter, city_id=city.id, actor_id=actor_id, + actor_role=actor.role.value, action_id="propose_open_action", + action_name=proposal.title, arena="informal", + process_mode=self.world.process_mode, candidates=candidates, + observations={**self._state_observation(city, state), "novel_mechanism": proposal.mechanism}, + rationale=proposal.rationale, effects=dict(proposal.executed_effects), + required=False, authorized=proposal.status == "authorized", + blocked_reason=None if proposal.status == "authorized" else proposal.validation_reason, + evidence_ids=["informal_networks", "attention"], + selection_provider=provider, + selection_rationale=( + f"{selection_meta[0]};开放行动审查:{proposal.validation_reason}" + ), + fallback=fallback, stage="initiative", sequence=sequence, + decision="act", target_actor_id=proposal.target_actor_id or target_actor_id, + urgency=urgency, open_action_proposal_id=proposal.id, + ) + + def _execute( + self, city: CityState, state: OrganizationProcessState, + actor_id: str, action_id: str, required: bool, + selection_meta: tuple[str, str, bool], + *, stage: str = "coordination", sequence: int = 0, + urgency: float = 0.5, target_actor_id: str | None = None, + ) -> OrganizationActionRecord: + definition = ACTION_CATALOG[action_id] + actor = self.world.agents[actor_id] + arena = "control" if action_id == "fiscal_guardrail" else ( + "formal" if "formal" in definition.arenas and action_id in { + "request_materials", "verify_site_readiness", "risk_assessment", "legality_review", + "collective_deliberation", "fund_due_diligence", + } else "informal" + ) + if action_id == "clarify_need": + arena = "formal" if self.world.process_mode == ProcessMode.FORMAL else "informal" + candidates = [ + item for item in ROLE_ACTIONS[actor.role] + if arena in ACTION_CATALOG[item].arenas or ( + arena == "control" and "control" in ACTION_CATALOG[item].arenas + ) + ] + effects = self._effects(city, state, actor, action_id) + state.action_sequence.append(action_id) + return OrganizationActionRecord( + id=f"org-action-{len(self.world.organization_actions)+1:05d}", + quarter=self.world.quarter, + city_id=city.id, + actor_id=actor_id, + actor_role=actor.role.value, + action_id=action_id, + action_name=definition.name, + arena=arena, + process_mode=self.world.process_mode, + candidates=candidates, + observations={ + "fiscal_pressure": round(city.fiscal_pressure, 3), + "agenda_priority": round(state.agenda_priority, 3), + "coalition_support": round(state.coalition_support, 3), + "procedural_completeness": round(state.procedural_completeness, 3), + }, + rationale=definition.rationale, + effects=effects, + required=required, + evidence_ids=list(definition.evidence_ids), + selection_rationale=selection_meta[0], + selection_provider=selection_meta[1], + fallback=selection_meta[2], + stage=stage, + sequence=sequence, + decision="act", + target_actor_id=target_actor_id, + urgency=urgency, + ) + + def _effects( + self, city: CityState, state: OrganizationProcessState, + actor: AgentState, action_id: str, + ) -> dict[str, float]: + effects: dict[str, float] = {} + if action_id == "request_materials": + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.12) + effects["approval_speed_bonus"] = -0.025 + elif action_id == "verify_site_readiness": + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.1) + effects["approval_speed_bonus"] = 0.015 + elif action_id == "clarify_need": + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.1) + effects["credibility_delta"] = 0.006 + elif action_id == "preconsult_finance": + state.finance_preconsulted = True + state.relationships["investment-finance"] = min( + 1.0, state.relationships.get("investment-finance", 0.42) + 0.1 + ) + effects["equity_shift"] = 0.08 + elif action_id == "frame_strategic_project": + state.agenda_priority = min(1.0, state.agenda_priority + 0.16) + effects["proposal_aggressiveness"] = 0.06 + elif action_id == "mobilize_park_coalition": + state.coalition_support = min(1.0, state.coalition_support + 0.14) + effects["approval_speed_bonus"] = 0.035 + elif action_id == "risk_assessment": + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.24) + risk = city.fiscal_pressure + actor.traits.get("risk_aversion", 0.7) * 0.45 + if risk > 0.76: + state.risk_posture = "conservative" + elif ( + state.finance_preconsulted + and state.coalition_support > 0.44 + and risk < 0.72 + ): + state.risk_posture = "developmental" + else: + state.risk_posture = "balanced" + elif action_id == "disclose_fiscal_boundary": + state.finance_preconsulted = True + state.relationships["investment-finance"] = min( + 1.0, state.relationships.get("investment-finance", 0.42) + 0.08 + ) + effects["proposal_aggressiveness"] = -0.04 + elif action_id == "negotiate_phasing": + state.finance_preconsulted = True + effects["equity_shift"] = 0.12 + elif action_id == "legality_review": + state.legal_reviewed = True + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.28) + state.legitimacy = min(1.0, state.legitimacy + 0.06) + elif action_id == "collective_deliberation": + state.collective_deliberated = True + state.procedural_completeness = min(1.0, state.procedural_completeness + 0.24) + state.legitimacy = min(1.0, state.legitimacy + 0.05) + elif action_id == "broker_compromise": + state.coalition_support = min(1.0, state.coalition_support + 0.1) + effects["equity_shift"] = 0.1 + elif action_id == "authorize_pilot": + state.pilot_authorized = True + effects["proposal_aggressiveness"] = -0.05 + effects["approval_speed_bonus"] = 0.05 + elif action_id == "fiscal_guardrail": + # Without a formal risk and legality file, finance applies a precautionary + # haircut. This changes a policy choice, never the underlying budget. + state.risk_posture = ( + "conservative" if not state.legal_reviewed else "balanced" + ) + return effects + + @staticmethod + def _merge_effects(target: OrganizationRoundEffects, effects: dict[str, float]) -> None: + target.proposal_aggressiveness += effects.get("proposal_aggressiveness", 0.0) + target.equity_shift += effects.get("equity_shift", 0.0) + target.approval_speed_bonus += effects.get("approval_speed_bonus", 0.0) + target.credibility_delta += effects.get("credibility_delta", 0.0) + + +def behavior_evidence_payload() -> list[dict[str, str]]: + return [{"id": key, **value} for key, value in BEHAVIOR_EVIDENCE.items()] + + +def action_catalog_payload() -> list[dict[str, object]]: + return [ + { + "id": item.id, + "name": item.name, + "roles": [role.value for role in item.roles], + "arenas": list(item.arenas), + "rationale": item.rationale, + "evidence_ids": list(item.evidence_ids), + } + for item in ACTION_CATALOG.values() + ] diff --git a/src/insidegov/organization_dynamics.py b/src/insidegov/organization_dynamics.py new file mode 100644 index 0000000..9592cd1 --- /dev/null +++ b/src/insidegov/organization_dynamics.py @@ -0,0 +1,510 @@ +from __future__ import annotations + +import random +from dataclasses import asdict +from typing import Any + +from .models import ( + AgentRole, + Event, + OpenActionProposal, + OpportunityWindow, + OrganizationActionRecord, + OrganizationLearningState, + OrganizationPlan, + OrganizationPlanNode, + OrganizationProcessState, + PlanStrategyOption, + WorldState, +) + +ROLE_MECHANISMS: dict[AgentRole, set[str]] = { + AgentRole.INVESTMENT: { + "upward_endorsement", "association_coalition", "demonstration_project", + "meeting_window", "strategic_delay", "expert_consultation", + }, + AgentRole.PARK: { + "association_coalition", "demonstration_project", "strategic_delay", + "expert_consultation", + }, + AgentRole.FINANCE: {"strategic_delay", "expert_consultation"}, + AgentRole.CITY_LEADER: { + "upward_endorsement", "demonstration_project", "meeting_window", + "strategic_delay", "cross_department_taskforce", "expert_consultation", + }, + AgentRole.LEGAL: {"strategic_delay", "expert_consultation"}, +} + +EFFECT_CAPS: dict[str, float] = { + "agenda_priority_delta": 0.18, + "coalition_support_delta": 0.15, + "procedural_completeness_delta": 0.10, + "approval_speed_bonus": 0.05, + "credibility_delta": 0.012, + "attention_budget_delta": 1.0, +} + + +class OrganizationDynamics: + """Plans, political opportunity windows, permission checks and learning.""" + + def __init__(self, world: WorldState): + self.world = world + + def advance_opportunity_windows(self) -> list[OpportunityWindow]: + quarter = self.world.quarter + for window in self.world.opportunity_windows: + if window.status == "active" and window.end_quarter < quarter: + window.status = "closed" + created: list[OpportunityWindow] = [] + for city_index, (city_id, city) in enumerate(self.world.cities.items()): + state = self.world.organization_processes.setdefault( + city_id, OrganizationProcessState(city_id=city_id) + ) + state.attention_budget = 2 + schedule = random.Random(f"insidegov:window-schedule:{self.world.seed}:{city_id}") + meeting_quarter = 1 + schedule.randrange(4) + leadership_quarter = 4 + schedule.randrange(5) + candidates: list[tuple[str, str, float, str, dict[str, float]]] = [] + if (quarter - meeting_quarter) % 4 == 0: + candidates.append(( + "major_meeting", "重大会议形成集中议程窗口", 0.72, + f"城市年度会议日历:第{meeting_quarter}模4季度", + {"agenda_priority_delta": 0.10, "attention_budget_delta": 1.0}, + )) + superior_rng = random.Random( + f"insidegov:superior-policy:{self.world.seed}:{city_id}:q{quarter}" + ) + if superior_rng.random() < 0.20: + candidates.append(( + "superior_policy", "上级产业政策释放支持信号", 0.65, + "上级政策流中的可观察信号", + {"agenda_priority_delta": 0.12, "coalition_support_delta": 0.05}, + )) + competing = [ + other.id for other in self.world.cities.values() + if other.id != city_id and other.active_offer is not None + ] + if competing: + candidates.append(( + "competing_city_offer", "竞争城市报价打开抢签窗口", 0.7, + f"可观察到竞争城市已报价:{','.join(competing)}", + {"agenda_priority_delta": 0.14, "attention_budget_delta": 1.0}, + )) + if city.fiscal_pressure > 0.62: + candidates.append(( + "fiscal_pressure", "财政收入与承诺压力收窄政策窗口", city.fiscal_pressure, + "财政账本中的压力指标超过0.62", + {"agenda_priority_delta": -0.08}, + )) + incident_rng = random.Random( + f"insidegov:public-attention:{self.world.seed}:{city_id}:q{quarter}" + ) + if incident_rng.random() < 0.08: + candidates.append(( + "public_attention", "舆情事件改变领导注意力配置", 0.58, + "外生公共注意力冲击", + {"agenda_priority_delta": 0.11, "coalition_support_delta": -0.04}, + )) + if quarter == leadership_quarter: + candidates.append(( + "leadership_change", "部门负责人调整触发记忆继承", 0.8, + "世界生成时确定的任期节点", + {"agenda_priority_delta": -0.04}, + )) + for kind, title, magnitude, trigger, effects in candidates: + window_id = f"window-{city_id}-q{quarter}-{kind}" + if any(item.id == window_id for item in self.world.opportunity_windows): + continue + observed = [ + agent_id for agent_id, agent in self.world.agents.items() + if agent.owner_id == city_id + ] + window = OpportunityWindow( + id=window_id, city_id=city_id, kind=kind, title=title, + start_quarter=quarter, end_quarter=quarter + (1 if kind != "major_meeting" else 0), + magnitude=round(magnitude, 3), source="world_state", trigger=trigger, + effects=effects, observed_by=observed, + ) + self.world.opportunity_windows.append(window) + created.append(window) + self._apply_window(window, state) + self.world.events.append(Event( + quarter, "opportunity_window", title, + f"{trigger};状态影响 {effects}", city_id, severity="warning" + if kind in {"fiscal_pressure", "public_attention", "leadership_change"} + else "info", + )) + for city_id, state in self.world.organization_processes.items(): + active = self.active_windows(city_id) + state.attention_budget = min( + 4, + 2 + sum(int(item.effects.get("attention_budget_delta", 0)) for item in active), + ) + return created + + def active_windows(self, city_id: str, actor_id: str | None = None) -> list[OpportunityWindow]: + return [ + item for item in self.world.opportunity_windows + if item.status == "active" + and item.start_quarter <= self.world.quarter <= item.end_quarter + and item.city_id in {None, city_id} + and (actor_id is None or not item.observed_by or actor_id in item.observed_by) + ] + + def _apply_window(self, window: OpportunityWindow, state: OrganizationProcessState) -> None: + if window.applied: + return + state.agenda_priority = min( + 1.0, max(0.0, state.agenda_priority + window.effects.get("agenda_priority_delta", 0)) + ) + state.coalition_support = min( + 1.0, max(0.0, state.coalition_support + window.effects.get("coalition_support_delta", 0)) + ) + if window.kind == "fiscal_pressure": + state.risk_posture = "conservative" + if window.kind == "leadership_change" and window.city_id: + self._leadership_succession(window.city_id) + window.applied = True + + def learning(self, agent_id: str) -> OrganizationLearningState: + if agent_id not in self.world.organization_learning: + self.world.organization_learning[agent_id] = OrganizationLearningState( + agent_id=agent_id, + trust_by_actor={ + other_id: 0.5 for other_id, other in self.world.agents.items() + if other.owner_id == self.world.agents[agent_id].owner_id + and other_id != agent_id + }, + ) + return self.world.organization_learning[agent_id] + + def learning_context(self, agent_id: str) -> dict[str, Any]: + state = self.learning(agent_id) + return { + "trust_by_actor": dict(state.trust_by_actor), + "firm_type_beliefs": dict(state.firm_type_beliefs), + "veto_count": state.veto_count, + "strategy_preferences": dict(state.strategy_preferences), + "routines": dict(state.routines), + "leadership_generation": state.leadership_generation, + "recent_lessons": state.lessons[-5:], + "transferable_lessons": state.transferable_lessons[-3:], + } + + def create_plan( + self, actor_id: str, city_id: str, payload: dict[str, Any], + allowed_actions: list[str], provider: str, fallback: bool, + ) -> OrganizationPlan: + current = self.world.quarter + alternatives = [] + for index, item in enumerate(payload.get("alternatives", []), start=1): + if not isinstance(item, dict): + continue + alternatives.append(PlanStrategyOption( + id=str(item.get("id") or f"strategy_{index}"), + name=str(item.get("name") or item.get("id") or f"策略{index}"), + approach=str(item.get("approach") or "分阶段推进"), + benefits=list(item.get("benefits", [])), + risks=list(item.get("risks", [])) or ([str(item["risk"])] if item.get("risk") else []), + score=float(item.get("score") or 0.5), + )) + if not alternatives: + alternatives = [ + PlanStrategyOption("fallback", "可执行路径", "按职责边界逐步推进", score=0.5) + ] + allowed = set(allowed_actions) + raw_steps = payload.get("steps", []) + if not raw_steps and isinstance(payload.get("action_sequence"), list): + raw_steps = [ + { + "id": f"s{index}", "title": f"执行 {action_id}", + "action_id": action_id, "earliest_offset": index - 1, + "latest_offset": index + 1, + "preconditions": [] if index == 1 else [f"s{index-1}已完成或窗口发生"], + "on_success": f"s{index+1}" if index < len(payload["action_sequence"]) else None, + "on_failure": "replan", "expected_effects": {}, + } + for index, action_id in enumerate(payload["action_sequence"], start=1) + ] + nodes: list[OrganizationPlanNode] = [] + for index, row in enumerate(raw_steps, start=1): + action_id = str(row.get("action_id", "")) + if action_id not in allowed: + action_id = allowed_actions[0] + earliest = current + int(row.get("earliest_offset", index - 1)) + latest = max(earliest, current + int(row.get("latest_offset", index + 1))) + nodes.append(OrganizationPlanNode( + id=str(row.get("id") or f"s{index}"), + title=str(row.get("title") or action_id), action_id=action_id, + earliest_quarter=earliest, latest_quarter=latest, + preconditions=list(row.get("preconditions", [])), + on_success=row.get("on_success"), on_failure=row.get("on_failure"), + expected_effects={ + key: float(value) for key, value in row.get("expected_effects", {}).items() + if isinstance(value, (int, float)) + }, + )) + if not nodes: + nodes = [ + OrganizationPlanNode("s1", allowed_actions[0], allowed_actions[0], current, current + 1), + OrganizationPlanNode("s2", allowed_actions[-1], allowed_actions[-1], current + 1, current + 3), + ] + selected_strategy = str(payload.get("selected_strategy") or alternatives[0].id) + if selected_strategy not in {item.id for item in alternatives}: + selected_strategy = max(alternatives, key=lambda item: item.score).id + plan = OrganizationPlan( + id=f"plan-{len(self.world.organization_plans)+1:04d}", + city_id=city_id, actor_id=actor_id, created_quarter=current, + horizon_quarter=max(node.latest_quarter for node in nodes), + objective=str(payload.get("objective") or "推进当前议题"), + alternatives=alternatives, + selected_strategy=selected_strategy, + nodes=nodes, + assumptions=[ + *list(payload.get("assumptions", [])), + *([str(payload["branch_condition"])] if payload.get("branch_condition") else []), + ], + rationale=str(payload.get("rationale") or "根据当前状态选择可执行路径"), + provider=provider, fallback=fallback, + ) + self.world.organization_plans.append(plan) + return plan + + def active_plan(self, actor_id: str) -> OrganizationPlan | None: + for plan in reversed(self.world.organization_plans): + if plan.actor_id == actor_id and plan.status == "active": + if self.world.quarter > plan.horizon_quarter: + plan.status = "expired" + self._transfer_plan_lesson(plan, "计划超出期限,需重新规划") + continue + return plan + return None + + def current_plan_node(self, plan: OrganizationPlan) -> OrganizationPlanNode | None: + for node in plan.nodes: + if node.status == "pending" and self.world.quarter > node.latest_quarter: + node.status = "deviated" + node.deviation_reason = "超过计划最晚执行季度" + plan.review_history.append({ + "quarter": self.world.quarter, "node_id": node.id, + "outcome": "deadline_missed", "reason": node.deviation_reason, + }) + pending = [node for node in plan.nodes if node.status == "pending"] + if not pending: + plan.status = "completed" + self._transfer_plan_lesson(plan, "计划节点已完成或形成可解释偏离") + return None + for index, node in enumerate(plan.nodes): + if node.status != "pending" or self.world.quarter < node.earliest_quarter: + continue + previous_ok = index == 0 or plan.nodes[index - 1].status in {"completed", "deviated", "skipped"} + window_open = bool(self.active_windows(plan.city_id)) + if previous_ok or window_open: + return node + return pending[0] if pending[0].earliest_quarter <= self.world.quarter else None + + def review_plan_action( + self, plan: OrganizationPlan | None, node: OrganizationPlanNode | None, + record: OrganizationActionRecord, + ) -> None: + if not plan or not node: + return + record.plan_id = plan.id + record.plan_node_id = node.id + if record.decision == "act" and record.authorized: + node.actual_action_id = record.action_id + node.executed_quarter = self.world.quarter + if record.action_id in {node.action_id, f"novel:{record.open_action_proposal_id}"} or ( + node.action_id == "propose_open_action" and record.open_action_proposal_id + ): + node.status = "completed" + outcome = "executed_as_planned" + else: + node.status = "deviated" + node.deviation_reason = f"实际执行{record.action_id},原计划为{node.action_id}" + record.deviation_reason = node.deviation_reason + outcome = "action_deviation" + elif record.decision in {"wait", "deferred"}: + outcome = record.decision + else: + node.status = "deviated" + node.deviation_reason = record.blocked_reason or "动作未获授权" + record.deviation_reason = node.deviation_reason + outcome = "blocked" + plan.review_history.append({ + "quarter": self.world.quarter, "node_id": node.id, + "planned_action": node.action_id, "actual_action": record.action_id, + "outcome": outcome, "reason": record.deviation_reason or record.blocked_reason, + }) + + def validate_open_action( + self, actor_id: str, city_id: str, payload: dict[str, Any], + provider: str, fallback: bool, + ) -> OpenActionProposal: + actor = self.world.agents[actor_id] + proposal = OpenActionProposal( + id=f"open-action-{len(self.world.open_action_proposals)+1:04d}", + quarter=self.world.quarter, city_id=city_id, actor_id=actor_id, + title=str(payload.get("title") or "未命名组织行动"), + mechanism=str(payload.get("mechanism") or "strategic_delay"), + target_actor_id=payload.get("target_actor_id"), + requested_effects={ + key: float(value) for key, value in payload.get("requested_effects", {}).items() + if isinstance(value, (int, float)) + }, + resource_request={ + key: float(value) for key, value in payload.get("resource_request", {}).items() + if isinstance(value, (int, float)) + }, + rationale=str(payload.get("rationale") or "提出新的组织协调手段"), + provider=provider, fallback=fallback, + ) + allowed_mechanisms = ROLE_MECHANISMS.get(actor.role, set()) + forbidden_resources = { + key: value for key, value in proposal.resource_request.items() + if value > 0 and key not in {"staff_time", "meeting_slots"} + } + invalid_effects = set(proposal.requested_effects) - set(EFFECT_CAPS) + if proposal.mechanism not in allowed_mechanisms: + proposal.status = "rejected" + proposal.validation_reason = f"{actor.role.value}无权使用{proposal.mechanism}机制" + elif forbidden_resources: + proposal.status = "rejected" + proposal.validation_reason = f"新行动不得创造财政或物质资源:{sorted(forbidden_resources)}" + elif invalid_effects: + proposal.status = "rejected" + proposal.validation_reason = f"存在不可执行状态维度:{sorted(invalid_effects)}" + else: + proposal.status = "authorized" + proposal.validation_reason = "职责、资源与状态效果均通过规则审查" + proposal.executed_effects = { + key: round(max(-cap, min(cap, value)), 4) + for key, value in proposal.requested_effects.items() + if (cap := EFFECT_CAPS.get(key)) is not None + } + self._apply_open_effects(city_id, proposal.executed_effects) + self.world.open_action_proposals.append(proposal) + return proposal + + def _apply_open_effects(self, city_id: str, effects: dict[str, float]) -> None: + state = self.world.organization_processes[city_id] + state.agenda_priority = min( + 1.0, max(0.0, state.agenda_priority + effects.get("agenda_priority_delta", 0)) + ) + state.coalition_support = min( + 1.0, max(0.0, state.coalition_support + effects.get("coalition_support_delta", 0)) + ) + state.procedural_completeness = min( + 1.0, + max(0.0, state.procedural_completeness + effects.get("procedural_completeness_delta", 0)), + ) + state.attention_budget = min( + 4, max(1, state.attention_budget + int(effects.get("attention_budget_delta", 0))) + ) + + def learn_from_action(self, record: OrganizationActionRecord) -> None: + learning = self.learning(record.actor_id) + learning.action_attempts[record.action_id] = learning.action_attempts.get(record.action_id, 0) + 1 + success = record.authorized and record.decision == "act" + if success: + learning.action_successes[record.action_id] = learning.action_successes.get(record.action_id, 0) + 1 + reward = 0.08 if success else -0.05 + reward += min(0.12, sum(value for value in record.effects.values() if value > 0) * 0.2) + prior = learning.strategy_preferences.get(record.action_id, 0.5) + learning.strategy_preferences[record.action_id] = round( + min(1.0, max(0.0, prior * 0.78 + (0.5 + reward) * 0.22)), 3 + ) + if record.target_actor_id: + trust = learning.trust_by_actor.get(record.target_actor_id, 0.5) + learning.trust_by_actor[record.target_actor_id] = round( + min(1.0, max(0.0, trust + (0.035 if success else -0.04))), 3 + ) + attempts = learning.action_attempts[record.action_id] + successes = learning.action_successes.get(record.action_id, 0) + if attempts >= 3 and successes / attempts >= 0.66: + learning.routines[record.action_id] = round(successes / attempts, 3) + lesson = ( + f"Q{record.quarter} {record.action_name}" + f"{'得到执行' if success else '未执行'};偏离={record.deviation_reason or '无'}" + ) + if not learning.lessons or learning.lessons[-1] != lesson: + learning.lessons.append(lesson) + learning.lessons = learning.lessons[-20:] + + def record_veto(self, city_id: str, rationale: str) -> None: + investment_id = f"{city_id}_investment" + learning = self.learning(investment_id) + learning.veto_count += 1 + finance_id = f"{city_id}_finance" + trust = learning.trust_by_actor.get(finance_id, 0.5) + learning.trust_by_actor[finance_id] = round(max(0.0, trust - 0.025), 3) + learning.strategy_preferences["negotiate_phasing"] = min( + 1.0, learning.strategy_preferences.get("negotiate_phasing", 0.5) + 0.08 + ) + lesson = f"第{learning.veto_count}次财政否决:{rationale}" + learning.lessons.append(lesson) + if learning.veto_count >= 3: + transferable = "连续财政否决后,应由高现金方案转向分期、试点或联盟论证" + if transferable not in learning.transferable_lessons: + learning.transferable_lessons.append(transferable) + + def record_successful_coordination(self, city_id: str) -> None: + investment = self.learning(f"{city_id}_investment") + investment.successful_coordination_count += 1 + finance_id = f"{city_id}_finance" + investment.trust_by_actor[finance_id] = round( + min(1.0, investment.trust_by_actor.get(finance_id, 0.5) + 0.04), 3 + ) + + def record_enterprise_response(self, city_id: str, response: str) -> None: + updates = { + "accept": {"commitment_reliability": 0.08}, + "counter": {"bargaining_intensity": 0.08}, + "terminate": {"exit_sensitivity": 0.1}, + }[response] + for suffix in ("investment", "leader"): + learning = self.learning(f"{city_id}_{suffix}") + for key, delta in updates.items(): + learning.firm_type_beliefs[key] = round( + min(1.0, learning.firm_type_beliefs.get(key, 0.5) * 0.82 + (0.5 + delta) * 0.18), + 3, + ) + + def _leadership_succession(self, city_id: str) -> None: + leader_id = f"{city_id}_leader" + agent = self.world.agents.get(leader_id) + if not agent: + return + learning = self.learning(leader_id) + inheritance = 0.6 + keep = max(1, round(len(agent.memories) * inheritance)) if agent.memories else 0 + agent.memories = agent.memories[-keep:] if keep else [] + agent.last_reflection = "负责人调整:继承制度性档案,但个人关系与策略偏好部分重置" + learning.leadership_generation += 1 + learning.inherited_memory_ratio = inheritance + learning.trust_by_actor = { + actor_id: round(0.5 + (trust - 0.5) * inheritance, 3) + for actor_id, trust in learning.trust_by_actor.items() + } + learning.strategy_preferences = { + action_id: round(0.5 + (score - 0.5) * inheritance, 3) + for action_id, score in learning.strategy_preferences.items() + } + learning.lessons.append("领导更替后保留组织惯例与制度档案,弱化个人关系记忆") + + def _transfer_plan_lesson(self, plan: OrganizationPlan, reason: str) -> None: + learning = self.learning(plan.actor_id) + deviations = sum(node.status == "deviated" for node in plan.nodes) + lesson = f"计划{plan.id}({plan.selected_strategy})结束:{reason};偏离{deviations}个节点" + if lesson not in learning.transferable_lessons: + learning.transferable_lessons.append(lesson) + learning.transferable_lessons = learning.transferable_lessons[-10:] + + @staticmethod + def payload(value: Any) -> dict[str, Any]: + if hasattr(value, "model_dump"): + return value.model_dump() + if hasattr(value, "__dataclass_fields__"): + return asdict(value) + return dict(value) diff --git a/src/insidegov/scenarios.py b/src/insidegov/scenarios.py index bce00be..0da1324 100644 --- a/src/insidegov/scenarios.py +++ b/src/insidegov/scenarios.py @@ -12,6 +12,7 @@ FirmType, GovBelief, LatentNeed, + OrganizationProcessState, Phase, PlatformState, StatedNeed, @@ -193,6 +194,18 @@ def seeded_city( }, traits={"risk_aversion": 0.24, "short_termism": 0.78, "trust_sensitivity": 0.42}, ) + agents[f"{city.id}_legal"] = AgentState( + id=f"{city.id}_legal", name=f"{city.name}司法审查机构", role=AgentRole.LEGAL, + owner_id=city.id, goals=["保证权限合法", "识别程序瑕疵", "提高承诺可执行性"], + private_facts={"review_capacity": city.administrative_capacity / 100}, + traits={"risk_aversion": 0.72, "short_termism": 0.18, "trust_sensitivity": 0.55}, + ) + agents[f"{city.id}_park"] = AgentState( + id=f"{city.id}_park", name=f"{city.name}产业园区", role=AgentRole.PARK, + owner_id=city.id, goals=["形成产业集聚", "提高土地利用率", "协调项目执行"], + private_facts={"land_pressure": max(0.0, 1 - city.industrial_land / 800)}, + traits={"risk_aversion": 0.38, "short_termism": 0.58, "trust_sensitivity": 0.66}, + ) agents["firm_nova_board"] = AgentState( id="firm_nova_board", name="星澜显示董事会", role=AgentRole.ENTERPRISE, owner_id="firm_nova", goals=["提高长期投资回报", "降低政策与建设风险", "获得稳定供应链"], @@ -252,6 +265,7 @@ def seeded_city( events=[], traces=[], action_audits=[], + organization_actions=[], history=[], interventions=[], negotiation_protocol="clarify_first", @@ -266,6 +280,10 @@ def seeded_city( trust=firms["firm_nova"].perceived_credibility[city_id], ) for city_id in cities }, + organization_processes={ + city_id: OrganizationProcessState(city_id=city_id) + for city_id in cities + }, ) @@ -451,6 +469,7 @@ def create_talent_world( events=[], traces=[], action_audits=[], + organization_actions=[], history=[], interventions=[], talents=talents, diff --git a/src/insidegov/serde.py b/src/insidegov/serde.py index 359e1d9..236d58f 100644 --- a/src/insidegov/serde.py +++ b/src/insidegov/serde.py @@ -23,8 +23,16 @@ MetricsSnapshot, NegotiationRecord, NegotiationRound, + OpenActionProposal, + OpportunityWindow, + OrganizationActionRecord, + OrganizationLearningState, + OrganizationPlan, + OrganizationPlanNode, + OrganizationProcessState, PaymentTranche, Phase, + PlanStrategyOption, PlatformState, PolicyPackage, Promise, @@ -80,6 +88,22 @@ def world_from_dict(data: dict) -> WorldState: private_facts={"signing_target": 1.0, "cash_preference": 0.6, "competitive_intensity": 0.7}, traits={"risk_aversion": 0.24, "short_termism": 0.78, "trust_sensitivity": 0.42}, ) + legal_id = f"{city.id}_legal" + if legal_id not in agents: + agents[legal_id] = AgentState( + id=legal_id, name=f"{city.name}司法审查机构", role=AgentRole.LEGAL, + owner_id=city.id, goals=["保证权限合法", "识别程序瑕疵"], + private_facts={"review_capacity": city.administrative_capacity / 100}, + traits={"risk_aversion": 0.72, "short_termism": 0.18, "trust_sensitivity": 0.55}, + ) + park_id = f"{city.id}_park" + if park_id not in agents: + agents[park_id] = AgentState( + id=park_id, name=f"{city.name}产业园区", role=AgentRole.PARK, + owner_id=city.id, goals=["形成产业集聚", "协调项目执行"], + private_facts={"land_pressure": max(0.0, 1 - city.industrial_land / 800)}, + traits={"risk_aversion": 0.38, "short_termism": 0.58, "trust_sensitivity": 0.66}, + ) return WorldState( id=data["id"], name=data["name"], seed=data["seed"], quarter=data["quarter"], phase=Phase(data["phase"]), cities=cities, firms=firms, agents=agents, @@ -95,6 +119,32 @@ def world_from_dict(data: dict) -> WorldState: events=[Event(**e) for e in data.get("events", [])], traces=[DecisionTrace(**t) for t in data.get("traces", [])], action_audits=[AgentActionAudit(**item) for item in data.get("action_audits", [])], + organization_actions=[ + OrganizationActionRecord(**item) + for item in data.get("organization_actions", []) + ], + organization_plans=[ + OrganizationPlan(**{ + **item, + "alternatives": [ + PlanStrategyOption(**row) for row in item.get("alternatives", []) + ], + "nodes": [ + OrganizationPlanNode(**row) for row in item.get("nodes", []) + ], + }) + for item in data.get("organization_plans", []) + ], + open_action_proposals=[ + OpenActionProposal(**item) for item in data.get("open_action_proposals", []) + ], + opportunity_windows=[ + OpportunityWindow(**item) for item in data.get("opportunity_windows", []) + ], + organization_learning={ + key: OrganizationLearningState(**item) + for key, item in data.get("organization_learning", {}).items() + }, history=[MetricsSnapshot(**{**h, "phase": Phase(h["phase"])}) for h in data.get("history", [])], interventions=[Intervention(**i) for i in data.get("interventions", [])], intervention_plans=[InterventionPlan(**{ @@ -110,8 +160,18 @@ def world_from_dict(data: dict) -> WorldState: parameter_provenance=data.get("parameter_provenance", {}), market_demand=data.get("market_demand", 100.0), demand_multiplier=data.get("demand_multiplier", 1.0), market_price=data.get("market_price", 1.0), selected_city_id=data.get("selected_city_id"), + recruitment_status=data.get("recruitment_status", "active"), + negotiation_round_limit=data.get("negotiation_round_limit", 6), parent_id=data.get("parent_id"), policy_mode=data.get("policy_mode", "deterministic"), model_name=data.get("model_name"), + process_mode=data.get("process_mode", "hybrid"), + organization_processes={ + key: OrganizationProcessState(**item) + for key, item in data.get("organization_processes", {}).items() + } or { + city_id: OrganizationProcessState(city_id=city_id) + for city_id in cities + }, mechanisms=data.get("mechanisms", { "private_information": True, "internal_governance": True, "credibility_diffusion": True, "supplier_spillover": True, diff --git a/tests/test_engine.py b/tests/test_engine.py index 88aa2bf..3851581 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -1,7 +1,9 @@ from insidegov.agents import ( DeterministicCognition, + EnterpriseResponseAction, FinanceAction, ResolutionAction, + _repair_structured_payload, ) from insidegov.engine import SimulationEngine from insidegov.experiments import run_comparison @@ -15,6 +17,16 @@ def test_reproducible_for_same_seed(): assert a.to_dict()["history"] == b.to_dict()["history"] +def test_chinese_enterprise_response_is_repaired_to_structured_protocol(): + repaired = _repair_structured_payload( + EnterpriseResponseAction, + '{"response":"还价","counter_terms":{"subsidy_floor":9},"confidence":0.8,"rationale":"保障不足"}', + ) + action = EnterpriseResponseAction.model_validate(repaired) + assert action.response == "counter" + assert action.counter_terms["subsidy_floor"] == 9 + + def test_seed_changes_world_conditions_and_population_outcomes(): worlds = [ SimulationEngine(create_full_lifecycle_world(seed, str(seed))).run(16) @@ -68,7 +80,8 @@ def test_internal_governance_creates_real_veto_and_memory_chain(): assert len(lin.payment_schedule) >= 3 assert [turn["act"] for turn in lin.turns] == ["proposal", "review", "coordination"] audits = [item for item in world.action_audits if item.quarter == 1] - assert len(audits) == 12 + assert len(audits) == 13 + assert any(item.action_type == "decide_negotiation_timing" for item in audits) assert len(world.external_negotiations) == 3 assert all(item.enterprise_response in {"accept", "counter", "terminate"} for item in world.external_negotiations) finance_audit = next( diff --git a/tests/test_organization.py b/tests/test_organization.py new file mode 100644 index 0000000..8f52439 --- /dev/null +++ b/tests/test_organization.py @@ -0,0 +1,290 @@ +from fastapi.testclient import TestClient + +from insidegov.agents import ( + DeterministicCognition, + EnterpriseResponseAction, + NegotiationTimingAction, + OrganizationActionChoice, + OrganizationInitiativeChoice, + ProcedureTransitionChoice, +) +from insidegov.api import app +from insidegov.engine import SimulationEngine +from insidegov.experiments import run_organization_mode_comparison +from insidegov.models import MemoryRecord +from insidegov.organization import ROLE_ACTIONS +from insidegov.organization_dynamics import OrganizationDynamics +from insidegov.scenarios import create_full_lifecycle_world + +client = TestClient(app) + + +def run_mode(mode: str, quarters: int = 3): + world = create_full_lifecycle_world(42, f"test-{mode}") + world.process_mode = mode + return SimulationEngine(world).run(quarters) + + +def test_roles_have_different_action_sets(): + unique_sets = {actions for actions in ROLE_ACTIONS.values() if actions} + assert len(unique_sets) >= 5 + assert "preconsult_finance" in ROLE_ACTIONS[next( + role for role in ROLE_ACTIONS if role.value == "investment" + )] + assert "legality_review" in ROLE_ACTIONS[next( + role for role in ROLE_ACTIONS if role.value == "legal" + )] + + +def test_formal_mode_enforces_legal_and_collective_gates(): + world = run_mode("formal", 1) + for city_id in world.cities: + actions = [ + item.action_id for item in world.organization_actions + if item.city_id == city_id + ] + assert "risk_assessment" in actions + assert "legality_review" in actions + assert "collective_deliberation" in actions + assert not {"preconsult_finance", "broker_compromise"} & set(actions) + + +def test_informal_mode_releases_sequence_but_keeps_fiscal_guardrail(): + world = run_mode("informal", 1) + for city_id in world.cities: + actions = [ + item.action_id for item in world.organization_actions + if item.city_id == city_id + ] + assert actions[-1] == "fiscal_guardrail" + assert "legality_review" not in actions + assert "collective_deliberation" not in actions + negotiation = next(item for item in world.negotiations if item.city_id == city_id) + assert negotiation.final_cost <= negotiation.finance_limit + 0.02 + + +def test_hybrid_mode_combines_informal_moves_and_formal_gates_reproducibly(): + first = run_mode("hybrid", 3) + second = run_mode("hybrid", 3) + assert [item.action_id for item in first.organization_actions] == [ + item.action_id for item in second.organization_actions + ] + assert any(item.arena == "informal" for item in first.organization_actions) + assert any(item.action_id == "legality_review" for item in first.organization_actions) + assert all( + process.legal_reviewed and process.collective_deliberated + for process in first.organization_processes.values() + ) + + +def test_llm_provider_can_choose_only_role_authorized_discretionary_action(): + class OrganizationLLM(DeterministicCognition): + mode = "llm" + model_name = "test-organization-llm" + + def choose_organization_action(self, agent, candidates, observation, memories): + return OrganizationActionChoice( + action_id=candidates[-1], + confidence=0.9, + rationale="根据本组织目标选择授权列表中的末项策略", + ) + + world = create_full_lifecycle_world(42, "organization-llm") + world.policy_mode = "llm" + SimulationEngine(world, cognition=OrganizationLLM()).run(1) + discretionary = [item for item in world.organization_actions if not item.required] + assert discretionary + assert all(item.selection_provider == "test-organization-llm" for item in discretionary) + assert all(item.action_id in item.candidates for item in discretionary) + + +def test_three_mode_comparison_and_evidence_api(): + rows = run_organization_mode_comparison(seed=42, quarters=4) + assert [item["process_mode"] for item in rows] == ["formal", "informal", "hybrid"] + assert len({tuple(item["action_sequence"]) for item in rows}) == 3 + evidence = client.get("/organization/evidence") + assert evidence.status_code == 200 + assert {item["id"] for item in evidence.json()} >= { + "formal_procedure", "fund_independence", "informal_networks", "attention", + } + actions = client.get("/organization/actions").json() + assert any(item["id"] == "fiscal_guardrail" for item in actions) + + +def test_departments_decide_who_acts_and_attention_defers_lower_priority_moves(): + class InitiativeCognition(DeterministicCognition): + def choose_organization_initiative(self, agent, candidates, observation, memories): + urgency = {"investment": 0.95, "finance": 0.85, "park": 0.7, "city_leader": 0.2}[agent.role.value] + if agent.role.value == "city_leader": + return OrganizationInitiativeChoice( + decision="wait", action_id="wait", urgency=urgency, + rationale="当前不占用注意力", + ) + return OrganizationInitiativeChoice( + decision="act", action_id=candidates[0], urgency=urgency, + rationale="此刻主动发起", + ) + + world = create_full_lifecycle_world(42, "initiative-autonomy") + SimulationEngine(world, cognition=InitiativeCognition()).run(1) + for city_id in world.cities: + rows = [ + item for item in world.organization_actions + if item.city_id == city_id and item.stage == "initiative" + ] + assert len(rows) == 4 + assert sum(item.decision == "act" for item in rows) == 2 + assert any(item.decision == "wait" for item in rows) + assert any(item.decision == "deferred" for item in rows) + + +def test_formal_process_can_be_returned_then_resumed_by_agent(): + class ReturnThenResume(DeterministicCognition): + def choose_procedure_transition(self, agent, candidates, observation, memories): + transition = ( + "return_for_revision" + if observation["formal_status"] == "dormant" + else "resume_formal_process" + ) + return ProcedureTransitionChoice( + transition=transition, + rationale="材料不足先退回,补齐后恢复程序", + ) + + world = create_full_lifecycle_world(42, "return-resume") + engine = SimulationEngine(world, cognition=ReturnThenResume()) + engine.run(1) + assert not world.negotiations + assert all(state.formal_status == "returned" for state in world.organization_processes.values()) + engine.run(1) + assert len(world.negotiations) == 3 + assert all(state.last_transition == "resume_formal_process" for state in world.organization_processes.values()) + + +def test_enterprise_can_accept_and_select_before_fixed_third_quarter(): + class EarlyBoard(DeterministicCognition): + def respond_to_offer(self, agent, observation, memories): + return EnterpriseResponseAction( + response="accept", confidence=0.92, + rationale="高效用报价已满足董事会条件", + ) + + def decide_negotiation_timing(self, agent, observation, memories): + return NegotiationTimingAction( + decision="select_now", confidence=0.9, + rationale="立即锁定已接受的优势报价", + ) + + world = create_full_lifecycle_world(42, "early-board") + SimulationEngine(world, cognition=EarlyBoard()).run(1) + assert world.quarter == 1 + assert world.selected_city_id is not None + assert world.recruitment_status == "selected" + assert any(item.enterprise_response == "accept" for item in world.external_negotiations) + + +def test_multi_step_plans_are_persisted_and_linked_to_actual_actions(): + world = create_full_lifecycle_world(42, "plan-tree") + SimulationEngine(world).run(3) + assert len(world.organization_plans) == 12 + investment = next( + plan for plan in world.organization_plans + if plan.actor_id == "city_hai_investment" + ) + assert len(investment.alternatives) == 3 + assert len(investment.nodes) >= 2 + linked = [item for item in world.organization_actions if item.plan_id == investment.id] + assert linked + assert any(node.status == "completed" for node in investment.nodes) + assert investment.review_history + + +def test_open_action_is_generated_then_permission_checked_and_effect_capped(): + world = create_full_lifecycle_world(42, "open-action") + dynamics = OrganizationDynamics(world) + authorized = dynamics.validate_open_action( + "city_hai_investment", "city_hai", + { + "title": "邀请上级部门背书", + "mechanism": "upward_endorsement", + "target_actor_id": "city_hai_leader", + "requested_effects": {"agenda_priority_delta": 0.9}, + "resource_request": {}, "rationale": "打开议程窗口", + }, + "test", False, + ) + assert authorized.status == "authorized" + assert authorized.executed_effects["agenda_priority_delta"] == 0.18 + rejected = dynamics.validate_open_action( + "city_hai_finance", "city_hai", + { + "title": "财政局自行寻求上级政治背书", + "mechanism": "upward_endorsement", + "requested_effects": {"agenda_priority_delta": 0.1}, + "resource_request": {"cash": 5}, "rationale": "越权测试", + }, + "test", False, + ) + assert rejected.status == "rejected" + assert not rejected.executed_effects + + +def test_opportunity_windows_are_endogenous_observable_and_change_attention(): + world = create_full_lifecycle_world(42, "windows") + engine = SimulationEngine(world) + engine.run(2) + assert world.opportunity_windows + assert any(item.kind == "major_meeting" for item in world.opportunity_windows) + assert any(event.kind == "opportunity_window" for event in world.events) + assert any(state.attention_budget > 2 for state in world.organization_processes.values()) + + +def test_three_vetoes_change_next_plan_and_create_transferable_lesson(): + class PersistentNegotiator(DeterministicCognition): + def respond_to_offer(self, agent, observation, memories): + return EnterpriseResponseAction( + response="counter", counter_terms={"require_phased_delivery": 1.0}, + confidence=0.8, rationale="继续检验政府适应能力", + ) + + def decide_negotiation_timing(self, agent, observation, memories): + return NegotiationTimingAction( + decision="continue_negotiating", confidence=0.8, + rationale="为组织学习实验继续谈判", + ) + + world = create_full_lifecycle_world(42, "learning-after-veto") + world.negotiation_round_limit = 8 + SimulationEngine(world, cognition=PersistentNegotiator()).run(5) + learning = world.organization_learning["city_lin_investment"] + assert learning.veto_count >= 3 + assert any("连续财政否决" in item for item in learning.transferable_lessons) + plans = [ + plan for plan in world.organization_plans + if plan.actor_id == "city_lin_investment" + ] + assert len(plans) >= 2 + assert plans[-1].selected_strategy == "phased_adaptation" + assert any(node.action_id == "propose_open_action" for node in plans[-1].nodes) + + +def test_leadership_change_partially_inherits_memory_but_keeps_institutional_learning(): + world = create_full_lifecycle_world(42, "succession") + leader = world.agents["city_lin_leader"] + for index in range(6): + leader.memories.append(MemoryRecord( + id=f"m{index}", quarter=0, kind="case", content=f"经验{index}", + importance=0.8, + )) + dynamics = OrganizationDynamics(world) + learning = dynamics.learning(leader.id) + learning.routines["broker_compromise"] = 0.8 + for quarter in range(1, 10): + world.quarter = quarter + dynamics.advance_opportunity_windows() + if learning.leadership_generation > 1: + break + assert learning.leadership_generation == 2 + assert learning.inherited_memory_ratio == 0.6 + assert len(leader.memories) < 6 + assert learning.routines["broker_compromise"] == 0.8 From 19b96e6dadbf154afad99a4a44ea77adbe211a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=A0=E6=AC=A3?= <115392788+lizhangxin666@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:21:07 +0800 Subject: [PATCH 2/3] feat: add source-backed organization calibration --- .../HEFEI_NIO_ORGANIZATION_CALIBRATION.md | 93 + .../report.json | 2760 +++++++++++++++++ .../report.md | 35 + .../research/organization-autonomy-upgrade.md | 71 + .../organizational-behavior-evidence.md | 75 + src/insidegov/api.py | 49 +- src/insidegov/cases.py | 3 + src/insidegov/cli.py | 52 +- src/insidegov/organizational_calibration.py | 714 +++++ tests/test_organizational_calibration.py | 71 + 10 files changed, 3919 insertions(+), 4 deletions(-) create mode 100644 docs/cases/HEFEI_NIO_ORGANIZATION_CALIBRATION.md create mode 100644 docs/reports/hefei-nio-organization-calibration/report.json create mode 100644 docs/reports/hefei-nio-organization-calibration/report.md create mode 100644 docs/research/organization-autonomy-upgrade.md create mode 100644 docs/research/organizational-behavior-evidence.md create mode 100644 src/insidegov/organizational_calibration.py create mode 100644 tests/test_organizational_calibration.py diff --git a/docs/cases/HEFEI_NIO_ORGANIZATION_CALIBRATION.md b/docs/cases/HEFEI_NIO_ORGANIZATION_CALIBRATION.md new file mode 100644 index 0000000..4b355ff --- /dev/null +++ b/docs/cases/HEFEI_NIO_ORGANIZATION_CALIBRATION.md @@ -0,0 +1,93 @@ +# 2020合肥—蔚来:组织行为校准与质量评估设计 + +## 为什么需要单独做组织行为校准 + +现有真实案例参数卡已经校准了财政规模、70亿元战略投资、企业资产与现金投入、 +产能和分期结构,但“数值接近历史结果”不等于“组织行为接近真实过程”。本评估把 +参数校准与行为校准分开,回答五个不同问题: + +1. 模拟是否复现了公开可观察的关键组织行为; +2. 行动主体、权限和工具是否匹配; +3. 框架协议、正式协议、分期出资和执行是否保持正确顺序; +4. 未用于模式选择的后续历史节点能否被模拟命中; +5. 系统是否把不可观察的内部过程错误包装成历史事实。 + +## 证据编码规则 + +每条证据被标记为以下类型之一: + +| 类型 | 含义 | 是否评分 | +|---|---|:---:| +| `direct_public_record` | 协议、年报或公告直接披露 | 是 | +| `triangulated_inference` | 多份材料共同支持机制类别,但不支持逐轮细节 | 低权重 | +| `triangulated_public_record` | 多来源支持后续方向性结果 | 是 | +| `not_publicly_observable` | 内部会商、否决措辞、游说对象没有公开记录 | 否 | + +因此,“财政局具体说了什么”“市领导是否在某次会前单独协调”不会被编码为历史金标准。 +这些行为可以在模拟世界中出现,但必须标为模型生成的机制路径。 + +## 校准集与留出集 + +### 校准集:截至2020年4月29日 + +- 2月先形成总部落户框架协议,4月再签正式投资和股东协议; +- 疫情期间招商推动与最终正式治理结构并存; +- 国投招商、安徽省高新投、合肥市建投等多主体联合参与; +- 核心工具为股权投资,不是单一财政局无条件现金补贴; +- 70亿元采用五期出资; +- 部分后续支持与产能、采购额、车辆销售等绩效条件挂钩。 + +### 留出验证集 + +- 2020年6月:首两期50亿元中48亿元到账; +- 2020年10月:蔚来中国总部正式启用; +- 此后:政企继续推进智能电动汽车产业链合作。 + +模式选择只能看到校准集。留出节点不能参与选择,否则只是把历史结局重新写进模型。 + +## 评分结构 + +| 指标 | 权重 | 说明 | +|---|---:|---| +| 校准事件拟合 | 35% | 公开行为、组织结构和合同工具 | +| 留出节点 | 35% | 早期到账、运营里程碑和产业扩散 | +| 角色边界 | 12% | 基金、财政、司法和领导是否越权 | +| 证据纪律 | 10% | 行动是否有机制依据,是否区分事实与模拟 | +| 行动顺序 | 8% | 动员、审查、基金尽调和合同执行的相对顺序 | + +高“校准得分”本身不代表预测能力,因为70亿元等结构已经用于建模。真正更重要的是 +留出得分、失败节点和跨案例外部验证。 + +## 数据源 + +- [NIO 2020 Form 20-F](https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm) +- [NIO China Shareholders Agreement](https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm) +- [蔚来正式投资协议公告](https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/) +- [蔚来早期到账公告](https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/) +- [商务部转载:2020年2月框架协议](https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html) +- [商务部:蔚来中国总部启用](https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html) +- [国务院国资委:多投资主体协同](https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html) + +## 运行 + +```bash +uv run insidegov case-hefei-nio-org-calibration \ + --seeds 11,23,42,57,89 \ + --modes formal,informal,hybrid \ + --quarters 16 +``` + +报告写入: + +```text +docs/reports/hefei-nio-organization-calibration/report.json +docs/reports/hefei-nio-organization-calibration/report.md +``` + +## 当前评估边界 + +- 单案例主要提供内部效度,不能证明适用于其他产业和城市; +- 真实自然月被映射为政策阶段季度,不能逐日比较; +- 全市产业链发展无法识别为单一项目的净因果效应; +- 确定性策略在强案例参数下可能跨seed差异过小; +- 下一步需要加入第二个失败或退出案例,并让专家对事件编码进行双人复核。 diff --git a/docs/reports/hefei-nio-organization-calibration/report.json b/docs/reports/hefei-nio-organization-calibration/report.json new file mode 100644 index 0000000..7b97dae --- /dev/null +++ b/docs/reports/hefei-nio-organization-calibration/report.json @@ -0,0 +1,2760 @@ +{ + "schema_version": "1.0", + "generated_at": "2026-08-13T01:11:01.889874+00:00", + "case": "2020合肥—蔚来", + "evaluation_type": "organizational_behavior_calibration_with_holdout", + "cognition_policy": "deterministic", + "seeds": [ + 11, + 23, + 42, + 57, + 89 + ], + "candidate_modes": [ + "formal", + "informal", + "hybrid" + ], + "calibration_cutoff": "2020-04-29 definitive agreements", + "holdout_events": [ + "2020-06 early cash injection", + "2020-10 headquarters operational", + "post-deal cluster cooperation" + ], + "selected_process_mode": "hybrid", + "selection_rule": "highest mean calibration-split score with role-boundary and evidence-discipline guardrails; holdout outcomes excluded from selection", + "mode_summary": [ + { + "process_mode": "formal", + "runs": 5, + "selection_score": 95.34, + "calibration_score_mean": 93.35, + "calibration_score_stdev": 0.0, + "holdout_score_mean": 91.43, + "holdout_score_stdev": 4.89, + "role_boundary_score_mean": 100.0, + "evidence_discipline_score_mean": 100.0, + "sequence_score_mean": 100.0, + "composite_score_mean": 94.67, + "composite_score_stdev": 1.71 + }, + { + "process_mode": "informal", + "runs": 5, + "selection_score": 92.03, + "calibration_score_mean": 93.0, + "calibration_score_stdev": 0.0, + "holdout_score_mean": 91.43, + "holdout_score_stdev": 4.89, + "role_boundary_score_mean": 100.0, + "evidence_discipline_score_mean": 100.0, + "sequence_score_mean": 61.67, + "composite_score_mean": 91.48, + "composite_score_stdev": 1.71 + }, + { + "process_mode": "hybrid", + "runs": 5, + "selection_score": 99.2, + "calibration_score_mean": 98.86, + "calibration_score_stdev": 0.0, + "holdout_score_mean": 91.43, + "holdout_score_stdev": 4.89, + "role_boundary_score_mean": 100.0, + "evidence_discipline_score_mean": 100.0, + "sequence_score_mean": 100.0, + "composite_score_mean": 96.6, + "composite_score_stdev": 1.71 + } + ], + "selected_mode_quality": { + "calibration_score_mean": 98.86, + "holdout_score_mean": 91.43, + "composite_score_mean": 96.6, + "composite_score_stdev": 1.71 + }, + "selected_mode_benchmark_summary": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "非正式动员动作=8;正式审查动作=9", + "evidence_status": "triangulated_inference" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "独立资金池=3;发生尽调的主体=3", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score_mean": 94.37, + "score_stdev": 0.0, + "observed_example": "股权支持=71.31亿元;补贴=10.30亿元", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "股权相关期次=5;到期阶段=[1, 2, 3]", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score_mean": 85.42, + "score_stdev": 0.0, + "observed_example": "归一化早期外部股权到账=38.00亿元", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score_mean": 93.0, + "score_stdev": 15.65, + "observed_example": "项目进度=100.0%;首次投产里程碑=9", + "evidence_status": "direct_public_record" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score_mean": 100.0, + "score_stdev": 0.0, + "observed_example": "Q16集群规模=13", + "evidence_status": "triangulated_public_record" + } + ], + "quality_gaps": [ + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score_mean": 85.42, + "score_stdev": 0.0, + "observed_example": "归一化早期外部股权到账=38.00亿元", + "evidence_status": "direct_public_record" + } + ], + "diagnostics": { + "zero_score_variation_across_seeds": false, + "interpretation": "不同seed产生了可观察质量差异,应结合均值、方差和失败轨迹解释。" + }, + "benchmarks": [ + { + "id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "historical_date": "2020-02-25 → 2020-04-29", + "split": "calibration", + "evidence_status": "direct_public_record", + "weight": 1.2, + "observable_claim": "2月先签总部落户合作框架,4月再由多方签署正式投资与股东协议。", + "matcher": "framework_before_definitive", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "rationale": "校准分阶段议程化,而不是要求公开材料披露每次内部会商。", + "quantitative": true + }, + { + "id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "historical_date": "2020-02 → 2020-04", + "split": "calibration", + "evidence_status": "triangulated_inference", + "weight": 0.8, + "observable_claim": "疫情期间持续远程招商并形成框架协议;最终协议包含多投资人、公司治理、出资条件和正式权利义务。", + "matcher": "hybrid_mobilization_and_review", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "rationale": "只校准非正式动员与正式审查两类机制均出现,不宣称已知真实部门行动顺序。", + "quantitative": true + }, + { + "id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "historical_date": "2020-04-29", + "split": "calibration", + "evidence_status": "direct_public_record", + "weight": 1.3, + "observable_claim": "股东协议列明国投招商、安徽省高新投、合肥市建投等投资主体共同参与。", + "matcher": "multi_party_investor_coalition", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "rationale": "检验组织世界是否把70亿元错误建模为单一财政局现金支出。", + "quantitative": true + }, + { + "id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "historical_date": "2020-04-29", + "split": "calibration", + "evidence_status": "direct_public_record", + "weight": 1.4, + "observable_claim": "战略投资者以70亿元现金增资取得约24.1%股权。", + "matcher": "equity_not_cash_subsidy", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "rationale": "金额是已使用的结构校准点,因此不能再次作为留出预测证据。", + "quantitative": true + }, + { + "id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "historical_date": "2020-04-29", + "split": "calibration", + "evidence_status": "direct_public_record", + "weight": 1.2, + "observable_claim": "战略投资者70亿元按35、15、10、5、5亿元五期投入。", + "matcher": "five_equity_installments", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "rationale": "校准合同结构;不要求模拟恰好复制每一期金额。", + "quantitative": true + }, + { + "id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "historical_date": "2020-04-29", + "split": "calibration", + "evidence_status": "direct_public_record", + "weight": 1.0, + "observable_claim": "经开区租金、金融与税收支持以产能、采购额和车辆销售等绩效目标为条件。", + "matcher": "performance_contingent_support", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "rationale": "检验合同是否具有条件性,而不是把所有承诺在签约时立即兑现。", + "quantitative": true + }, + { + "id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "historical_date": "2020-06", + "split": "holdout", + "evidence_status": "direct_public_record", + "weight": 1.4, + "observable_claim": "截至6月底,首两期50亿元中48亿元已到账。", + "matcher": "early_cash_injection", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "rationale": "留出节点;用归一化早期履约里程碑检验,不参与模式选择。", + "quantitative": true + }, + { + "id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "historical_date": "2020-10-09", + "split": "holdout", + "evidence_status": "direct_public_record", + "weight": 1.0, + "observable_claim": "蔚来中国总部于2020年10月正式启用。", + "matcher": "headquarters_operational", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "rationale": "用项目投产/运营里程碑检验组织承诺是否进入执行状态。", + "quantitative": true + }, + { + "id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "historical_date": "2021以后", + "split": "holdout", + "evidence_status": "triangulated_public_record", + "weight": 0.8, + "observable_claim": "政企继续推进智能电动汽车产业集群与上下游协同。", + "matcher": "continued_cluster_cooperation", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "rationale": "只能验证方向和是否出现扩散,不能把全市产业增长归因给单一项目。", + "quantitative": true + }, + { + "id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "historical_date": "2020-02 → 2020-04", + "split": "unverifiable", + "evidence_status": "not_publicly_observable", + "weight": 0.0, + "observable_claim": "公开材料不足以恢复财政局、招商部门和市领导的逐轮内部对话。", + "matcher": null, + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "rationale": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。", + "quantitative": false + } + ], + "runs": [ + { + "world_id": "hefei-nio-org-calibration-formal-11", + "seed": 11, + "process_mode": "formal", + "calibration_score": 93.35, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 95.44, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=0;正式审查动作=12", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.958, + "weight": 1.4, + "observed": "股权支持=70.00亿元;补贴=9.54亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-formal-23", + "seed": 23, + "process_mode": "formal", + "calibration_score": 93.35, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 95.44, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=0;正式审查动作=12", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.958, + "weight": 1.4, + "observed": "股权支持=70.00亿元;补贴=9.54亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-formal-42", + "seed": 42, + "process_mode": "formal", + "calibration_score": 93.35, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 95.44, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=0;正式审查动作=12", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.958, + "weight": 1.4, + "observed": "股权支持=70.00亿元;补贴=9.54亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-formal-57", + "seed": 57, + "process_mode": "formal", + "calibration_score": 93.35, + "holdout_score": 82.68, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 91.61, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=0;正式审查动作=13", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.958, + "weight": 1.4, + "observed": "股权支持=70.00亿元;补贴=9.54亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 0.65, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=10", + "matched": false, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=11", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-formal-89", + "seed": 89, + "process_mode": "formal", + "calibration_score": 93.35, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 95.44, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=0;正式审查动作=13", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.958, + "weight": 1.4, + "observed": "股权支持=70.00亿元;补贴=9.54亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-informal-11", + "seed": 11, + "process_mode": "informal", + "calibration_score": 93.0, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 61.67, + "composite_score": 92.25, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=0", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9407, + "weight": 1.4, + "observed": "股权支持=67.69亿元;补贴=8.20亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-informal-23", + "seed": 23, + "process_mode": "informal", + "calibration_score": 93.0, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 61.67, + "composite_score": 92.25, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=0", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9407, + "weight": 1.4, + "observed": "股权支持=67.69亿元;补贴=8.20亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-informal-42", + "seed": 42, + "process_mode": "informal", + "calibration_score": 93.0, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 61.67, + "composite_score": 92.25, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=0", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9407, + "weight": 1.4, + "observed": "股权支持=67.69亿元;补贴=8.20亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-informal-57", + "seed": 57, + "process_mode": "informal", + "calibration_score": 93.0, + "holdout_score": 82.68, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 61.67, + "composite_score": 88.42, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=7;正式审查动作=0", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9407, + "weight": 1.4, + "observed": "股权支持=67.69亿元;补贴=8.20亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 0.65, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=10", + "matched": false, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=11", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-informal-89", + "seed": 89, + "process_mode": "informal", + "calibration_score": 93.0, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 61.67, + "composite_score": 92.25, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 0.5, + "weight": 0.8, + "observed": "非正式动员动作=7;正式审查动作=0", + "matched": false, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9407, + "weight": 1.4, + "observed": "股权支持=67.69亿元;补贴=8.20亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-hybrid-11", + "seed": 11, + "process_mode": "hybrid", + "calibration_score": 98.86, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 97.37, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 1.0, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=9", + "matched": true, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9437, + "weight": 1.4, + "observed": "股权支持=71.31亿元;补贴=10.30亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-hybrid-23", + "seed": 23, + "process_mode": "hybrid", + "calibration_score": 98.86, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 97.37, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 1.0, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=9", + "matched": true, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9437, + "weight": 1.4, + "observed": "股权支持=71.31亿元;补贴=10.30亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-hybrid-42", + "seed": 42, + "process_mode": "hybrid", + "calibration_score": 98.86, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 97.37, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 1.0, + "weight": 0.8, + "observed": "非正式动员动作=8;正式审查动作=9", + "matched": true, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9437, + "weight": 1.4, + "observed": "股权支持=71.31亿元;补贴=10.30亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-hybrid-57", + "seed": 57, + "process_mode": "hybrid", + "calibration_score": 98.86, + "holdout_score": 82.68, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 93.54, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 1.0, + "weight": 0.8, + "observed": "非正式动员动作=7;正式审查动作=10", + "matched": true, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9437, + "weight": 1.4, + "observed": "股权支持=71.31亿元;补贴=10.30亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 0.65, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=10", + "matched": false, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=11", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + }, + { + "world_id": "hefei-nio-org-calibration-hybrid-89", + "seed": 89, + "process_mode": "hybrid", + "calibration_score": 98.86, + "holdout_score": 93.62, + "role_boundary_score": 100.0, + "evidence_discipline_score": 100.0, + "sequence_score": 100.0, + "composite_score": 97.37, + "matches": [ + { + "benchmark_id": "framework_before_definitive", + "label": "先框架协议、后正式投资协议", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "合肥对外协商季度=[1, 2, 3];Q3选址=city_lin", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "阶段而非自然月对齐" + }, + { + "benchmark_id": "hybrid_mobilization_and_review", + "label": "招商推动与正式治理并存", + "split": "calibration", + "score": 1.0, + "weight": 0.8, + "observed": "非正式动员动作=7;正式审查动作=9", + "matched": true, + "evidence_status": "triangulated_inference", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "不解释为真实逐轮行动" + }, + { + "benchmark_id": "multi_party_investor_coalition", + "label": "多层级投资主体联合出资", + "split": "calibration", + "score": 1.0, + "weight": 1.3, + "observed": "独立资金池=3;发生尽调的主体=3", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "" + }, + { + "benchmark_id": "equity_not_cash_subsidy", + "label": "核心工具是股权投资而非无条件现金补贴", + "split": "calibration", + "score": 0.9437, + "weight": 1.4, + "observed": "股权支持=71.31亿元;补贴=10.30亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "70亿元是结构校准点,不计入留出预测" + }, + { + "benchmark_id": "five_equity_installments", + "label": "股权资本分期而非一次性支付", + "split": "calibration", + "score": 1.0, + "weight": 1.2, + "observed": "股权相关期次=5;到期阶段=[1, 2, 3]", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-entry-definitive-agreements/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "只校准分期结构,不拟合每期金额" + }, + { + "benchmark_id": "performance_contingent_support", + "label": "后续支持与经营绩效条件挂钩", + "split": "calibration", + "score": 1.0, + "weight": 1.0, + "observed": "条件节点=['equipment_ordered', 'production_commissioned', 'project_progress>=0.55']", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "" + }, + { + "benchmark_id": "early_cash_injection", + "label": "首两期投资基本按期到账", + "split": "holdout", + "score": 0.8542, + "weight": 1.4, + "observed": "归一化早期外部股权到账=38.00亿元", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://ir.nio.com/news-events/news-releases/news-release-details/nio-announces-substantial-completion-cash-injections/", + "https://www.sec.gov/Archives/edgar/data/1736541/000110465921046834/nio-20201231x20f.htm" + ], + "note": "历史基准48亿元" + }, + { + "benchmark_id": "headquarters_operational", + "label": "总部从协议进入实际运营", + "split": "holdout", + "score": 1.0, + "weight": 1.0, + "observed": "项目进度=100.0%;首次投产里程碑=9", + "matched": true, + "evidence_status": "direct_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" + ], + "note": "用Q9作为归一化总部运营节点" + }, + { + "benchmark_id": "continued_cluster_cooperation", + "label": "落地后继续推进产业链合作", + "split": "holdout", + "score": 1.0, + "weight": 0.8, + "observed": "Q16集群规模=13", + "matched": true, + "evidence_status": "triangulated_public_record", + "sources": [ + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html", + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" + ], + "note": "只评价扩散方向" + }, + { + "benchmark_id": "private_department_dialogue", + "label": "财政局具体否决措辞与会前游说网络", + "split": "unverifiable", + "score": null, + "weight": 0.0, + "observed": "公开资料不可观察;不评分", + "matched": null, + "evidence_status": "not_publicly_observable", + "sources": [ + "https://www.sec.gov/Archives/edgar/data/1736541/000110465920061585/nio-20191231xex4d36.htm" + ], + "note": "模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。" + } + ], + "unsupported_internal_actions": [] + } + ], + "limitations": [ + "70亿元股权总量已用于结构校准,不能再作为独立预测成功证据。", + "公开材料没有披露逐轮内部会商,模拟对话只代表机制假设。", + "季度是归一化政策阶段,不与现实自然月机械对应。", + "后续全市产业链增长不能被识别为蔚来项目的单独因果贡献。", + "单案例只能做内部效度检查,跨案例外部效度仍需第二个案例。", + "确定性案例在当前seed集合上的行为得分可能缺少变异,不能替代LLM与专家复核。" + ], + "markdown": "# 2020合肥—蔚来:组织行为校准与质量评估\n\n## 结论\n\n- 校准集选择的过程模式:**hybrid**;\n- 校准行为得分:**98.9/100**;\n- 留出历史节点得分:**91.4/100**;\n- 综合质量:**96.6/100** (跨seed标准差 1.71)。\n\n模式只根据2020年4月29日前的行为证据选择;6月到账、10月总部启用和后续产业协同为留出验证,不参与选择。\n\n## 模式比较\n\n| 模式 | 校准行为 | 留出验证 | 角色边界 | 证据纪律 | 顺序 | 综合 |\n|---|---:|---:|---:|---:|---:|---:|\n| formal | 93.3 | 91.4 | 100.0 | 100.0 | 100.0 | 94.7 |\n| informal | 93.0 | 91.4 | 100.0 | 100.0 | 61.7 | 91.5 |\n| hybrid | 98.9 | 91.4 | 100.0 | 100.0 | 100.0 | 96.6 |\n\n## 证据纪律\n\n财政局具体否决措辞、会前游说对象和真实内部行动顺序没有公开记录,因此被编码为`not_publicly_observable`,不进入命中率。系统只能说“模拟生成了一个可能机制”,不能说“复现了真实内部对话”。\n\n## 未完全匹配的节点\n\n- 首两期投资基本按期到账:85.4/100;归一化早期外部股权到账=38.00亿元\n\n## 主要限制\n\n- 70亿元股权总量已用于结构校准,不能再作为独立预测成功证据。\n- 公开材料没有披露逐轮内部会商,模拟对话只代表机制假设。\n- 季度是归一化政策阶段,不与现实自然月机械对应。\n- 后续全市产业链增长不能被识别为蔚来项目的单独因果贡献。\n- 单案例只能做内部效度检查,跨案例外部效度仍需第二个案例。\n- 确定性案例在当前seed集合上的行为得分可能缺少变异,不能替代LLM与专家复核。\n" +} \ No newline at end of file diff --git a/docs/reports/hefei-nio-organization-calibration/report.md b/docs/reports/hefei-nio-organization-calibration/report.md new file mode 100644 index 0000000..a553de1 --- /dev/null +++ b/docs/reports/hefei-nio-organization-calibration/report.md @@ -0,0 +1,35 @@ +# 2020合肥—蔚来:组织行为校准与质量评估 + +## 结论 + +- 校准集选择的过程模式:**hybrid**; +- 校准行为得分:**98.9/100**; +- 留出历史节点得分:**91.4/100**; +- 综合质量:**96.6/100** (跨seed标准差 1.71)。 + +模式只根据2020年4月29日前的行为证据选择;6月到账、10月总部启用和后续产业协同为留出验证,不参与选择。 + +## 模式比较 + +| 模式 | 校准行为 | 留出验证 | 角色边界 | 证据纪律 | 顺序 | 综合 | +|---|---:|---:|---:|---:|---:|---:| +| formal | 93.3 | 91.4 | 100.0 | 100.0 | 100.0 | 94.7 | +| informal | 93.0 | 91.4 | 100.0 | 100.0 | 61.7 | 91.5 | +| hybrid | 98.9 | 91.4 | 100.0 | 100.0 | 100.0 | 96.6 | + +## 证据纪律 + +财政局具体否决措辞、会前游说对象和真实内部行动顺序没有公开记录,因此被编码为`not_publicly_observable`,不进入命中率。系统只能说“模拟生成了一个可能机制”,不能说“复现了真实内部对话”。 + +## 未完全匹配的节点 + +- 首两期投资基本按期到账:85.4/100;归一化早期外部股权到账=38.00亿元 + +## 主要限制 + +- 70亿元股权总量已用于结构校准,不能再作为独立预测成功证据。 +- 公开材料没有披露逐轮内部会商,模拟对话只代表机制假设。 +- 季度是归一化政策阶段,不与现实自然月机械对应。 +- 后续全市产业链增长不能被识别为蔚来项目的单独因果贡献。 +- 单案例只能做内部效度检查,跨案例外部效度仍需第二个案例。 +- 确定性案例在当前seed集合上的行为得分可能缺少变异,不能替代LLM与专家复核。 diff --git a/docs/research/organization-autonomy-upgrade.md b/docs/research/organization-autonomy-upgrade.md new file mode 100644 index 0000000..d94a481 --- /dev/null +++ b/docs/research/organization-autonomy-upgrade.md @@ -0,0 +1,71 @@ +# 组织自主性升级:计划、开放行动、机会窗口与长期学习 + +本次升级把组织 Agent 从“一轮一决策”改为跨季度的受约束行动者。四个模块共用同一条证据链: + +```text +比较策略路径并保存计划树 +→ 感知政策、竞争、财政与注意力窗口 +→ 执行计划动作或提出目录外行动 +→ 权限、资源、合法性与效果幅度审查 +→ 更新程序、关系与客观世界 +→ 记录计划偏离并形成组织经验 +→ 下一轮规划读取信任、惯例、否决历史和可迁移教训 +``` + +## 1. 可检验的多步计划 + +每个城市的招商局、财政局、园区和市领导分别保存 `OrganizationPlan`。计划不是隐藏的思维链,而是可审计的决策产物,包括: + +- 两到三条候选策略及其收益、风险和评分; +- 被选择的策略; +- 两到五个跨季度节点; +- 每个节点的最早和最晚执行季度; +- 前置条件、成功路径、失败后的重规划入口; +- 原计划动作、实际动作和偏离理由。 + +系统不保存模型内部推理过程,只保存足以被研究者复核的计划结构与行动理由。 + +## 2. 开放行动与安全执行 + +Agent 可以通过 `propose_open_action` 提出目录外行动,例如上级背书、行业协会联盟、示范工程、会议窗口、战略等待、跨部门工作组和专家论证。 + +开放不等于任意。规则引擎依次检查: + +1. 该机制是否属于本部门职责; +2. 是否试图代替其他部门或企业作决定; +3. 是否凭空申请财政、股权、土地、信贷等资源; +4. 请求改变的状态维度是否可执行; +5. 效果幅度是否超过实验上限。 + +通过审查后,动作只会被映射为议程、联盟、程序完整度、注意力、审批速度或可信度的小幅变化。财政、合同和生产状态仍不能由 LLM 直接修改。 + +## 3. 内生政治机会窗口 + +`OpportunityWindow` 由世界状态和可复现随机流生成,当前包括: + +- 上级产业政策信号; +- 重大会议日历; +- 竞争城市率先报价; +- 财政压力收窄政策空间; +- 舆情改变领导注意力; +- 部门负责人调整。 + +窗口具有开始、结束、强度、触发证据、可见主体和状态效果。Agent 可以提前规划,但不能提前知道尚未出现的窗口;窗口发生后才进入其局部观察。 + +## 4. 长期组织学习 + +每个组织保存独立的 `OrganizationLearningState`: + +- 对其他部门的稳定信任; +- 对企业谈判类型的信念; +- 行动尝试、成功率和策略偏好; +- 重复成功后形成的组织惯例; +- 财政否决次数和协调成功次数; +- 当前领导代际与记忆继承比例; +- 本项目教训与可跨项目迁移的教训。 + +连续财政否决会提高分期、试点和新型组织行动的偏好。负责人调整时,个人关系与策略偏好部分衰减,但制度档案、组织惯例和部分历史记忆被保留,从而区分“个人记忆”与“组织记忆”。 + +## 研究边界 + +这些模块用于研究行动路径、程序冲突和组织适应,不证明模拟行为与现实政府完全一致。计划评分、窗口发生率、信任更新速度和效果上限仍需用真实案例校准,并通过多 seed、机制消融和失败案例报告检验。 diff --git a/docs/research/organizational-behavior-evidence.md b/docs/research/organizational-behavior-evidence.md new file mode 100644 index 0000000..2635c1d --- /dev/null +++ b/docs/research/organizational-behavior-evidence.md @@ -0,0 +1,75 @@ +# 组织行动内核:行为依据与编码边界 + +本模块模拟的是组织行动,而不是把部门拟人化为拥有无限行为能力的个人。组织 Agent 只能从与其职责、权限和所处程序相匹配的动作集中选择;确定性规则仍负责财政扣减、合同生效、项目进度、生产和违约后果。 + +## 1. 正式科层程序 + +司法部对《重大行政决策程序暂行条例》的官方说明将公众参与、专家论证、风险评估、合法性审查和集体讨论列为法定程序,其中合法性审查和集体讨论是刚性程序;行政首长最后发表意见,意见与多数不一致时需说明理由,讨论过程应记录并追责。 + +- 来源:[司法部负责人就《重大行政决策程序暂行条例》答记者问](https://www.moj.gov.cn/pub/sfbgw/zcjd/201905/t20190516_390231.html) +- 编码:`risk_assessment`、`legality_review`、`collective_deliberation`。 +- 边界:正式模式不得跳过合法性审查和集体讨论;风险评估影响财政局选择的风险政策,但不直接创造财政资金。 + +## 2. 政府投资基金的独立决策 + +国务院办公厅的指导意见要求政府投资基金市场化、法治化、专业化运作,建立独立投资决策和尽职调查机制;政府部门可以监督投向和进度,但不得以行政手段干预日常管理和具体项目投资决策。 + +- 来源:[国务院办公厅关于促进政府投资基金高质量发展的指导意见](https://www.mee.gov.cn/zcwj/gwywj/202501/t20250108_1100235.shtml) +- 编码:`fund_due_diligence`,基金根据自身资本余额、尽调阈值和项目质量独立批准或拒绝。 +- 边界:市领导不能把被财政否决的现金自动转换为基金出资;基金批准额不占本级财政现金上限,但进入基金自身资本和履约账本。 + +## 3. 非正式政治动力学 + +Chen 与 Bian 对中国城市政策试验的研究把政策推动中的困难概括为对上议程设置、同级协调和对下执行。当正式权力网络不足时,政策企业家会作为对上的“主动顾问”、对同级的“修辞盟友”和对下的“支持型导师”建立非正式网络。 + +- 来源:[Policy Experimentation within Bureaucratic Power Networks](https://www.cambridge.org/core/journals/china-quarterly/article/policy-experimentation-within-bureaucratic-power-networks-the-policebusiness-cooperation-scheme-in-urban-china/6E7E2E1536065118E626082F0CFF58E8) +- 编码:`frame_strategic_project`、`mobilize_park_coalition`、`preconsult_finance`、`broker_compromise`、`authorize_pilot`。 +- 边界:非正式不等于违规。它可以改变议程优先级、信任、联盟、行动顺序和政策工具组合,但最终报价仍必须通过财政硬约束。 + +## 4. 有限注意力 + +科层组织中的政策议题竞争受到有限注意力约束,项目进入领导议程不是固定事件。 + +- 来源:[Competition for attention in the Chinese bureaucracy](https://link.springer.com/article/10.1186/s40711-018-0071-z) +- 编码:每座城市拥有可变化的 `agenda_priority`;招商局可尝试上推议题,但是否成为优先事项受季度、竞争压力和既有联盟共同影响。 + +## 三种模式的实验含义 + +| 模式 | 允许的自主行动 | 必须保留的硬边界 | +|---|---|---| +| 正式 | 材料补充、需求澄清、风险政策选择 | 合法性审查、集体讨论、财政结算 | +| 非正式 | 会前沟通、议程上推、同级联盟、领导调解、试点 | 财政硬约束、合同和生产规则 | +| 混合 | 先由非正式网络组织注意力和方案,再进入正式审查 | 正式审查链与全部状态结算 | + +三模式共享同一个初始世界和随机种子。比较结果用于识别“程序如何改变行动路径与结果”,不代表某一现实制度的真实因果效应大小。 + +## 行动发起与程序状态机 + +v0.6 不再由季度脚本指定“谁先行动”。每个招商局、财政局、园区和市领导都会独立输出: + +```text +act / wait ++ 本角色权限内的 action_id ++ urgency ++ target_actor_id ++ rationale +``` + +同一城市每轮只有有限的 `attention_budget`。主动发起者按紧迫度竞争注意力,未入选行动会被记录为 `deferred`,主动等待则记录为 `wait`;两者都保留当时可选动作、理由和复盘,因而不会在展示层被误写成“什么都没有发生”。 + +随后市领导从与当前状态相容的转换中决策: + +```text +dormant → start / pause / return +paused 或 returned → resume / re-agenda / abandon +completed → re-agenda / keep closed +active → proceed / pause / return +``` + +只有 `start`、`resume`、`re-agenda`、`proceed` 或非正式模式下的授权动作会放行对外报价。正式或混合模式一旦放行,风险评估、合法性审查和集体讨论仍是不可跳过的规则门;Agent 决定程序是否与何时进入,规则引擎决定进入后必须满足什么。 + +## 企业的时点自主性 + +企业董事会不再在 Q3 被固定选址。它对每座城市逐轮输出 `accept / counter / terminate`,并在全局层面输出 `select_now / continue_negotiating / exit_all`。认知层可比较效用差距、信息充分度、其他城市状态和等待成本;规则层只保留两条边界:低于私有最低效用门槛的接受不会执行,达到实验最大谈判轮次时必须选址或退出。 + +因此,同一世界中可能出现 Q1 提前接受、程序退回后 Q2 恢复、连续还价、暂停等待或全部退出,而不是统一沿用 Q1—Q3 的预写节奏。 diff --git a/src/insidegov/api.py b/src/insidegov/api.py index 728d9f4..0998b5a 100644 --- a/src/insidegov/api.py +++ b/src/insidegov/api.py @@ -19,11 +19,14 @@ run_comparison, run_negotiation_comparison, run_negotiation_matrix, + run_organization_mode_comparison, run_talent_comparison, run_talent_matrix, ) from .models import Event from .negotiation_engine import NegotiationEngine +from .organization import action_catalog_payload, behavior_evidence_payload +from .organizational_calibration import run_hefei_nio_organization_calibration from .p2 import ( CandidateRepository, compare_worlds, @@ -48,6 +51,7 @@ class CreateWorldRequest(BaseModel): name: str = "地方产业发展全生命周期" policy_mode: Literal["deterministic", "llm"] = "deterministic" model_name: Literal["deepseek-v4-flash", "deepseek-v4-pro"] | None = None + process_mode: Literal["formal", "informal", "hybrid"] = "hybrid" class CreateTalentWorldRequest(BaseModel): @@ -156,7 +160,7 @@ class DemoRequest(BaseModel): app = FastAPI( title="InsideGov API", - version="0.5.0", + version="0.6.0", description="Reproducible government-business interaction policy laboratory", ) app.add_middleware( @@ -189,7 +193,7 @@ def _public_world(world) -> dict: @app.get("/health") def health() -> dict[str, str]: - return {"status": "ok", "version": "0.5.0"} + return {"status": "ok", "version": "0.6.0"} @app.get("/capabilities") @@ -205,13 +209,35 @@ def capabilities() -> dict: "natural_language_interventions": True, "grounded_interviews": True, "automatic_reports": True, + "multi_step_organization_planning": True, + "open_action_permission_validation": True, + "endogenous_opportunity_windows": True, + "long_term_organization_learning": True, + "source_backed_organization_calibration": True, "material_candidate_worlds": True, "guided_demo": True, "experiment_report_center": True, "joint_investment_funds": True, + "organization_action_sets": True, + "process_modes": ["formal", "informal", "hybrid"], } +@app.get("/organization/actions") +def organization_actions() -> list[dict[str, object]]: + return action_catalog_payload() + + +@app.get("/organization/evidence") +def organization_evidence() -> list[dict[str, str]]: + return behavior_evidence_payload() + + +@app.get("/experiments/organization-modes") +def organization_mode_comparison(seed: int = 42, quarters: int = 16) -> list[dict]: + return run_organization_mode_comparison(seed, quarters) + + @app.post("/demos/hefei-nio") def hefei_nio_demo(request: DemoRequest) -> dict: bundle = create_hefei_nio_demo( @@ -229,6 +255,24 @@ def hefei_nio_sensitivity(seed: int = 42, quarters: int = 16) -> dict: return run_hefei_nio_sensitivity(seed=seed, quarters=quarters) +@app.get("/cases/hefei-nio/organization-calibration") +def hefei_nio_organization_calibration( + seeds: str = "11,23,42,57,89", + modes: str = "formal,informal,hybrid", + quarters: int = 16, +) -> dict: + try: + seed_values = [int(item) for item in seeds.split(",") if item.strip()] + mode_values = [item.strip() for item in modes.split(",") if item.strip()] + return run_hefei_nio_organization_calibration( + seeds=seed_values, + candidate_modes=mode_values, + quarters=quarters, + ) + except ValueError as exc: + raise HTTPException(400, str(exc)) from exc + + @app.get("/experiment-reports") def experiment_reports() -> list[dict]: return experiment_report_repository.list() @@ -359,6 +403,7 @@ def create_world(request: CreateWorldRequest) -> dict: world.name = request.name world.policy_mode = request.policy_mode world.model_name = request.model_name + world.process_mode = request.process_mode engine = SimulationEngine(world) if request.policy_mode == "llm" and engine.world.policy_mode != "llm": engine._event( diff --git a/src/insidegov/cases.py b/src/insidegov/cases.py index 8e47264..c34f29f 100644 --- a/src/insidegov/cases.py +++ b/src/insidegov/cases.py @@ -55,6 +55,9 @@ def create_hefei_nio_world( select a city, settle promises and evolve suppliers through the normal engine. """ world = create_full_lifecycle_world(seed, world_id) + # The published 2020 calibration is anchored to the formal-process baseline; + # callers can still switch this same world to informal or hybrid explicitly. + world.process_mode = "formal" world.name = "真实案例校准:2020合肥—蔚来" hefei = world.cities["city_lin"] hefei.name = "合肥市" diff --git a/src/insidegov/cli.py b/src/insidegov/cli.py index 6c0ad4f..3380545 100644 --- a/src/insidegov/cli.py +++ b/src/insidegov/cli.py @@ -13,10 +13,12 @@ run_experiment_matrix, run_negotiation_comparison, run_negotiation_matrix, + run_organization_mode_comparison, run_talent_comparison, run_talent_matrix, ) from .negotiation_engine import NegotiationEngine +from .organizational_calibration import run_hefei_nio_organization_calibration from .repository import WorldRepository from .scenarios import ( create_full_lifecycle_world, @@ -32,6 +34,13 @@ def main() -> None: run = sub.add_parser("run", help="run the full lifecycle scenario") run.add_argument("--quarters", type=int, default=16) run.add_argument("--seed", type=int, default=42) + run.add_argument("--process-mode", choices=["formal", "informal", "hybrid"], default="hybrid") + organization_compare = sub.add_parser( + "organization-compare", + help="compare formal, informal and hybrid organization processes", + ) + organization_compare.add_argument("--quarters", type=int, default=16) + organization_compare.add_argument("--seed", type=int, default=42) sub.add_parser("compare", help="run four counterfactual branches") matrix = sub.add_parser("matrix", help="run P1 multi-strategy and multi-seed matrix") matrix.add_argument("--seeds", default="11,23,42,57,89") @@ -83,10 +92,23 @@ def main() -> None: case.add_argument("--fiscal-shock", type=float, default=0.0, help="optional Q4 available-budget multiplier, e.g. 0.7") sensitivity = sub.add_parser( "case-hefei-nio-sensitivity", - help="run 15%/25%/35% fiscal-space sensitivity with and without joint funds", + help="run 15%%/25%%/35%% fiscal-space sensitivity with and without joint funds", ) sensitivity.add_argument("--quarters", type=int, default=16) sensitivity.add_argument("--seed", type=int, default=42) + behavior_calibration = sub.add_parser( + "case-hefei-nio-org-calibration", + help="calibrate organization behavior on pre-deal evidence and validate held-out milestones", + ) + behavior_calibration.add_argument("--seeds", default="11,23,42,57,89") + behavior_calibration.add_argument("--quarters", type=int, default=16) + behavior_calibration.add_argument( + "--modes", default="formal,informal,hybrid", + help="comma-separated process modes", + ) + behavior_calibration.add_argument( + "--output", default="docs/reports/hefei-nio-organization-calibration", + ) demo = sub.add_parser("demo", help="build the complete five-minute demo bundle") demo.add_argument("--seed", type=int, default=42) demo.add_argument("--fiscal-multiplier", type=float, default=0.5) @@ -94,6 +116,12 @@ def main() -> None: if args.command == "compare": print(json.dumps(run_comparison(), ensure_ascii=False, indent=2)) return + if args.command == "organization-compare": + print(json.dumps( + run_organization_mode_comparison(args.seed, args.quarters), + ensure_ascii=False, indent=2, + )) + return if args.command == "matrix": seeds = [int(item) for item in args.seeds.split(",")] strategies = ( @@ -186,6 +214,22 @@ def main() -> None: indent=2, )) return + if args.command == "case-hefei-nio-org-calibration": + report = run_hefei_nio_organization_calibration( + seeds=[int(item) for item in args.seeds.split(",") if item.strip()], + candidate_modes=[item.strip() for item in args.modes.split(",") if item.strip()], + quarters=args.quarters, + output_dir=args.output, + ) + print(json.dumps({ + "case": report["case"], + "selected_process_mode": report["selected_process_mode"], + "selected_mode_quality": report["selected_mode_quality"], + "mode_summary": report["mode_summary"], + "report_dir": args.output, + "limitations": report["limitations"], + }, ensure_ascii=False, indent=2)) + return if args.command == "demo": bundle = create_hefei_nio_demo( WorldRepository(), args.seed, args.fiscal_multiplier, @@ -200,12 +244,16 @@ def main() -> None: "sensitivity": bundle["sensitivity"], }, ensure_ascii=False, indent=2, default=str)) return - engine = SimulationEngine(create_full_lifecycle_world(args.seed)) + world = create_full_lifecycle_world(args.seed) + world.process_mode = args.process_mode + engine = SimulationEngine(world) engine.run(args.quarters) summary = { "world": engine.world.name, "quarter": engine.world.quarter, "selected_city": engine.world.selected_city_id, + "process_mode": engine.world.process_mode, + "organization_actions": engine.world.to_dict()["organization_actions"], "metrics": engine.world.to_dict()["history"][-1], "latest_events": engine.world.to_dict()["events"][-8:], } diff --git a/src/insidegov/organizational_calibration.py b/src/insidegov/organizational_calibration.py new file mode 100644 index 0000000..bb79831 --- /dev/null +++ b/src/insidegov/organizational_calibration.py @@ -0,0 +1,714 @@ +"""Source-backed organizational behavior calibration and quality evaluation. + +This module deliberately separates three things that are often mixed together: + +1. public facts used to configure a case; +2. observable organizational behavior used for calibration; +3. later historical outcomes held out for validation. + +Private conversations that are absent from public records are never scored as +historical ground truth. They remain simulated mechanisms and are reported as +unverifiable rather than counted as either matches or errors. +""" + +from __future__ import annotations + +import json +import math +import statistics +from collections.abc import Callable, Iterable +from dataclasses import asdict, dataclass, field +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +from .agents import CognitiveProvider +from .cases import ( + NIO_2020_20F_URL, + NIO_AGREEMENT_URL, + NIO_FULFILLMENT_URL, + create_hefei_nio_world, +) +from .engine import SimulationEngine +from .models import WorldState +from .serde import world_from_dict + +NIO_SHAREHOLDERS_AGREEMENT_URL = ( + "https://www.sec.gov/Archives/edgar/data/1736541/" + "000110465920061585/nio-20191231xex4d36.htm" +) +MOFCOM_FRAMEWORK_URL = ( + "https://tradeinservices.mofcom.gov.cn/article/news/gnxw/202002/99256.html" +) +MOFCOM_HEADQUARTERS_URL = ( + "https://tradeinservices.mofcom.gov.cn/article/shidian/jyjliu/202201/125856.html" +) +SASAC_COALITION_URL = ( + "https://wap.sasac.gov.cn/n2588025/n2588129/c18459830/content.html" +) + + +@dataclass(slots=True) +class HistoricalBehaviorBenchmark: + id: str + label: str + historical_date: str + split: str + evidence_status: str + weight: float + observable_claim: str + matcher: str | None + sources: list[str] + rationale: str + quantitative: bool = True + + +@dataclass(slots=True) +class BehaviorMatch: + benchmark_id: str + label: str + split: str + score: float | None + weight: float + observed: str + matched: bool | None + evidence_status: str + sources: list[str] + note: str = "" + + +@dataclass(slots=True) +class WorldBehaviorEvaluation: + world_id: str + seed: int + process_mode: str + calibration_score: float + holdout_score: float + role_boundary_score: float + evidence_discipline_score: float + sequence_score: float + composite_score: float + matches: list[BehaviorMatch] = field(default_factory=list) + unsupported_internal_actions: list[str] = field(default_factory=list) + + +def hefei_nio_behavior_benchmarks() -> list[HistoricalBehaviorBenchmark]: + """Return the case codebook, including explicitly unobservable mechanisms.""" + + return [ + HistoricalBehaviorBenchmark( + id="framework_before_definitive", + label="先框架协议、后正式投资协议", + historical_date="2020-02-25 → 2020-04-29", + split="calibration", + evidence_status="direct_public_record", + weight=1.2, + observable_claim=( + "2月先签总部落户合作框架,4月再由多方签署正式投资与股东协议。" + ), + matcher="framework_before_definitive", + sources=[MOFCOM_FRAMEWORK_URL, NIO_SHAREHOLDERS_AGREEMENT_URL], + rationale="校准分阶段议程化,而不是要求公开材料披露每次内部会商。", + ), + HistoricalBehaviorBenchmark( + id="hybrid_mobilization_and_review", + label="招商推动与正式治理并存", + historical_date="2020-02 → 2020-04", + split="calibration", + evidence_status="triangulated_inference", + weight=0.8, + observable_claim=( + "疫情期间持续远程招商并形成框架协议;最终协议包含多投资人、公司治理、" + "出资条件和正式权利义务。" + ), + matcher="hybrid_mobilization_and_review", + sources=[MOFCOM_FRAMEWORK_URL, NIO_SHAREHOLDERS_AGREEMENT_URL], + rationale=( + "只校准非正式动员与正式审查两类机制均出现,不宣称已知真实部门行动顺序。" + ), + ), + HistoricalBehaviorBenchmark( + id="multi_party_investor_coalition", + label="多层级投资主体联合出资", + historical_date="2020-04-29", + split="calibration", + evidence_status="direct_public_record", + weight=1.3, + observable_claim=( + "股东协议列明国投招商、安徽省高新投、合肥市建投等投资主体共同参与。" + ), + matcher="multi_party_investor_coalition", + sources=[NIO_SHAREHOLDERS_AGREEMENT_URL, SASAC_COALITION_URL], + rationale="检验组织世界是否把70亿元错误建模为单一财政局现金支出。", + ), + HistoricalBehaviorBenchmark( + id="equity_not_cash_subsidy", + label="核心工具是股权投资而非无条件现金补贴", + historical_date="2020-04-29", + split="calibration", + evidence_status="direct_public_record", + weight=1.4, + observable_claim="战略投资者以70亿元现金增资取得约24.1%股权。", + matcher="equity_not_cash_subsidy", + sources=[NIO_AGREEMENT_URL, NIO_2020_20F_URL], + rationale="金额是已使用的结构校准点,因此不能再次作为留出预测证据。", + ), + HistoricalBehaviorBenchmark( + id="five_equity_installments", + label="股权资本分期而非一次性支付", + historical_date="2020-04-29", + split="calibration", + evidence_status="direct_public_record", + weight=1.2, + observable_claim="战略投资者70亿元按35、15、10、5、5亿元五期投入。", + matcher="five_equity_installments", + sources=[NIO_AGREEMENT_URL, NIO_SHAREHOLDERS_AGREEMENT_URL], + rationale="校准合同结构;不要求模拟恰好复制每一期金额。", + ), + HistoricalBehaviorBenchmark( + id="performance_contingent_support", + label="后续支持与经营绩效条件挂钩", + historical_date="2020-04-29", + split="calibration", + evidence_status="direct_public_record", + weight=1.0, + observable_claim=( + "经开区租金、金融与税收支持以产能、采购额和车辆销售等绩效目标为条件。" + ), + matcher="performance_contingent_support", + sources=[NIO_2020_20F_URL], + rationale="检验合同是否具有条件性,而不是把所有承诺在签约时立即兑现。", + ), + HistoricalBehaviorBenchmark( + id="early_cash_injection", + label="首两期投资基本按期到账", + historical_date="2020-06", + split="holdout", + evidence_status="direct_public_record", + weight=1.4, + observable_claim="截至6月底,首两期50亿元中48亿元已到账。", + matcher="early_cash_injection", + sources=[NIO_FULFILLMENT_URL, NIO_2020_20F_URL], + rationale="留出节点;用归一化早期履约里程碑检验,不参与模式选择。", + ), + HistoricalBehaviorBenchmark( + id="headquarters_operational", + label="总部从协议进入实际运营", + historical_date="2020-10-09", + split="holdout", + evidence_status="direct_public_record", + weight=1.0, + observable_claim="蔚来中国总部于2020年10月正式启用。", + matcher="headquarters_operational", + sources=[MOFCOM_HEADQUARTERS_URL], + rationale="用项目投产/运营里程碑检验组织承诺是否进入执行状态。", + ), + HistoricalBehaviorBenchmark( + id="continued_cluster_cooperation", + label="落地后继续推进产业链合作", + historical_date="2021以后", + split="holdout", + evidence_status="triangulated_public_record", + weight=0.8, + observable_claim="政企继续推进智能电动汽车产业集群与上下游协同。", + matcher="continued_cluster_cooperation", + sources=[MOFCOM_HEADQUARTERS_URL, SASAC_COALITION_URL], + rationale="只能验证方向和是否出现扩散,不能把全市产业增长归因给单一项目。", + ), + HistoricalBehaviorBenchmark( + id="private_department_dialogue", + label="财政局具体否决措辞与会前游说网络", + historical_date="2020-02 → 2020-04", + split="unverifiable", + evidence_status="not_publicly_observable", + weight=0.0, + observable_claim="公开材料不足以恢复财政局、招商部门和市领导的逐轮内部对话。", + matcher=None, + sources=[NIO_SHAREHOLDERS_AGREEMENT_URL], + rationale="模型可以生成该机制,但不得将其标为真实历史事实或纳入命中率。", + quantitative=False, + ), + ] + + +def evaluate_hefei_nio_behavior( + calibration_world: WorldState, + holdout_world: WorldState, +) -> WorldBehaviorEvaluation: + """Score one run against observable case behavior with an honest holdout.""" + + matches = [ + _match_benchmark(item, calibration_world, holdout_world) + for item in hefei_nio_behavior_benchmarks() + ] + calibration_score = _weighted_score(matches, "calibration") + holdout_score = _weighted_score(matches, "holdout") + role_boundary_score = _role_boundary_score(calibration_world) + evidence_discipline_score, unsupported = _evidence_discipline(calibration_world) + sequence_score = _sequence_score(calibration_world) + composite = 100 * ( + 0.35 * calibration_score + + 0.35 * holdout_score + + 0.12 * role_boundary_score + + 0.10 * evidence_discipline_score + + 0.08 * sequence_score + ) + return WorldBehaviorEvaluation( + world_id=holdout_world.id, + seed=holdout_world.seed, + process_mode=holdout_world.process_mode, + calibration_score=round(calibration_score * 100, 2), + holdout_score=round(holdout_score * 100, 2), + role_boundary_score=round(role_boundary_score * 100, 2), + evidence_discipline_score=round(evidence_discipline_score * 100, 2), + sequence_score=round(sequence_score * 100, 2), + composite_score=round(composite, 2), + matches=matches, + unsupported_internal_actions=unsupported, + ) + + +def run_hefei_nio_organization_calibration( + seeds: Iterable[int] = (11, 23, 42, 57, 89), + candidate_modes: Iterable[str] = ("formal", "informal", "hybrid"), + quarters: int = 16, + output_dir: str | Path | None = None, + cognition_factory: Callable[[str, int], CognitiveProvider] | None = None, +) -> dict[str, Any]: + """Select a process mode on calibration events, then report holdout quality. + + The winning mode is chosen *only* using the calibration split. Later cash + injection, headquarters operation and cluster cooperation never influence + model selection. This prevents the most obvious form of historical leakage. + """ + + seed_list = list(seeds) + mode_list = list(candidate_modes) + if not seed_list: + raise ValueError("at least one seed is required") + if not mode_list or set(mode_list) - {"formal", "informal", "hybrid"}: + raise ValueError("candidate_modes must contain formal, informal or hybrid") + if quarters < 9: + raise ValueError("quarters must be at least 9 for holdout milestone evaluation") + + evaluations: list[WorldBehaviorEvaluation] = [] + for mode in mode_list: + for seed in seed_list: + world = create_hefei_nio_world( + seed, + world_id=f"hefei-nio-org-calibration-{mode}-{seed}", + ) + world.process_mode = mode + cognition = cognition_factory(mode, seed) if cognition_factory else None + engine = SimulationEngine(world, cognition=cognition) + for _ in range(3): + engine.step() + calibration_world = world_from_dict(engine.world.to_dict()) + for _ in range(quarters - 3): + engine.step() + evaluations.append(evaluate_hefei_nio_behavior(calibration_world, engine.world)) + + mode_summary = [_summarize_mode(mode, evaluations) for mode in mode_list] + # Mode selection sees calibration metrics and boundary validity only. It does + # not use holdout_score or final composite_score. + selected = max( + mode_summary, + key=lambda row: ( + row["selection_score"], + row["calibration_score_mean"], + -row["calibration_score_stdev"], + ), + ) + selected_mode = selected["process_mode"] + selected_runs = [item for item in evaluations if item.process_mode == selected_mode] + benchmark_summary = _summarize_benchmarks(selected_runs) + quality_gaps = [item for item in benchmark_summary if item["score_mean"] < 90] + zero_variation = all( + row["calibration_score_stdev"] == 0 and row["holdout_score_stdev"] == 0 + for row in mode_summary + ) + report = { + "schema_version": "1.0", + "generated_at": datetime.now(UTC).isoformat(), + "case": "2020合肥—蔚来", + "evaluation_type": "organizational_behavior_calibration_with_holdout", + "cognition_policy": ( + "injected_provider" if cognition_factory else "deterministic" + ), + "seeds": seed_list, + "candidate_modes": mode_list, + "calibration_cutoff": "2020-04-29 definitive agreements", + "holdout_events": [ + "2020-06 early cash injection", + "2020-10 headquarters operational", + "post-deal cluster cooperation", + ], + "selected_process_mode": selected_mode, + "selection_rule": ( + "highest mean calibration-split score with role-boundary and evidence-discipline " + "guardrails; holdout outcomes excluded from selection" + ), + "mode_summary": mode_summary, + "selected_mode_quality": { + "calibration_score_mean": _mean(item.calibration_score for item in selected_runs), + "holdout_score_mean": _mean(item.holdout_score for item in selected_runs), + "composite_score_mean": _mean(item.composite_score for item in selected_runs), + "composite_score_stdev": _stdev(item.composite_score for item in selected_runs), + }, + "selected_mode_benchmark_summary": benchmark_summary, + "quality_gaps": quality_gaps, + "diagnostics": { + "zero_score_variation_across_seeds": zero_variation, + "interpretation": ( + "各seed行为得分完全一致,说明真实案例的强参数锚定和确定性策略压过了" + "随机异质性;这提高复现性,但不能作为行为鲁棒性的充分证据。" + if zero_variation else + "不同seed产生了可观察质量差异,应结合均值、方差和失败轨迹解释。" + ), + }, + "benchmarks": [asdict(item) for item in hefei_nio_behavior_benchmarks()], + "runs": [ + { + **asdict(item), + "matches": [asdict(match) for match in item.matches], + } + for item in evaluations + ], + "limitations": [ + "70亿元股权总量已用于结构校准,不能再作为独立预测成功证据。", + "公开材料没有披露逐轮内部会商,模拟对话只代表机制假设。", + "季度是归一化政策阶段,不与现实自然月机械对应。", + "后续全市产业链增长不能被识别为蔚来项目的单独因果贡献。", + "单案例只能做内部效度检查,跨案例外部效度仍需第二个案例。", + "确定性案例在当前seed集合上的行为得分可能缺少变异,不能替代LLM与专家复核。", + ], + } + report["markdown"] = render_organization_calibration_markdown(report) + if output_dir is not None: + root = Path(output_dir) + root.mkdir(parents=True, exist_ok=True) + (root / "report.json").write_text( + json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8" + ) + (root / "report.md").write_text(report["markdown"], encoding="utf-8") + return report + + +def render_organization_calibration_markdown(report: dict[str, Any]) -> str: + selected = report["selected_mode_quality"] + lines = [ + "# 2020合肥—蔚来:组织行为校准与质量评估", + "", + "## 结论", + "", + f"- 校准集选择的过程模式:**{report['selected_process_mode']}**;", + f"- 校准行为得分:**{selected['calibration_score_mean']:.1f}/100**;", + f"- 留出历史节点得分:**{selected['holdout_score_mean']:.1f}/100**;", + ( + f"- 综合质量:**{selected['composite_score_mean']:.1f}/100** " + f"(跨seed标准差 {selected['composite_score_stdev']:.2f})。" + ), + "", + ( + "模式只根据2020年4月29日前的行为证据选择;6月到账、10月总部启用和后续" + "产业协同为留出验证,不参与选择。" + ), + "", + "## 模式比较", + "", + "| 模式 | 校准行为 | 留出验证 | 角色边界 | 证据纪律 | 顺序 | 综合 |", + "|---|---:|---:|---:|---:|---:|---:|", + ] + for row in report["mode_summary"]: + lines.append( + f"| {row['process_mode']} | {row['calibration_score_mean']:.1f} | " + f"{row['holdout_score_mean']:.1f} | {row['role_boundary_score_mean']:.1f} | " + f"{row['evidence_discipline_score_mean']:.1f} | {row['sequence_score_mean']:.1f} | " + f"{row['composite_score_mean']:.1f} |" + ) + lines.extend([ + "", + "## 证据纪律", + "", + ( + "财政局具体否决措辞、会前游说对象和真实内部行动顺序没有公开记录,因此被编码为" + "`not_publicly_observable`,不进入命中率。系统只能说“模拟生成了一个可能机制”," + "不能说“复现了真实内部对话”。" + ), + "", + "## 未完全匹配的节点", + "", + "## 主要限制", + "", + ]) + gaps = report.get("quality_gaps", []) + if gaps: + insertion = [ + f"- {item['label']}:{item['score_mean']:.1f}/100;{item['observed_example']}" + for item in gaps + ] + marker = lines.index("## 主要限制") + lines[marker:marker] = insertion + [""] + else: + marker = lines.index("## 主要限制") + lines[marker:marker] = ["- 当前阈值下没有低于90分的节点。", ""] + lines.extend(f"- {item}" for item in report["limitations"]) + return "\n".join(lines) + "\n" + + +def _match_benchmark( + benchmark: HistoricalBehaviorBenchmark, + calibration_world: WorldState, + holdout_world: WorldState, +) -> BehaviorMatch: + if not benchmark.quantitative or benchmark.matcher is None: + return BehaviorMatch( + benchmark.id, + benchmark.label, + benchmark.split, + None, + benchmark.weight, + "公开资料不可观察;不评分", + None, + benchmark.evidence_status, + benchmark.sources, + benchmark.rationale, + ) + matcher = globals()[f"_match_{benchmark.matcher}"] + score, observed, note = matcher(calibration_world, holdout_world) + score = min(1.0, max(0.0, float(score))) + return BehaviorMatch( + benchmark.id, + benchmark.label, + benchmark.split, + round(score, 4), + benchmark.weight, + observed, + score >= 0.75, + benchmark.evidence_status, + benchmark.sources, + note, + ) + + +def _match_framework_before_definitive(train: WorldState, _: WorldState): + rounds = sorted({item.quarter for item in train.external_negotiations if item.city_id == "city_lin"}) + score = 0.55 * min(1.0, len(rounds) / 2) + 0.45 * (train.selected_city_id == "city_lin") + return score, f"合肥对外协商季度={rounds};Q3选址={train.selected_city_id}", "阶段而非自然月对齐" + + +def _match_hybrid_mobilization_and_review(train: WorldState, _: WorldState): + actions = [ + item for item in train.organization_actions + if item.city_id == "city_lin" and item.authorized + ] + informal_ids = { + "preconsult_finance", "mobilize_park_coalition", "frame_strategic_project", + "disclose_fiscal_boundary", "broker_compromise", "propose_open_action", + } + formal_ids = {"risk_assessment", "legality_review", "collective_deliberation"} + informal = sum(item.action_id in informal_ids for item in actions) + formal = sum(item.action_id in formal_ids for item in actions) + score = (0.5 if informal else 0.0) + (0.5 if formal else 0.0) + return score, f"非正式动员动作={informal};正式审查动作={formal}", "不解释为真实逐轮行动" + + +def _match_multi_party_investor_coalition(train: WorldState, _: WorldState): + funds = len(train.investment_funds) + reviewed = { + item.actor_id for item in train.organization_actions + if item.city_id == "city_lin" and item.action_id == "fund_due_diligence" + and item.authorized + } + score = 0.55 * min(1.0, funds / 3) + 0.45 * min(1.0, len(reviewed) / 3) + return score, f"独立资金池={funds};发生尽调的主体={len(reviewed)}", "" + + +def _match_equity_not_cash_subsidy(train: WorldState, _: WorldState): + offer = train.cities["city_lin"].active_offer + if offer is None: + return 0.0, "未形成合肥报价", "" + total_equity = offer.total_equity_support + amount_score = max(0.0, 1 - abs(total_equity - 70.0) / 70.0) + structure_score = min(1.0, total_equity / max(total_equity + offer.subsidy, 1.0)) + score = 0.65 * amount_score + 0.35 * structure_score + return score, ( + f"股权支持={total_equity:.2f}亿元;补贴={offer.subsidy:.2f}亿元" + ), "70亿元是结构校准点,不计入留出预测" + + +def _match_five_equity_installments(train: WorldState, _: WorldState): + offer = train.cities["city_lin"].active_offer + if offer is None: + return 0.0, "未形成支付计划", "" + rows = [item for item in offer.payment_schedule if "equity" in item.item] + count_score = max(0.0, 1 - abs(len(rows) - 5) / 5) + staged_score = 1.0 if len({item.due_offset for item in rows}) >= 3 else 0.5 + score = 0.65 * count_score + 0.35 * staged_score + return score, ( + f"股权相关期次={len(rows)};到期阶段={sorted({item.due_offset for item in rows})}" + ), "只校准分期结构,不拟合每期金额" + + +def _match_performance_contingent_support(train: WorldState, _: WorldState): + offer = train.cities["city_lin"].active_offer + if offer is None: + return 0.0, "未形成条件承诺", "" + conditional = [ + item.condition for item in offer.payment_schedule + if item.condition not in {"contract_signed", "unconditional"} + ] + score = min(1.0, len(set(conditional)) / 3) + return score, f"条件节点={sorted(set(conditional))}", "" + + +def _match_early_cash_injection(_: WorldState, holdout: WorldState): + early = [ + item for item in holdout.promises + if item.city_id == "city_lin" and item.item == "external_equity" + and item.due_quarter <= 5 + ] + paid = sum(item.paid_amount for item in early) + # Historical holdout is 48 of the first 50 billion. The model is rewarded + # for both substantial fulfillment and approximate timing, not exact identity. + amount_score = max(0.0, 1 - abs(paid - 48.0) / 48.0) + fulfillment_score = sum(item.paid_amount > 0 for item in early) / max(len(early), 1) + score = 0.7 * amount_score + 0.3 * fulfillment_score + return score, f"归一化早期外部股权到账={paid:.2f}亿元", "历史基准48亿元" + + +def _match_headquarters_operational(_: WorldState, holdout: WorldState): + anchor = holdout.firms["firm_nova"] + milestone_quarters = [ + item.quarter for item in holdout.events + if item.kind == "milestone" and "投产" in item.title + ] + on_time = bool(milestone_quarters and min(milestone_quarters) <= 9) + score = 1.0 if on_time else (0.65 if anchor.operating else min(0.6, anchor.project_progress)) + return score, ( + f"项目进度={anchor.project_progress:.1%};首次投产里程碑=" + f"{min(milestone_quarters) if milestone_quarters else '无'}" + ), "用Q9作为归一化总部运营节点" + + +def _match_continued_cluster_cooperation(_: WorldState, holdout: WorldState): + final = holdout.history[-1] + score = min(1.0, max(0.0, (final.cluster_size - 1) / 8)) + return score, f"Q{holdout.quarter}集群规模={final.cluster_size}", "只评价扩散方向" + + +def _sequence_score(world: WorldState) -> float: + actions = [ + item for item in world.organization_actions + if item.city_id == "city_lin" and item.authorized + ] + indexed = { + "mobilization": min((item.quarter for item in actions if item.action_id in { + "preconsult_finance", "mobilize_park_coalition", "frame_strategic_project", + "request_materials", "clarify_need", + }), default=99), + "formal_review": min((item.quarter for item in actions if item.action_id in { + "risk_assessment", "legality_review", "collective_deliberation", + }), default=99), + "fund_review": min((item.quarter for item in actions if item.action_id == "fund_due_diligence"), default=99), + } + present = sum(value < 99 for value in indexed.values()) / 3 + ordered = 1.0 if indexed["mobilization"] <= indexed["formal_review"] <= indexed["fund_review"] else 0.5 + return 0.7 * present + 0.3 * ordered + + +def _role_boundary_score(world: WorldState) -> float: + relevant = [item for item in world.organization_actions if item.city_id == "city_lin"] + if not relevant: + return 0.0 + violations = 0 + for item in relevant: + if item.action_id == "fund_due_diligence" and item.actor_role != "fund": + violations += 1 + if item.action_id == "legality_review" and item.actor_role != "legal": + violations += 1 + if item.action_id == "risk_assessment" and item.actor_role != "finance": + violations += 1 + if item.action_id == "collective_deliberation" and item.actor_role != "city_leader": + violations += 1 + return max(0.0, 1 - violations / len(relevant)) + + +def _evidence_discipline(world: WorldState) -> tuple[float, list[str]]: + relevant = [item for item in world.organization_actions if item.city_id == "city_lin"] + if not relevant: + return 0.0, ["没有组织行动可审计"] + grounded = [item for item in relevant if item.evidence_ids] + unsupported = [ + f"Q{item.quarter}:{item.actor_id}:{item.action_id}" + for item in relevant if not item.evidence_ids + ] + # Internally generated rationales are acceptable only when kept in the audit + # trail as simulation output. They are not promoted into the benchmark. + return len(grounded) / len(relevant), unsupported + + +def _weighted_score(matches: list[BehaviorMatch], split: str) -> float: + rows = [item for item in matches if item.split == split and item.score is not None] + denominator = sum(item.weight for item in rows) + return sum(float(item.score) * item.weight for item in rows) / max(denominator, 1e-9) + + +def _summarize_mode( + mode: str, evaluations: list[WorldBehaviorEvaluation] +) -> dict[str, Any]: + rows = [item for item in evaluations if item.process_mode == mode] + calibration = _mean(item.calibration_score for item in rows) + boundary = _mean(item.role_boundary_score for item in rows) + evidence = _mean(item.evidence_discipline_score for item in rows) + sequence = _mean(item.sequence_score for item in rows) + selection_score = 0.70 * calibration + 0.12 * boundary + 0.10 * evidence + 0.08 * sequence + return { + "process_mode": mode, + "runs": len(rows), + "selection_score": round(selection_score, 2), + "calibration_score_mean": calibration, + "calibration_score_stdev": _stdev(item.calibration_score for item in rows), + "holdout_score_mean": _mean(item.holdout_score for item in rows), + "holdout_score_stdev": _stdev(item.holdout_score for item in rows), + "role_boundary_score_mean": boundary, + "evidence_discipline_score_mean": evidence, + "sequence_score_mean": sequence, + "composite_score_mean": _mean(item.composite_score for item in rows), + "composite_score_stdev": _stdev(item.composite_score for item in rows), + } + + +def _summarize_benchmarks( + evaluations: list[WorldBehaviorEvaluation], +) -> list[dict[str, Any]]: + benchmark_rows: dict[str, list[BehaviorMatch]] = {} + for evaluation in evaluations: + for match in evaluation.matches: + if match.score is not None: + benchmark_rows.setdefault(match.benchmark_id, []).append(match) + results = [] + for benchmark in hefei_nio_behavior_benchmarks(): + rows = benchmark_rows.get(benchmark.id, []) + if not rows: + continue + results.append({ + "benchmark_id": benchmark.id, + "label": benchmark.label, + "split": benchmark.split, + "score_mean": _mean(float(item.score) * 100 for item in rows), + "score_stdev": _stdev(float(item.score) * 100 for item in rows), + "observed_example": rows[0].observed, + "evidence_status": benchmark.evidence_status, + }) + return results + + +def _mean(values: Iterable[float]) -> float: + rows = list(values) + return round(statistics.fmean(rows), 2) if rows else math.nan + + +def _stdev(values: Iterable[float]) -> float: + rows = list(values) + return round(statistics.stdev(rows), 2) if len(rows) > 1 else 0.0 diff --git a/tests/test_organizational_calibration.py b/tests/test_organizational_calibration.py new file mode 100644 index 0000000..39656c2 --- /dev/null +++ b/tests/test_organizational_calibration.py @@ -0,0 +1,71 @@ +import json + +from fastapi.testclient import TestClient + +from insidegov.api import app +from insidegov.organizational_calibration import ( + hefei_nio_behavior_benchmarks, + run_hefei_nio_organization_calibration, +) + +client = TestClient(app) + + +def test_case_codebook_separates_calibration_holdout_and_unobservable_events(): + benchmarks = hefei_nio_behavior_benchmarks() + assert {item.split for item in benchmarks} == { + "calibration", "holdout", "unverifiable", + } + unobservable = [item for item in benchmarks if item.split == "unverifiable"] + assert unobservable + assert all(not item.quantitative and item.matcher is None for item in unobservable) + assert all(item.sources for item in benchmarks) + + +def test_behavior_calibration_selects_mode_without_using_holdout(tmp_path): + report = run_hefei_nio_organization_calibration( + seeds=[11, 42], + quarters=16, + output_dir=tmp_path, + ) + assert report["selected_process_mode"] == "hybrid" + assert "holdout outcomes excluded" in report["selection_rule"] + assert report["selected_mode_quality"]["calibration_score_mean"] > 90 + assert report["selected_mode_quality"]["holdout_score_mean"] < 100 + summaries = {item["process_mode"]: item for item in report["mode_summary"]} + assert summaries["hybrid"]["selection_score"] > summaries["formal"]["selection_score"] + assert summaries["hybrid"]["selection_score"] > summaries["informal"]["selection_score"] + assert report["quality_gaps"] + assert any( + item["benchmark_id"] == "early_cash_injection" + for item in report["quality_gaps"] + ) + assert report["diagnostics"]["zero_score_variation_across_seeds"] is True + assert (tmp_path / "report.json").exists() + assert (tmp_path / "report.md").exists() + saved = json.loads((tmp_path / "report.json").read_text(encoding="utf-8")) + assert saved["selected_process_mode"] == "hybrid" + + +def test_behavior_calibration_api_returns_source_backed_report(): + response = client.get( + "/cases/hefei-nio/organization-calibration", + params={"seeds": "42", "modes": "formal,hybrid", "quarters": 16}, + ) + assert response.status_code == 200 + payload = response.json() + assert payload["evaluation_type"] == "organizational_behavior_calibration_with_holdout" + assert payload["selected_process_mode"] == "hybrid" + assert any( + item["evidence_status"] == "not_publicly_observable" + for item in payload["benchmarks"] + ) + + +def test_formal_mode_no_longer_leaks_informal_discretionary_actions(): + report = run_hefei_nio_organization_calibration( + seeds=[42], candidate_modes=["formal", "hybrid"], quarters=16, + ) + formal = next(item for item in report["runs"] if item["process_mode"] == "formal") + hybrid = next(item for item in report["runs"] if item["process_mode"] == "hybrid") + assert formal["calibration_score"] < hybrid["calibration_score"] From e0b09d4da4821efa2c37c8f96015bd7b1856eccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=A0=E6=AC=A3?= <115392788+lizhangxin666@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:21:20 +0800 Subject: [PATCH 3/3] feat(web): add auditable organization behavior workbench --- CHANGELOG.md | 21 + README.md | 13 +- apps/web/app/globals.css | 2 + apps/web/app/lib/api.ts | 555 ++++++++++--- apps/web/app/page.tsx | 1547 ++++++++++++++++++++++++++++++++++-- apps/web/package-lock.json | 4 +- apps/web/package.json | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 9 files changed, 1942 insertions(+), 206 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9528b02..af9e937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ 本项目遵循语义化版本。日期使用北京时间。 +## [0.6.0] - 2026-08-13 + +### Added + +- 组织作为行动者的自主行为内核,为招商、财政、园区、市领导、司法审查、企业和产业基金配置角色专属行动空间; +- 正式科层、非正式政治动力学与混合三种过程模式,以及议程启动、暂停、退回、恢复和重新议程化; +- 跨季度计划树、目录外候选行动审查、内生政治机会窗口和长期组织学习; +- 企业自主接受、还价、等待、选址与退出决策,以及模型建议、规则修正和最终执行的审计轨迹; +- 合肥—蔚来组织行为校准与留出集验证,支持多模式、多随机种子比较和可复现报告。 + +### Changed + +- Web 回放界面增加组织计划、行动时机、程序状态、机会窗口、权限检查、组织学习和复盘展示; +- API、序列化结构和 TypeScript 类型扩展到组织行为与校准结果; +- 项目、Web 和健康检查版本升级到 0.6.0。 + +### Fixed + +- 阻止正式科层模式泄漏仅适用于非正式过程的候选行动; +- 将财政、法律、合同和生产约束保留为确定性硬边界,避免自主行动越权修改世界状态。 + ## [0.5.0] - 2026-08-13 ### Added diff --git a/README.md b/README.md index b2c19d0..0543742 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ InsideGov 是一个面向政企互动研究与政策演示的多智能体实验 - **实验必须可复现**:种子、配置、干预和事件日志构成完整实验记录。 - **seed 生成世界,不是只生成噪声**:种子同时决定城市财政/产业禀赋、企业私有偏好、部门底线、信用先验和项目扰动。 - **三个场景共用一个世界**:招商、履约和产业演化不是三套脚本,而是一条因果链。 +- **组织而非个人是行动者**:不同部门拥有不同动作集并自主决定行动或等待;议程、会前沟通、联盟、风险政策、程序转换和行动顺序可以变化,财政、合同与生产边界仍保持刚性。 ## 快速开始 @@ -32,6 +33,14 @@ uv run insidegov demo --seed 42 --fiscal-multiplier 0.5 uv run insidegov case-hefei-nio --seed 42 --quarters 16 uv run insidegov case-hefei-nio-sensitivity --seed 42 --quarters 16 uv run insidegov run --quarters 16 +# 同一 seed 比较正式科层、非正式动力学与混合过程 +uv run insidegov organization-compare --seed 42 --quarters 16 + +# 真实案例的组织行为校准:校准集选模式,后续节点做留出验证 +uv run insidegov case-hefei-nio-org-calibration \ + --seeds 11,23,42,57,89 \ + --modes formal,informal,hybrid \ + --quarters 16 uv run insidegov compare uv run insidegov matrix --no-llm # 只跑确定性 + Flash(检查点保存在 .insidegov) @@ -80,6 +89,8 @@ tests/ 可复现性和关键约束测试 ## 当前能力 - 三座异质城市、招商局—财政局—市领导—企业董事会可执行 Agent 与供应商网络; +- **组织行动内核**:招商、财政、司法审查、园区、市领导、企业与产业基金拥有角色专属动作集;各部门自主争夺有限注意力,正式程序可启动、暂停、退回、恢复或重新议程化,企业可在任意轮次接受、还价、退出、等待或选址;前端完整回放模型选择、规则门与行动后复盘。 +- **组织自主学习闭环**:组织保存跨季度计划树并比较候选策略;可提出目录外行动并接受职责和资源审查;上级政策、会议、竞争、财政、舆情和领导更替形成内生机会窗口;否决、协调和企业回应持续更新信任、策略偏好、组织惯例与可迁移教训。 - Agent 私有观察、记忆检索、结构化行动、事后复盘与财政否决协调; - 多维招商政策包和企业异质偏好; - 有条件承诺、财政支付、延期与信誉更新; @@ -97,7 +108,7 @@ tests/ 可复现性和关键约束测试 ## 产品文档 -完整的产品定位、目标用户、端到端流程、世界与 Agent 设计、功能需求、验收标准和版本路线见 [docs/PRODUCT.md](docs/PRODUCT.md)。系统实现另见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md),研究机制与结论边界见 [docs/MODEL.md](docs/MODEL.md)。人才对接场景的设计理念、机制量化与实验结果见 [docs/TALENT_SCENARIO.md](docs/TALENT_SCENARIO.md);政企协商机制实验室的七机制设计、对齐度量、H1-H7 假设与帕累托结论见 [docs/NEGOTIATION_LAB.md](docs/NEGOTIATION_LAB.md)。 +完整的产品定位、目标用户、端到端流程、世界与 Agent 设计、功能需求、验收标准和版本路线见 [docs/PRODUCT.md](docs/PRODUCT.md)。组织行动的现实依据与编码边界见 [docs/research/organizational-behavior-evidence.md](docs/research/organizational-behavior-evidence.md),计划、开放行动、机会窗口和学习机制见 [docs/research/organization-autonomy-upgrade.md](docs/research/organization-autonomy-upgrade.md)。系统实现另见 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md),研究机制与结论边界见 [docs/MODEL.md](docs/MODEL.md)。人才对接场景的设计理念、机制量化与实验结果见 [docs/TALENT_SCENARIO.md](docs/TALENT_SCENARIO.md);政企协商机制实验室的七机制设计、对齐度量、H1-H7 假设与帕累托结论见 [docs/NEGOTIATION_LAB.md](docs/NEGOTIATION_LAB.md)。 ## 研究边界 diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 364f015..ceb47ad 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -21,5 +21,7 @@ button:disabled { opacity:.5;cursor:not-allowed!important; } .trace-drawer{margin:0 12px 12px;max-width:1696px;margin-inline:auto}.trace-grid{padding:9px}.trace-grid article{display:grid;grid-template-columns:35px 135px 1.1fr 1.4fr 85px 120px;align-items:center;gap:12px;padding:11px;border-bottom:1px solid #203438}.trace-grid small{font-size:8px;color:var(--muted)}.trace-grid h3{font-size:11px;margin:3px 0}.trace-grid p{font-size:9px;margin:3px 0;color:#9fb0b4}.trace-no{font:11px var(--font-geist-mono);color:#496167}.trace-grid>article>strong{font:13px var(--font-geist-mono);color:var(--amber)}.trace-grid button{border:0;background:transparent;color:var(--cyan);font-size:9px;cursor:pointer}footer{height:42px;border-top:1px solid var(--line);display:flex;justify-content:space-between;align-items:center;padding:0 22px;color:#50676c;font-size:9px;max-width:1720px;margin:auto} .empty-chart{width:100%;align-self:center;text-align:center;color:var(--muted);font-size:11px}.modal-backdrop{position:fixed;inset:0;z-index:60;background:#020708d9;display:grid;place-items:center;padding:40px}.modal{width:min(760px,90vw);max-height:82vh;padding:28px;position:relative;overflow:auto}.modal>small{color:var(--cyan);font:9px var(--font-geist-mono);letter-spacing:.16em}.modal>h2{margin:7px 0 18px}.modal>p{color:#a0b0b4;line-height:1.7;font-size:12px}.modal-close{position:absolute;right:14px;top:12px;background:transparent;border:0;color:var(--muted);font-size:24px;cursor:pointer}.modal-list{display:grid;gap:8px}.modal-list article{background:#0a1517;border:1px solid var(--line);padding:13px;display:grid;grid-template-columns:repeat(3,1fr);gap:7px}.modal-list article strong,.modal-list article p{grid-column:1/-1}.modal-list article span{font-size:10px;color:#a7b9bd}.modal-list article p{font-size:10px;color:var(--muted);margin:3px 0}.settings{display:grid;grid-template-columns:1fr 1fr;gap:12px}.settings label{display:grid;gap:7px;color:var(--muted);font-size:10px}.settings select{background:#0b181a;border:1px solid var(--line);color:var(--ink);padding:10px}.hint{color:var(--amber)!important;font-size:10px!important} .tranches{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:5px;border-top:1px dashed var(--line);padding-top:8px}.tranches i{font-style:normal;font-size:8px;color:var(--cyan);background:#102427;padding:5px 7px} +.org-state{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin:12px 0}.org-state span{display:grid;gap:5px;background:#091517;border:1px solid var(--line);padding:10px;color:var(--muted);font-size:9px}.org-state b{color:var(--amber);font:13px var(--font-geist-mono)}.org-timeline{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.org-timeline article{border:1px solid var(--line);background:#0a1618;padding:12px;display:grid;gap:7px;margin:0}.org-timeline article.formal{border-top:2px solid var(--cyan)}.org-timeline article.informal{border-top:2px solid var(--amber)}.org-timeline article.control{border-top:2px solid var(--red)}.org-timeline header{display:flex;align-items:center;gap:7px}.org-timeline time{color:var(--cyan);font:9px var(--font-geist-mono)}.org-timeline header strong{font-size:9px}.org-timeline header em{margin-left:auto;font-style:normal;color:var(--muted);font-size:8px}.org-timeline h3{font-size:12px;margin:2px 0}.org-timeline p{font-size:9px;color:var(--muted);margin:0;line-height:1.6}.org-timeline>article>small{font-size:8px;color:#71878c}.org-timeline footer{height:auto;border:0;padding:7px 0 0;display:flex;justify-content:flex-start;gap:5px;flex-wrap:wrap}.org-timeline footer a{color:var(--cyan);font-size:8px;text-decoration:none;border-bottom:1px dotted var(--cyan)} .demo-banner{max-width:1696px;margin:12px auto 0;border:1px solid #4f6653;background:linear-gradient(100deg,#142723,#0d1c1d 70%);min-height:72px;padding:12px 18px;display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:10px}.demo-banner div{display:grid;gap:3px}.demo-banner small{color:var(--green);font:9px var(--font-geist-mono);letter-spacing:.14em}.demo-banner strong{font-size:14px}.demo-banner span{color:var(--muted);font-size:9px}.demo-banner button{border:1px solid #42675a;background:#112723;color:var(--green);padding:10px 15px;cursor:pointer}.demo-banner button:first-of-type{background:var(--green);color:#07130f;font-weight:700}.modal-wide{width:min(1160px,94vw);max-height:90vh}.demo-intro{max-width:720px;margin:auto}.demo-intro ul{padding-left:20px;color:#9eb0b5;font-size:11px;line-height:1.8}.error-text{color:#ff9f97!important}.demo-replay{display:grid;gap:15px}.replay-proof{display:flex;align-items:center;gap:8px;flex-wrap:wrap;border:1px solid #31554b;background:#0b1b18;padding:10px}.replay-proof span{color:var(--green);font-weight:700;font-size:10px}.replay-proof code{font-size:9px;color:#9fc0b4;background:#071310;padding:5px 7px}.replay-proof b{margin-left:auto;color:var(--muted);font-size:9px}.replay-steps{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(6,1fr);gap:5px}.replay-steps button{width:100%;height:58px;border:1px solid var(--line);background:#0a1618;color:#71868b;display:flex;align-items:center;text-align:left;gap:7px;padding:8px;cursor:pointer}.replay-steps i{width:24px;height:24px;border:1px solid #34494e;border-radius:50%;display:grid;place-items:center;font-style:normal;font-size:9px}.replay-steps span{font-size:9px}.replay-steps small{display:block;margin-top:3px;color:#52676c}.replay-steps .active button{border-color:var(--cyan);color:var(--cyan);background:#11272a}.replay-steps .done button{border-color:#31544a;color:var(--green)}.replay-stage{min-height:440px;background:#091416;border:1px solid var(--line);padding:18px;overflow:auto}.replay-stage>header{border-bottom:1px solid var(--line);margin-bottom:14px}.replay-stage>header small{font:9px var(--font-geist-mono);color:var(--cyan)}.replay-stage>header h3{margin:5px 0 13px}.replay-grid,.fund-grid,.comparison-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}.replay-grid article,.fund-grid article,.comparison-cards article{border:1px solid var(--line);background:#0d1c1f;padding:14px}.replay-grid strong,.fund-grid strong,.comparison-cards strong{display:block;color:var(--amber);font-size:14px}.replay-grid p,.fund-grid p{color:var(--muted);font-size:10px;line-height:1.7}.evidence-stack{display:grid;gap:8px}.evidence-stack p{margin:0;border-left:2px solid var(--cyan);background:#0d1b1e;padding:12px;font-size:11px;line-height:1.7;color:#9eafb3}.offer-chain{display:grid;grid-template-columns:1fr 24px 1fr 24px 1fr;gap:7px;align-items:center}.offer-chain>article{border:1px solid var(--line);background:#0d1d20;padding:12px;display:grid;gap:6px}.offer-chain>article small{color:var(--cyan)}.offer-chain>article strong{font-size:12px}.offer-chain>article span{font-size:9px;color:var(--muted)}.offer-chain>b{text-align:center;color:var(--amber)}.private-audit{margin-top:14px}.private-audit h4,.sensitivity-table h4{font-size:11px;color:var(--amber)}.private-audit details{border:1px solid var(--line);background:#0b181a;margin:6px 0;padding:10px}.private-audit summary{cursor:pointer;color:#a8babd;font-size:10px}.private-audit p{font-size:9px;color:var(--muted);overflow-wrap:anywhere}.fund-grid article small{display:block;color:var(--cyan);font-size:8px;margin-bottom:7px}.fund-grid article strong{color:var(--ink);font-size:11px}.promise-table{margin-top:12px;border:1px solid var(--line)}.promise-table>div{display:grid;grid-template-columns:1fr 100px 1.4fr 80px;gap:8px;padding:8px 11px;border-bottom:1px solid var(--line);align-items:center;font-size:9px}.promise-table b{color:var(--amber)}.promise-table em{font-style:normal;text-transform:uppercase;color:var(--green)}.event-chain{display:grid;grid-template-columns:repeat(2,1fr);gap:7px}.event-chain article{display:grid;grid-template-columns:35px 1fr;gap:9px;border:1px solid var(--line);background:#0c191b;padding:10px}.event-chain time{color:var(--cyan);font:9px var(--font-geist-mono)}.event-chain strong{font-size:10px}.event-chain p{font-size:9px;color:var(--muted);margin:5px 0 0}.comparison-cards article small{display:block;color:var(--muted);font-size:9px}.comparison-cards article strong{font:19px var(--font-geist-mono);margin:8px 0}.comparison-cards article span{font-size:10px}.comparison-cards .warning{border-color:#6d5932}.boundary{border-left:2px solid var(--amber);background:#1c1911;color:#b8aa8e;padding:11px;font-size:10px;line-height:1.7}.sensitivity-table{margin-top:14px}.table-row{display:grid;grid-template-columns:repeat(5,1fr);border-bottom:1px solid var(--line);padding:7px 10px;font-size:9px}.table-row.header{color:var(--muted);background:#102024}.table-row b{color:var(--amber)}.replay-actions{display:flex;gap:7px;height:auto;padding:0;border:0;max-width:none}.replay-actions button,.replay-actions a{border:1px solid #355056;background:#0c191b;color:#9fb2b6;padding:9px 12px;cursor:pointer;font-size:9px}.replay-actions .primary{margin-left:auto;background:var(--cyan);border-color:var(--cyan);color:#071315;font-weight:700}.report-center{display:grid;gap:12px}.report-center>small{color:var(--cyan);font:9px var(--font-geist-mono);letter-spacing:.16em}.report-center>h2{margin:0}.report-toolbar{display:grid;grid-template-columns:1fr auto auto;gap:10px;align-items:end}.report-toolbar label{display:grid;gap:5px;color:var(--muted);font-size:9px}.report-toolbar select{background:#091517;border:1px solid var(--line);color:var(--ink);padding:9px}.report-toolbar div{display:flex}.report-toolbar button,.report-toolbar a{border:1px solid var(--line);background:#0b181a;color:var(--muted);padding:9px 11px;font-size:9px;cursor:pointer}.report-toolbar button.active{border-color:var(--cyan);color:var(--cyan)}.report-meta{display:flex;gap:10px;color:var(--muted);font-size:9px}.report-meta span{border:1px solid var(--line);padding:5px 8px}.summary-chart{display:grid;gap:6px}.summary-chart button{display:grid;grid-template-columns:150px 1fr 105px 120px;align-items:center;gap:10px;border:1px solid var(--line);background:#0b181a;color:var(--ink);padding:9px;text-align:left;cursor:pointer}.summary-chart button>span{font-size:10px}.summary-chart i{height:8px;background:#172b2f}.summary-chart i b{display:block;height:100%;background:linear-gradient(90deg,var(--cyan),var(--green))}.summary-chart strong{font:10px var(--font-geist-mono);color:var(--amber)}.summary-chart small{font-size:8px;color:var(--muted)}.seed-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;max-height:180px;overflow:auto}.seed-grid button{display:grid;text-align:left;gap:3px;border:1px solid var(--line);background:#0b181a;color:var(--ink);padding:8px;cursor:pointer}.seed-grid button.active{border-color:var(--cyan);background:#11272a}.seed-grid button.failed{border-color:#693b37}.seed-grid strong{font-size:10px}.seed-grid span,.seed-grid small{font-size:8px;color:var(--muted)}.run-detail{border:1px solid #38565b;background:#0c1a1d;padding:12px}.run-detail header{display:flex;justify-content:space-between;align-items:center}.run-detail h3{margin:4px 0}.run-detail header span{font-size:9px;color:var(--green)}.run-detail>div{display:grid;grid-template-columns:repeat(6,1fr);gap:6px}.run-detail p{display:grid;color:var(--muted);font-size:8px;background:#091416;padding:7px}.run-detail p strong{color:var(--ink);font-size:11px;margin-top:4px}.run-detail>button{border:1px solid var(--cyan);background:#11282a;color:var(--cyan);padding:9px 12px;cursor:pointer}.failure-panel{border-top:1px solid var(--line);padding-top:10px}.failure-panel h3{font-size:11px}.failure-panel article{display:grid;grid-template-columns:180px 100px 1fr;gap:8px;font-size:9px;color:var(--muted);padding:7px;background:#191313;border-bottom:1px solid #472a27}.failure-panel article p{margin:0}.failure-panel>p{color:var(--muted);font-size:9px} @media(max-width:1250px){.dashboard-grid{grid-template-columns:270px 1fr 310px}.metric-card strong{font-size:20px}} +.org-plan-grid,.learning-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin:8px 0 16px}.org-plan-grid>article,.learning-grid>article,.opportunity-strip>article{border:1px solid var(--line);background:#091517;padding:11px;display:grid;gap:6px}.org-plan-grid header{display:flex;justify-content:space-between}.org-plan-grid header em{font-style:normal;color:var(--green);font-size:8px}.org-plan-grid p,.learning-grid p,.opportunity-strip p{font-size:9px;color:var(--muted);margin:2px 0;line-height:1.5}.org-plan-grid small,.learning-grid small,.opportunity-strip small,.opportunity-strip span{font-size:8px;color:#71878c}.org-plan-grid summary{cursor:pointer;color:var(--cyan);font-size:9px;margin:6px 0}.plan-nodes{display:grid;gap:4px}.plan-nodes p{border-left:2px solid #43585d;padding:5px 7px;background:#0c1b1d}.plan-nodes p.completed{border-color:var(--green)}.plan-nodes p.deviated{border-color:var(--red)}.plan-nodes p.pending{border-color:var(--amber)}.opportunity-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin:8px 0 16px}.opportunity-strip article.active{border-top:2px solid var(--amber)}.learning-grid strong,.opportunity-strip strong{font-size:10px}.learning-grid span{font-size:8px;color:var(--amber)}.compact-list{margin-bottom:16px}.compact-list article{grid-template-columns:1fr 1fr}.org-timeline article.initiative{border-top:2px solid #9b8ad7}.org-timeline article.procedure{border-top:2px solid var(--green)} diff --git a/apps/web/app/lib/api.ts b/apps/web/app/lib/api.ts index ffc99ba..ca75922 100644 --- a/apps/web/app/lib/api.ts +++ b/apps/web/app/lib/api.ts @@ -1,4 +1,5 @@ -export const API_BASE = process.env.NEXT_PUBLIC_INSIDEGOV_API_URL ?? "http://localhost:8000"; +export const API_BASE = + process.env.NEXT_PUBLIC_INSIDEGOV_API_URL ?? "http://localhost:8000"; export type Offer = { subsidy: number; @@ -41,59 +42,232 @@ export type World = { quarter: number; phase: string; cities: Record; - firms: Record; - investment_funds: Record; + firms: Record< + string, + { id: string; name: string; operating: boolean; location: string | null } + >; + investment_funds: Record< + string, + { + id: string; + name: string; + city_id: string; + source_level: string; + available_capital: number; + committed_capital: number; + risk_tolerance: number; + due_diligence_threshold: number; + } + >; promises: Array<{ - id: string; item: string; amount: number; due_quarter: number; condition: string; - status: string; paid_amount: number; delayed_quarters: number; + id: string; + item: string; + amount: number; + due_quarter: number; + condition: string; + status: string; + paid_amount: number; + delayed_quarters: number; funding_source_id: string | null; }>; - events: Array<{ quarter: number; kind: string; title: string; detail: string; severity: string }>; + events: Array<{ + quarter: number; + kind: string; + title: string; + detail: string; + severity: string; + }>; traces: Array<{ - id: string; actor_id: string; action: string; evidence: string[]; - constraints: string[]; outcome: string; expected_effects: Record; + id: string; + actor_id: string; + action: string; + evidence: string[]; + constraints: string[]; + outcome: string; + expected_effects: Record; }>; action_audits: AgentActionAudit[]; + organization_actions: OrganizationAction[]; + organization_plans: OrganizationPlan[]; + open_action_proposals: OpenActionProposal[]; + opportunity_windows: OpportunityWindow[]; + organization_learning: Record; + organization_processes: Record< + string, + { + city_id: string; + agenda_priority: number; + procedural_completeness: number; + coalition_support: number; + legitimacy: number; + risk_posture: string; + finance_preconsulted: boolean; + legal_reviewed: boolean; + collective_deliberated: boolean; + pilot_authorized: boolean; + relationships: Record; + action_sequence: string[]; + formal_status: string; + return_count: number; + last_transition: string; + last_transition_quarter: number; + paused_reason: string | null; + attention_budget: number; + } + >; history: Metric[]; negotiations: Array<{ - id: string; quarter: number; city_id: string; proposal_cost: number; - finance_limit: number; finance_approved: boolean; concerns: string[]; - resolution: string; final_cost: number; policy_mode: string; - proposer_id: string; reviewer_id: string; coordinator_id: string; + id: string; + quarter: number; + city_id: string; + proposal_cost: number; + finance_limit: number; + finance_approved: boolean; + concerns: string[]; + resolution: string; + final_cost: number; + policy_mode: string; + proposer_id: string; + reviewer_id: string; + coordinator_id: string; proposal_tools: Record; finance_tool_limits: Record; final_tools: Record; - payment_schedule: Array<{ item: string; amount: number; due_offset: number; condition: string; funding_source_id?: string | null }>; - turns: Array<{ actor_id: string; act: string; summary: string; amount?: number; approved?: boolean }>; + payment_schedule: Array<{ + item: string; + amount: number; + due_offset: number; + condition: string; + funding_source_id?: string | null; + }>; + turns: Array<{ + actor_id: string; + act: string; + summary: string; + amount?: number; + approved?: boolean; + }>; }>; external_negotiations: Array<{ - id: string; quarter: number; city_id: string; firm_id: string; protocol: string; - stated_need: string; government_questions: string[]; + id: string; + quarter: number; + city_id: string; + firm_id: string; + protocol: string; + stated_need: string; + government_questions: string[]; disclosed_components: Record; - belief_before: Record; belief_after: Record; - belief_confidence: number; internal_negotiation_id: string; + belief_before: Record; + belief_after: Record; + belief_confidence: number; + internal_negotiation_id: string; government_offer: Record; enterprise_response: "accept" | "counter" | "terminate"; - counter_terms: Record; enterprise_rationale: string; - utility: number; minimum_utility: number; outcome: string; + counter_terms: Record; + enterprise_rationale: string; + utility: number; + minimum_utility: number; + outcome: string; }>; selected_city_id: string | null; + recruitment_status: string; + negotiation_round_limit: number; parent_id: string | null; policy_mode: string; + process_mode: "formal" | "informal" | "hybrid"; model_name: string | null; }; +export type OrganizationAction = { + id: string; + quarter: number; + city_id: string; + actor_id: string; + actor_role: string; + action_id: string; + action_name: string; + arena: "formal" | "informal" | "control" | "initiative" | "procedure"; + process_mode: string; + candidates: string[]; + observations: Record; + rationale: string; + effects: Record; + required: boolean; + authorized: boolean; + blocked_reason: string | null; + evidence_ids: string[]; + selection_provider: string; + selection_rationale: string; + fallback: boolean; + stage: string; + sequence: number; + decision: string; + target_actor_id: string | null; + urgency: number; + reflection: string; + plan_id: string | null; + plan_node_id: string | null; + deviation_reason: string | null; + open_action_proposal_id: string | null; +}; + +export type OrganizationPlan = { + id: string; + city_id: string; + actor_id: string; + created_quarter: number; + horizon_quarter: number; + objective: string; + alternatives: Array<{ + id: string; name: string; approach: string; benefits: string[]; risks: string[]; score: number; + }>; + selected_strategy: string; + nodes: Array<{ + id: string; title: string; action_id: string; earliest_quarter: number; latest_quarter: number; + preconditions: string[]; on_success: string | null; on_failure: string | null; + expected_effects: Record; status: string; executed_quarter: number | null; + actual_action_id: string | null; deviation_reason: string | null; + }>; + assumptions: string[]; + rationale: string; + status: string; + provider: string; + fallback: boolean; + review_history: Array>; +}; + +export type OpenActionProposal = { + id: string; quarter: number; city_id: string; actor_id: string; title: string; + mechanism: string; target_actor_id: string | null; + requested_effects: Record; resource_request: Record; + rationale: string; status: string; validation_reason: string; + executed_effects: Record; provider: string; fallback: boolean; +}; + +export type OpportunityWindow = { + id: string; city_id: string | null; kind: string; title: string; + start_quarter: number; end_quarter: number; magnitude: number; + source: string; trigger: string; effects: Record; + observed_by: string[]; status: string; applied: boolean; +}; + +export type OrganizationLearning = { + agent_id: string; trust_by_actor: Record; + firm_type_beliefs: Record; action_attempts: Record; + action_successes: Record; strategy_preferences: Record; + routines: Record; veto_count: number; successful_coordination_count: number; + leadership_generation: number; inherited_memory_ratio: number; + lessons: string[]; transferable_lessons: string[]; +}; + export type AgentActionAudit = { id: string; quarter: number; agent_id: string; action_type: string; observation: Record; - private_context_used: { redacted?: boolean; fields_used?: string[] } | Record; + private_context_used: + { redacted?: boolean; fields_used?: string[] } | Record; retrieved_memories: string[]; llm_suggestion: Record; rule_adjustment: Record; @@ -111,52 +285,105 @@ export type Capability = { llm_available: boolean; models: string[]; default_model: string; + process_modes?: string[]; }; export type InterventionPlan = { - id: string; source_text: string; effective_quarter: number; status: string; - assumptions: string[]; promise_priority: string | null; - changes: Array<{ target: string; operation: string; value: number; description: string }>; + id: string; + source_text: string; + effective_quarter: number; + status: string; + assumptions: string[]; + promise_priority: string | null; + changes: Array<{ + target: string; + operation: string; + value: number; + description: string; + }>; }; export type Interview = { - quarter: number; agent_id: string; question: string; answer: string; + quarter: number; + agent_id: string; + question: string; + answer: string; evidence_audit_ids: string[]; knowledge_labels: { - known_at_the_time: string[]; private_fields_used: string[]; - unknown_at_the_time: string[]; hindsight: string[]; hypothetical: boolean; + known_at_the_time: string[]; + private_fields_used: string[]; + unknown_at_the_time: string[]; + hindsight: string[]; + hypothetical: boolean; }; }; export type CandidateWorld = { - id: string; filename: string; content_excerpt: string; entities: string[]; - relations: Array<{ source: string; target: string; relation: string; evidence: string }>; - missing_fields: string[]; status: string; + id: string; + filename: string; + content_excerpt: string; + entities: string[]; + relations: Array<{ + source: string; + target: string; + relation: string; + evidence: string; + }>; + missing_fields: string[]; + status: string; parameters: Array<{ - id: string; target: string; evidence: string; suggested_value: number; - final_value: number | null; confidence: number; provenance: string; status: string; + id: string; + target: string; + evidence: string; + suggested_value: number; + final_value: number | null; + confidence: number; + provenance: string; + status: string; }>; }; export type DemoBundle = { - schema_version: string; generated_at: string; source: string; seed: number; + schema_version: string; + generated_at: string; + source: string; + seed: number; common_ancestor_quarter: number; - intervention: { quarter: number; target: string; operation: string; value: number }; - baseline: World; branch: World; - comparison: { baseline_world_id: string; branch_world_id: string; delta: Record }; + intervention: { + quarter: number; + target: string; + operation: string; + value: number; + }; + baseline: World; + branch: World; + comparison: { + baseline_world_id: string; + branch_world_id: string; + delta: Record; + }; replay: { - selected_city_id: string; selected_city_name: string; + selected_city_id: string; + selected_city_name: string; external_negotiation: World["external_negotiations"][number]; internal_negotiation: World["negotiations"][number]; synthetic_private_audit: Array<{ - audit_id: string; agent_id: string; private_context_used: Record; - observation: Record; llm_suggestion: Record; - rule_adjustment: Record; executed_action: Record; - rationale: string; reflection: string; provider: string; fallback: boolean; + audit_id: string; + agent_id: string; + private_context_used: Record; + observation: Record; + llm_suggestion: Record; + rule_adjustment: Record; + executed_action: Record; + rationale: string; + reflection: string; + provider: string; + fallback: boolean; }>; funding_partners: Array; promises: World["promises"]; - baseline_outcome: Metric; branch_outcome: Metric; + baseline_outcome: Metric; + branch_outcome: Metric; causal_events: World["events"]; steps: Array<{ id: string; title: string; quarter: number }>; }; @@ -164,46 +391,85 @@ export type DemoBundle = { }; export type HefeiSensitivity = { - case: string; seed: number; quarters: number; historical_equity_investment: number; - budget_shares: number[]; interpretation_boundary: string; + case: string; + seed: number; + quarters: number; + historical_equity_investment: number; + budget_shares: number[]; + interpretation_boundary: string; runs: Array<{ - available_budget_share: number; joint_investment: boolean; selected_city: string | null; - municipal_equity: number; external_equity: number; total_equity_support: number; - historical_equity_gap: number; fulfilled_promises: number; cluster_size: number; - total_employment: number; average_credibility: number; + available_budget_share: number; + joint_investment: boolean; + selected_city: string | null; + municipal_equity: number; + external_equity: number; + total_equity_support: number; + historical_equity_gap: number; + fulfilled_promises: number; + cluster_size: number; + total_employment: number; + average_credibility: number; }>; }; export type ExperimentReportSummary = { - id: string; title: string; kind: string; source: string; generated_at: string | null; - strategy_runs: number; ablation_runs: number; failures: number; + id: string; + title: string; + kind: string; + source: string; + generated_at: string | null; + strategy_runs: number; + ablation_runs: number; + failures: number; drilldown_available: boolean; }; export type ExperimentRun = { - strategy: string; seed: number; successful: boolean; selected_city: string | null; - fulfilled_promises: number; total_employment: number; total_committed_expenditure: number; - average_credibility: number; cluster_size: number; utilization: number; - world_id: string; world_available: boolean; + strategy: string; + seed: number; + successful: boolean; + selected_city: string | null; + fulfilled_promises: number; + total_employment: number; + total_committed_expenditure: number; + average_credibility: number; + cluster_size: number; + utilization: number; + world_id: string; + world_available: boolean; }; export type ExperimentMetricSummary = { - id?: string; name?: string; strategy?: string; mechanism?: string; - attempted?: number; successful?: number; success_rate?: number; + id?: string; + name?: string; + strategy?: string; + mechanism?: string; + attempted?: number; + successful?: number; + success_rate?: number; metrics?: Record; - employment?: number; employment_variance?: number; cluster?: number; - fulfilled_promises?: number; seeds?: number; + employment?: number; + employment_variance?: number; + cluster?: number; + fulfilled_promises?: number; + seeds?: number; }; export type ExperimentReport = { - report_id: string; generated_at?: string; + report_id: string; + generated_at?: string; report_summary: Partial; configuration: Record; strategy_summary: ExperimentMetricSummary[]; ablation_summary: ExperimentMetricSummary[]; strategy_runs: ExperimentRun[]; ablation_runs: ExperimentRun[]; - failure_cases: Array<{ strategy: string; seed: number; stage: string; reason: string }>; + failure_cases: Array<{ + strategy: string; + seed: number; + stage: string; + reason: string; + }>; llm_quality?: Array>; }; @@ -212,66 +478,127 @@ async function request(path: string, init?: RequestInit): Promise { ...init, headers: { "Content-Type": "application/json", ...init?.headers }, }); - if (!response.ok) throw new Error(`${response.status} ${await response.text()}`); + if (!response.ok) + throw new Error(`${response.status} ${await response.text()}`); return response.json() as Promise; } export const api = { capabilities: () => request("/capabilities"), - listWorlds: () => request>>("/worlds"), + listWorlds: () => + request< + Array< + Pick + > + >("/worlds"), getWorld: (id: string) => request(`/worlds/${id}`), - createWorld: (seed: number, policyMode: string, modelName: string) => request("/worlds", { - method: "POST", - body: JSON.stringify({ seed, policy_mode: policyMode, model_name: policyMode === "llm" ? modelName : null }), - }), - step: (id: string, quarters = 1) => request(`/worlds/${id}/step`, { - method: "POST", body: JSON.stringify({ quarters }), - }), - branch: (id: string) => request(`/worlds/${id}/branches`, { method: "POST" }), - branchFromHistory: (id: string, quarter: number) => request(`/worlds/${id}/branches/from-history`, { - method: "POST", body: JSON.stringify({ quarter }), - }), - createPair: (id: string, quarter: number) => request<{ baseline: World; branch: World; quarter: number }>(`/worlds/${id}/counterfactual-pairs`, { - method: "POST", body: JSON.stringify({ quarter }), - }), - syncPair: (baselineId: string, branchId: string, quarters: number) => request<{ baseline: World; branch: World; comparison: { delta: Record } }>("/experiments/sync-worlds", { - method: "POST", body: JSON.stringify({ baseline_world_id: baselineId, branch_world_id: branchId, quarters }), - }), - intervene: (id: string, kind: string, target: string, value: number) => request<{ accepted: boolean }>(`/worlds/${id}/interventions`, { - method: "POST", body: JSON.stringify({ kind, target, value }), - }), + createWorld: ( + seed: number, + policyMode: string, + modelName: string, + processMode = "hybrid", + ) => + request("/worlds", { + method: "POST", + body: JSON.stringify({ + seed, + policy_mode: policyMode, + model_name: policyMode === "llm" ? modelName : null, + process_mode: processMode, + }), + }), + step: (id: string, quarters = 1) => + request(`/worlds/${id}/step`, { + method: "POST", + body: JSON.stringify({ quarters }), + }), + branch: (id: string) => + request(`/worlds/${id}/branches`, { method: "POST" }), + branchFromHistory: (id: string, quarter: number) => + request(`/worlds/${id}/branches/from-history`, { + method: "POST", + body: JSON.stringify({ quarter }), + }), + createPair: (id: string, quarter: number) => + request<{ baseline: World; branch: World; quarter: number }>( + `/worlds/${id}/counterfactual-pairs`, + { + method: "POST", + body: JSON.stringify({ quarter }), + }, + ), + syncPair: (baselineId: string, branchId: string, quarters: number) => + request<{ + baseline: World; + branch: World; + comparison: { delta: Record }; + }>("/experiments/sync-worlds", { + method: "POST", + body: JSON.stringify({ + baseline_world_id: baselineId, + branch_world_id: branchId, + quarters, + }), + }), + intervene: (id: string, kind: string, target: string, value: number) => + request<{ accepted: boolean }>(`/worlds/${id}/interventions`, { + method: "POST", + body: JSON.stringify({ kind, target, value }), + }), audits: (id: string, quarter?: number, agentId?: string) => { const params = new URLSearchParams(); if (quarter != null) params.set("quarter", String(quarter)); if (agentId) params.set("agent_id", agentId); - return request(`/worlds/${id}/audits?${params.toString()}`); + return request( + `/worlds/${id}/audits?${params.toString()}`, + ); }, - draftIntervention: (id: string, text: string) => request(`/worlds/${id}/intervention-plans`, { - method: "POST", body: JSON.stringify({ text }), - }), - confirmIntervention: (id: string, planId: string) => request<{ accepted: boolean; plan: InterventionPlan }>(`/worlds/${id}/intervention-plans/confirm`, { - method: "POST", body: JSON.stringify({ plan_id: planId }), - }), - interview: (id: string, agentId: string, quarter: number, question: string) => request(`/worlds/${id}/interviews`, { - method: "POST", body: JSON.stringify({ agent_id: agentId, quarter, question }), - }), - createCandidate: (filename: string, content: string) => request("/materials/candidates", { - method: "POST", body: JSON.stringify({ filename, content }), - }), - confirmCandidate: (id: string, candidate: CandidateWorld) => request(`/materials/candidates/${id}/confirm`, { - method: "POST", body: JSON.stringify({ - parameters: candidate.parameters.map((item) => ({ - parameter_id: item.id, final_value: item.final_value ?? item.suggested_value, - provenance: item.provenance, - })), - seed: 42, + draftIntervention: (id: string, text: string) => + request(`/worlds/${id}/intervention-plans`, { + method: "POST", + body: JSON.stringify({ text }), + }), + confirmIntervention: (id: string, planId: string) => + request<{ accepted: boolean; plan: InterventionPlan }>( + `/worlds/${id}/intervention-plans/confirm`, + { + method: "POST", + body: JSON.stringify({ plan_id: planId }), + }, + ), + interview: (id: string, agentId: string, quarter: number, question: string) => + request(`/worlds/${id}/interviews`, { + method: "POST", + body: JSON.stringify({ agent_id: agentId, quarter, question }), + }), + createCandidate: (filename: string, content: string) => + request("/materials/candidates", { + method: "POST", + body: JSON.stringify({ filename, content }), + }), + confirmCandidate: (id: string, candidate: CandidateWorld) => + request(`/materials/candidates/${id}/confirm`, { + method: "POST", + body: JSON.stringify({ + parameters: candidate.parameters.map((item) => ({ + parameter_id: item.id, + final_value: item.final_value ?? item.suggested_value, + provenance: item.provenance, + })), + seed: 42, + }), + }), + createDemo: (seed = 42, fiscalMultiplier = 0.5) => + request("/demos/hefei-nio", { + method: "POST", + body: JSON.stringify({ seed, fiscal_multiplier: fiscalMultiplier }), }), - }), - createDemo: (seed = 42, fiscalMultiplier = 0.5) => request("/demos/hefei-nio", { - method: "POST", body: JSON.stringify({ seed, fiscal_multiplier: fiscalMultiplier }), - }), - hefeiSensitivity: (seed = 42) => request(`/cases/hefei-nio/sensitivity?seed=${seed}`), - listExperimentReports: () => request("/experiment-reports"), - getExperimentReport: (id: string) => request(`/experiment-reports/${id}`), - getExperimentWorld: (reportId: string, worldId: string) => request(`/experiment-reports/${reportId}/worlds/${worldId}`), + hefeiSensitivity: (seed = 42) => + request(`/cases/hefei-nio/sensitivity?seed=${seed}`), + listExperimentReports: () => + request("/experiment-reports"), + getExperimentReport: (id: string) => + request(`/experiment-reports/${id}`), + getExperimentWorld: (reportId: string, worldId: string) => + request(`/experiment-reports/${reportId}/worlds/${worldId}`), }; diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 287de13..cf4d16d 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -3,39 +3,90 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { DemoReplay } from "./components/DemoReplay"; import { ReportCenter } from "./components/ReportCenter"; -import { API_BASE, api, type AgentActionAudit, type CandidateWorld, type Capability, type InterventionPlan, type Interview, type Metric, type World } from "./lib/api"; +import { + API_BASE, + api, + type AgentActionAudit, + type CandidateWorld, + type Capability, + type InterventionPlan, + type Interview, + type Metric, + type OrganizationAction, + type World, +} from "./lib/api"; const phaseNames: Record = { - recruitment: "招商竞争", delivery: "承诺履约", industrialization: "产业演化", + recruitment: "招商竞争", + delivery: "承诺履约", + industrialization: "产业演化", }; const tones = ["amber", "cyan", "violet"]; export default function Home() { const [world, setWorld] = useState(null); const [capability, setCapability] = useState(null); - const [worldList, setWorldList] = useState>([]); + const [worldList, setWorldList] = useState< + Array<{ + id: string; + name: string; + quarter: number; + parent_id: string | null; + policy_mode: string; + }> + >([]); const [busy, setBusy] = useState(false); const [playing, setPlaying] = useState(false); const [error, setError] = useState(""); const [policyMode, setPolicyMode] = useState("deterministic"); const [model, setModel] = useState("deepseek-v4-flash"); - const [detail, setDetail] = useState<"demo" | "reports" | "negotiations" | "unified" | "trace" | "audit" | "counterfactual" | "interview" | "material" | "about" | null>(null); + const [processMode, setProcessMode] = useState("hybrid"); + const [detail, setDetail] = useState< + | "demo" + | "reports" + | "negotiations" + | "unified" + | "organization" + | "trace" + | "audit" + | "counterfactual" + | "interview" + | "material" + | "about" + | null + >(null); const remember = useCallback((next: World) => { setWorld(next); window.localStorage.setItem("insidegov_world_id", next.id); }, []); - const refreshList = useCallback(async () => setWorldList(await api.listWorlds()), []); + const refreshList = useCallback( + async () => setWorldList(await api.listWorlds()), + [], + ); - const create = useCallback(async (mode = policyMode, selectedModel = model) => { - setBusy(true); setError(""); - try { - remember(await api.createWorld(42, mode, selectedModel)); - await refreshList(); - } catch (caught) { setError(`无法连接模拟 API:${String(caught)}`); } - finally { setBusy(false); } - }, [model, policyMode, refreshList, remember]); + const create = useCallback( + async ( + mode = policyMode, + selectedModel = model, + selectedProcess = processMode, + ) => { + setBusy(true); + setError(""); + try { + remember( + await api.createWorld(42, mode, selectedModel, selectedProcess), + ); + await refreshList(); + } catch (caught) { + setError(`无法连接模拟 API:${String(caught)}`); + } finally { + setBusy(false); + } + }, + [model, policyMode, processMode, refreshList, remember], + ); useEffect(() => { let alive = true; @@ -43,152 +94,1476 @@ export default function Home() { try { const caps = await api.capabilities(); if (!alive) return; - setCapability(caps); setModel(caps.default_model); + setCapability(caps); + setModel(caps.default_model); const id = window.localStorage.getItem("insidegov_world_id"); if (id) { - try { remember(await api.getWorld(id)); } - catch { await create("deterministic", caps.default_model); } + try { + remember(await api.getWorld(id)); + } catch { + await create("deterministic", caps.default_model); + } } else await create("deterministic", caps.default_model); await refreshList(); - } catch (caught) { if (alive) setError(`后端未就绪:${String(caught)}`); } + } catch (caught) { + if (alive) setError(`后端未就绪:${String(caught)}`); + } })(); - return () => { alive = false; }; + return () => { + alive = false; + }; }, [create, refreshList, remember]); async function step() { if (!world || busy) return; - setBusy(true); setError(""); + setBusy(true); + setError(""); try { const next = await api.step(world.id); remember(next); if (next.quarter >= 16) setPlaying(false); + } catch (caught) { + setError(String(caught)); + } finally { + setBusy(false); } - catch (caught) { setError(String(caught)); } - finally { setBusy(false); } } useEffect(() => { if (!playing || !world || world.quarter >= 16) return; const timer = window.setTimeout(() => void step(), 700); return () => window.clearTimeout(timer); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [playing, world?.quarter]); const latest = world?.history.at(-1); const cities = world ? Object.values(world.cities) : []; - const selected = world?.selected_city_id ? world.cities[world.selected_city_id] : null; + const selected = world?.selected_city_id + ? world.cities[world.selected_city_id] + : null; const phase = world ? phaseNames[world.phase] : "准备中"; - const operating = world ? Object.values(world.firms).filter((firm) => firm.operating).length : 0; + const operating = world + ? Object.values(world.firms).filter((firm) => firm.operating).length + : 0; const history = useMemo(() => world?.history ?? [], [world]); - if (!world) return
内

InsideGov

{error || "正在恢复世界…"}

{error && }
; + if (!world) + return ( +
+ 内 +

InsideGov

+

{error || "正在恢复世界…"}

+ {error && } +
+ ); return (
-
内
InsideGov政企互动推演场
-
{world.name}{world.id}
- +
+ 内 +
+ InsideGov + 政企互动推演场 +
+
+
+ + {world.name} + {world.id} +
+
{error &&
{error}
}
-
COMPETITION DEMO · RULE-GENERATED从真实案例到反事实结果,六步验证博弈不是预写剧情合肥—蔚来公开参数卡 · 联合产业基金 · Agent 私有信息 · 跨期履约
- +
+ COMPETITION DEMO · RULE-GENERATED + 从真实案例到反事实结果,六步验证博弈不是预写剧情 + + 合肥—蔚来公开参数卡 · 联合产业基金 · Agent 私有信息 · 跨期履约 + +
+
-
模拟时钟2030 Q{((world.quarter || 1) - 1) % 4 + 1}第 {world.quarter} / 16 季度
-
{["招商竞争", "承诺履约", "产业演化"].map((item, index) =>
{index + 1}{item}
)}
-
+
+ 模拟时钟 + 2030 Q{(((world.quarter || 1) - 1) % 4) + 1} + 第 {world.quarter} / 16 季度 +
+
+ {["招商竞争", "承诺履约", "产业演化"].map((item, index) => ( +
+ {index + 1} + {item} +
+ ))} +
+
+ + + +
- - - - + + + +
-
WORLD STATE

产业世界态势

● 政府◆ 龙头· 链企
-
-
{cities[0]?.name[0]}{cities[0]?.name}{world.selected_city_id === cities[0]?.id ? "项目落地" : "竞争城市"}
-
{cities[2]?.name[0]}{cities[2]?.name}{world.selected_city_id === cities[2]?.id ? "项目落地" : "竞争城市"}
-
◆星澜显示{selected ? `${selected.name} · ${world.firms.firm_nova.operating ? "已投产" : "建设中"}` : "正在选址"}{Array.from({ length: Math.min(10, Math.max(0, operating - 1)) }, (_, i) => )}
-
{phase}{world.policy_mode === "llm" ? `${world.model_name} 负责认知,规则引擎负责状态更新` : "确定性异质认知层 · 可复现基线"}
+
+
+
+ WORLD STATE +

产业世界态势

+
+
+ ● 政府 + ◆ 龙头 + · 链企 +
+
+
+
+
+
+
+ {cities[0]?.name[0]} + {cities[0]?.name} + + {world.selected_city_id === cities[0]?.id + ? "项目落地" + : "竞争城市"} + +
+
+ {cities[2]?.name[0]} + {cities[2]?.name} + + {world.selected_city_id === cities[2]?.id + ? "项目落地" + : "竞争城市"} + +
+
+
+ + + +
+ ◆ + 星澜显示 + + {selected + ? `${selected.name} · ${world.firms.firm_nova.operating ? "已投产" : "建设中"}` + : "正在选址"} + + {Array.from( + { length: Math.min(10, Math.max(0, operating - 1)) }, + (_, i) => ( + + ), + )} +
+
+ {phase} + + {world.policy_mode === "llm" + ? `${world.model_name} 负责认知,规则引擎负责状态更新` + : "确定性异质认知层 · 可复现基线"} + +
-
EVOLUTION

产能与需求演化

安全阈值 68%
{history.length ? history.map((point) => ) :
推进世界后生成季度数据
}
市场需求形成产能数据源:世界状态日志
+
+
+
+ EVOLUTION +

产能与需求演化

+
+ 安全阈值 68% +
+
+ {history.length ? ( + history.map((point) => ( + + )) + ) : ( +
推进世界后生成季度数据
+ )} +
+
+ + + 市场需求 + + + + 形成产能 + + 数据源:世界状态日志 +
+
-
DECISION TRACE

可追溯决策链

{world.traces.slice(-4).reverse().map((trace, index) =>
0{index + 1}
{trace.actor_id}

{trace.action}

调用证据

{trace.evidence.join(" · ")}

内部权衡

{trace.constraints.join(" · ")}

{Object.values(trace.expected_effects)[0]?.toFixed?.(1) ?? "—"}
)}
+
+
+
+ DECISION TRACE +

可追溯决策链

+
+ +
+
+ {world.traces + .slice(-4) + .reverse() + .map((trace, index) => ( +
+ 0{index + 1} +
+ {trace.actor_id} +

{trace.action}

+
+
+ 调用证据 +

{trace.evidence.join(" · ")}

+
+
+ 内部权衡 +

{trace.constraints.join(" · ")}

+
+ + {Object.values(trace.expected_effects)[0]?.toFixed?.(1) ?? + "—"} + + +
+ ))} +
+
-
InsideGov v0.5 · 政策实验与机制探索,不构成现实政策预测{world.policy_mode === "llm" ? world.model_name : "确定性基线"} · Seed {world.seed} · 自动持久化
+
+ InsideGov v0.6 · 组织行动与政策实验,不构成现实政策预测 + + {world.process_mode} 过程 ·{" "} + {world.policy_mode === "llm" ? world.model_name : "确定性基线"} · Seed{" "} + {world.seed} + +
- {detail &&
{detail === "demo" ? : detail === "reports" ? : detail === "about" ? : detail === "negotiations" ? : detail === "unified" ? : detail === "audit" ? : detail === "counterfactual" ? : detail === "interview" ? : detail === "material" ? : }
} + {detail && ( +
+
+ + {detail === "demo" ? ( + + ) : detail === "reports" ? ( + + ) : detail === "about" ? ( + + ) : detail === "negotiations" ? ( + + ) : detail === "unified" ? ( + + ) : detail === "organization" ? ( + + ) : detail === "audit" ? ( + + ) : detail === "counterfactual" ? ( + + ) : detail === "interview" ? ( + + ) : detail === "material" ? ( + + ) : ( + + )} +
+
+ )}
); } -function fiscalCost(offer: { subsidy: number; equity: number; credit_support: number }) { return offer.subsidy + offer.equity + offer.credit_support * .08; } -function averageCredibility(cities: Array<{ objective_credibility: number }>) { return cities.length ? cities.reduce((sum, city) => sum + city.objective_credibility, 0) / cities.length : 0; } -function Metric({ label, value, delta, tone }: { label: string; value: string; delta: string; tone: string }) { return
{label}{value}{delta}
; } -function ChartBar({ point }: { point: Metric }) { return ; } +function fiscalCost(offer: { + subsidy: number; + equity: number; + credit_support: number; +}) { + return offer.subsidy + offer.equity + offer.credit_support * 0.08; +} +function averageCredibility(cities: Array<{ objective_credibility: number }>) { + return cities.length + ? cities.reduce((sum, city) => sum + city.objective_credibility, 0) / + cities.length + : 0; +} +function Metric({ + label, + value, + delta, + tone, +}: { + label: string; + value: string; + delta: string; + tone: string; +}) { + return ( +
+ {label} + {value} + {delta} +
+ ); +} +function ChartBar({ point }: { point: Metric }) { + return ( + + ); +} -function About({ capability, mode, model, setMode, setModel, create }: { capability: Capability | null; mode: string; model: string; setMode: (v: string) => void; setModel: (v: string) => void; create: (mode?: string, model?: string) => Promise }) { - return <>RESEARCH DESIGN

双层模拟与模式设置

大模型只负责私有观察下的理解、提案、否决、协调、选址与复盘;财政扣减、项目进度、履约和产能均由确定性引擎更新。

{capability?.llm_available ? "服务端已识别 DeepSeek 凭据。" : "服务端未识别 DEEPSEEK_API_KEY,LLM 选项已锁定。"}

; +function About({ + capability, + mode, + model, + processMode, + setMode, + setModel, + setProcessMode, + create, +}: { + capability: Capability | null; + mode: string; + model: string; + processMode: string; + setMode: (v: string) => void; + setModel: (v: string) => void; + setProcessMode: (v: string) => void; + create: ( + mode?: string, + model?: string, + processMode?: string, + ) => Promise; +}) { + return ( + <> + RESEARCH DESIGN +

认知层、组织过程与刚性结算

+

+ 组织 Agent + 从各自专属动作集中选择议程、会前沟通、联盟、审查与协调行动;财政扣减、合同、项目进度、履约和产能仍由确定性引擎更新。 +

+
+ + + +
+

+ 非正式行动可以改变议程、信任、工具组合和行动顺序,但不能越过财政与合同硬约束。 +

+ + + ); +} +const organizationEvidence: Record = { + formal_procedure: { + label: "重大行政决策程序暂行条例", + url: "https://www.moj.gov.cn/pub/sfbgw/zcjd/201905/t20190516_390231.html", + }, + fund_independence: { + label: "政府投资基金高质量发展指导意见", + url: "https://www.mee.gov.cn/zcwj/gwywj/202501/t20250108_1100235.shtml", + }, + informal_networks: { + label: "中国科层权力网络中的政策试验研究", + url: "https://www.cambridge.org/core/journals/china-quarterly/article/policy-experimentation-within-bureaucratic-power-networks-the-policebusiness-cooperation-scheme-in-urban-china/6E7E2E1536065118E626082F0CFF58E8", + }, + attention: { + label: "中国科层体系中的注意力竞争", + url: "https://link.springer.com/article/10.1186/s40711-018-0071-z", + }, +}; +function OrganizationTimeline({ world }: { world: World }) { + const [cityId, setCityId] = useState( + world.selected_city_id ?? Object.keys(world.cities)[0], + ); + const actions = (world.organization_actions ?? []).filter( + (item) => item.city_id === cityId, + ); + const plans = (world.organization_plans ?? []).filter( + (item) => item.city_id === cityId, + ); + const windows = (world.opportunity_windows ?? []).filter( + (item) => item.city_id === cityId || item.city_id === null, + ); + const openActions = (world.open_action_proposals ?? []).filter( + (item) => item.city_id === cityId, + ); + const learning = Object.values(world.organization_learning ?? {}).filter( + (item) => item.agent_id.startsWith(`${cityId}_`), + ); + const process = world.organization_processes?.[cityId]; + const arenaName = (item: OrganizationAction) => + item.arena === "formal" + ? "正式" + : item.arena === "informal" + ? "非正式" + : item.arena === "initiative" + ? "行动发起" + : item.arena === "procedure" + ? "程序转换" + : "硬约束"; + return ( + <> + ORGANIZATION AS ACTOR · EVIDENCE-GROUNDED +

谁发起、程序何时转换、企业何时回应

+

+ 当前世界使用 {world.process_mode}{" "} + 模式。各部门先独立决定行动或等待,再竞争有限注意力;领导可实时启动、暂停、退回或重新议程化正式程序。 +

+
+ +
+ {process && ( +
+ + 程序状态 {process.formal_status} + + + 最近转换 {process.last_transition} + + + 议程优先度 {(process.agenda_priority * 100).toFixed(0)}% + + + 程序完整度{" "} + {(process.procedural_completeness * 100).toFixed(0)}% + + + 联盟支持 {(process.coalition_support * 100).toFixed(0)}% + + + 注意力名额 {process.attention_budget} + +
+ )} +

多步计划:原计划 → 实际行动 → 偏离

+
+ {plans.map((plan) => ( +
+
+ {plan.actor_id} + {plan.status} +
+

+ {plan.objective} +

+ + 已选策略:{plan.selected_strategy} · Q{plan.created_quarter}—Q + {plan.horizon_quarter} · {plan.provider} + +
+ 比较过的策略树 + {plan.alternatives.map((option) => ( +

+ {option.id === plan.selected_strategy ? "✓ " : ""} + {option.name}({(option.score * 100).toFixed(0)}%): + {option.approach};风险 {option.risks.join("、") || "未标注"} +

+ ))} +
+
+ {plan.nodes.map((node) => ( +

+ {node.id} · {node.status} Q{node.earliest_quarter}—Q + {node.latest_quarter}:计划 {node.action_id} + {node.actual_action_id ? ` → 实际 ${node.actual_action_id}` : ""} + {node.deviation_reason ? `;偏离:${node.deviation_reason}` : ""} +

+ ))} +
+
+ ))} + {!plans.length &&

推进一轮后将生成可检验的跨季度计划。

} +
+

内生机会窗口

+
+ {windows.map((window) => ( +
+ Q{window.start_quarter} · {window.title} + {window.kind} · 强度 {(window.magnitude * 100).toFixed(0)}% +

{window.trigger}

+ {brief(window.effects)} +
+ ))} + {!windows.length &&

尚未出现政策、竞争、财政或注意力窗口。

} +
+

目录外行动与权限审查

+
+ {openActions.map((proposal) => ( +
+ Q{proposal.quarter} · {proposal.actor_id} · {proposal.title} + {proposal.mechanism} · {proposal.status} +

{proposal.rationale}

+ 申请效果:{brief(proposal.requested_effects)} + 规则执行:{brief(proposal.executed_effects)} · {proposal.validation_reason} +
+ ))} + {!openActions.length &&

Agent 尚未提出目录外组织手段。

} +
+

组织学习与制度记忆

+
+ {learning.map((item) => ( +
+ {item.agent_id} + 财政否决 {item.veto_count} 次 · 协调成功 {item.successful_coordination_count} 次 · 第 {item.leadership_generation} 任 + 稳定信任:{brief(item.trust_by_actor)} + 组织惯例:{Object.keys(item.routines).join("、") || "尚未形成"} +

{item.transferable_lessons.at(-1) ?? item.lessons.at(-1) ?? "尚无可迁移经验"}

+
+ ))} +
+
+ {actions.map((item) => ( +
+
+ + {item.actor_id} + + {arenaName(item)} + {item.required ? " · 必经" : ""} + +
+

{item.action_name}

+

{item.rationale}

+ + 决策:{item.decision} · 紧迫度 {(item.urgency * 100).toFixed(0)}% + {item.target_actor_id ? ` · 目标 ${item.target_actor_id}` : ""} + + + 当时可选:{item.candidates.join("、") || "唯一法定动作"} + + + 选择者:{item.selection_provider} + {item.fallback ? "(已降级)" : ""} · {item.selection_rationale} + + 状态影响:{brief(item.effects)} + {item.plan_id && ( + + 计划证据:{item.plan_id}/{item.plan_node_id} + {item.deviation_reason ? ` · 偏离 ${item.deviation_reason}` : " · 按计划"} + + )} + {item.reflection && ( +

+ 行动后复盘: + {item.reflection} +

+ )} + {item.blocked_reason && ( +

未执行:{item.blocked_reason}

+ )} + +
+ ))} + {!actions.length && ( +

推进一个招商季度后将出现自主组织行动。

+ )} +
+

+ 刚性边界只保留财政、合法性、合同和生产结算;发起主体、行动时点、程序转换和企业谈判时点均由组织 + Agent 决定。 +

+ + ); +} +function Negotiations({ world }: { world: World }) { + return ( + <> + INTERNAL GOVERNANCE +

招商局提案 → 产业基金审核 → 财政局否决 → 市领导协调

+
+ {world.negotiations + .slice() + .reverse() + .map((item) => ( +
+ + Q{item.quarter} · {world.cities[item.city_id].name} + + + 招商局:现金 {money(item.proposal_tools?.subsidy)},本级股权{" "} + {money(item.proposal_tools?.equity)} + + + 联合基金:独立审核 {money(item.proposal_tools?.external_equity)} + + + 财政局:现金上限 {money(item.finance_tool_limits?.subsidy)} + ,本级股权上限 {money(item.finance_tool_limits?.equity)} + + + 最终:现金 {money(item.final_tools?.subsidy)} + 总股权{" "} + {money(item.final_tools?.total_equity_support)} + +

{item.concerns.join(";")}

+
+ {item.payment_schedule?.map((row, index) => ( + + Q+{row.due_offset} · {toolName(row.item)}{" "} + {row.amount.toFixed(2)} 亿 · {row.condition} + + ))} +
+
+ ))} +
+ + ); +} +function UnifiedNegotiations({ world }: { world: World }) { + const internal = Object.fromEntries( + world.negotiations.map((item) => [item.id, item]), + ); + return ( + <> + NESTED NEGOTIATION +

企业表达 → 政府澄清 → 内部会商 → 企业回应

+
+ {world.external_negotiations + .slice() + .reverse() + .map((item) => { + const meeting = internal[item.internal_negotiation_id]; + return ( +
+ + Q{item.quarter} · {world.cities[item.city_id].name} ·{" "} + {item.enterprise_response} + +

+ 企业表面诉求: + {item.stated_need} +

+

+ 政府追问: + {item.government_questions.join("、") || "沿用已有理解"} +

+

+ 理解更新: + {brief(item.belief_before)} → {brief(item.belief_after)}(信心{" "} + {(item.belief_confidence * 100).toFixed(0)}%) +

+ {meeting && ( + <> + + 招商局:现金 {money(meeting.proposal_tools.subsidy)} + +本级股权 {money(meeting.proposal_tools.equity)} + + + 联合基金:{money(meeting.proposal_tools.external_equity)} + + + 财政硬上限:现金{" "} + {money(meeting.finance_tool_limits.subsidy)}+本级股权{" "} + {money(meeting.finance_tool_limits.equity)} + + + 领导协调:现金 {money(meeting.final_tools.subsidy)} + +总股权 {money(meeting.final_tools.total_equity_support)} + + + )} +

+ 企业回应: + {item.enterprise_rationale} +

+ {Object.keys(item.counter_terms).length > 0 && ( +

+ 还价条件: + {brief(item.counter_terms)} +

+ )} + + 效用 {item.utility.toFixed(1)} / 最低门槛{" "} + {item.minimum_utility.toFixed(1)} · 内部记录{" "} + {item.internal_negotiation_id} + +
+ ); + })} +
+ + ); +} +function Traces({ world }: { world: World }) { + return ( + <> + AUDIT LOG +

完整决策链

+
+ {world.traces + .slice() + .reverse() + .map((trace) => ( +
+ + {trace.actor_id} · {trace.action} + +

{trace.evidence.join(";")}

+ {trace.outcome} +
+ ))} +
+ + ); } -function Negotiations({ world }: { world: World }) { return <>INTERNAL GOVERNANCE

招商局提案 → 产业基金审核 → 财政局否决 → 市领导协调

{world.negotiations.slice().reverse().map((item) =>
Q{item.quarter} · {world.cities[item.city_id].name}招商局:现金 {money(item.proposal_tools?.subsidy)},本级股权 {money(item.proposal_tools?.equity)}联合基金:独立审核 {money(item.proposal_tools?.external_equity)}财政局:现金上限 {money(item.finance_tool_limits?.subsidy)},本级股权上限 {money(item.finance_tool_limits?.equity)}最终:现金 {money(item.final_tools?.subsidy)} + 总股权 {money(item.final_tools?.total_equity_support)}

{item.concerns.join(";")}

{item.payment_schedule?.map((row, index) => Q+{row.due_offset} · {toolName(row.item)} {row.amount.toFixed(2)} 亿 · {row.condition})}
)}
; } -function UnifiedNegotiations({ world }: { world: World }) { const internal = Object.fromEntries(world.negotiations.map((item) => [item.id, item])); return <>NESTED NEGOTIATION

企业表达 → 政府澄清 → 内部会商 → 企业回应

{world.external_negotiations.slice().reverse().map((item) => { const meeting = internal[item.internal_negotiation_id]; return
Q{item.quarter} · {world.cities[item.city_id].name} · {item.enterprise_response}

企业表面诉求:{item.stated_need}

政府追问:{item.government_questions.join("、") || "沿用已有理解"}

理解更新:{brief(item.belief_before)} → {brief(item.belief_after)}(信心 {(item.belief_confidence * 100).toFixed(0)}%)

{meeting && <>招商局:现金 {money(meeting.proposal_tools.subsidy)}+本级股权 {money(meeting.proposal_tools.equity)}联合基金:{money(meeting.proposal_tools.external_equity)}财政硬上限:现金 {money(meeting.finance_tool_limits.subsidy)}+本级股权 {money(meeting.finance_tool_limits.equity)}领导协调:现金 {money(meeting.final_tools.subsidy)}+总股权 {money(meeting.final_tools.total_equity_support)}}

企业回应:{item.enterprise_rationale}

{Object.keys(item.counter_terms).length > 0 &&

还价条件:{brief(item.counter_terms)}

}效用 {item.utility.toFixed(1)} / 最低门槛 {item.minimum_utility.toFixed(1)} · 内部记录 {item.internal_negotiation_id}
; })}
; } -function Traces({ world }: { world: World }) { return <>AUDIT LOG

完整决策链

{world.traces.slice().reverse().map((trace) =>
{trace.actor_id} · {trace.action}

{trace.evidence.join(";")}

{trace.outcome}
)}
; } function AuditTimeline({ world }: { world: World }) { const [quarter, setQuarter] = useState("all"); const [agent, setAgent] = useState("all"); const audits = world.action_audits ?? []; - const quarters = Array.from(new Set(audits.map((item) => item.quarter))).sort((a, b) => a - b); - const agents = Array.from(new Set(audits.map((item) => item.agent_id))).sort(); - const visible = audits.filter((item) => (quarter === "all" || item.quarter === quarter) && (agent === "all" || item.agent_id === agent)); - return <>P2.1 · AUDITABLE AGENTS

观察 → 建议 → 规则修正 → 执行 → 复盘

{visible.slice().reverse().map((item) => )}{!visible.length &&

当前筛选条件下暂无认知行动。

}
; -} -function AuditCard({ item }: { item: AgentActionAudit }) { const fields = item.private_context_used.fields_used; const fieldNames = Array.isArray(fields) ? fields.join("、") : ""; return
Q{item.quarter} · {item.agent_id} · {item.action_type}{item.provider}{item.fallback ? " · 已降级" : " · 原生执行"}

当时观察:{brief(item.observation)}

私有信息:{"redacted" in item.private_context_used ? `默认脱敏;使用字段 ${fieldNames}` : "已记录"}

模型/策略建议:{brief(item.llm_suggestion)}

规则修正:{brief(item.rule_adjustment)}

最终执行:{brief(item.executed_action)}

理由:{item.rationale}

复盘:{item.reflection}

诊断记录 {item.diagnostics.length} 条 · {item.outcome}
; } -function brief(value: unknown) { const text = JSON.stringify(value); return text.length > 240 ? `${text.slice(0, 237)}…` : text; } -function CounterfactualLab({ world, remember, refreshList }: { world: World; remember: (world: World) => void; refreshList: () => Promise }) { + const quarters = Array.from(new Set(audits.map((item) => item.quarter))).sort( + (a, b) => a - b, + ); + const agents = Array.from( + new Set(audits.map((item) => item.agent_id)), + ).sort(); + const visible = audits.filter( + (item) => + (quarter === "all" || item.quarter === quarter) && + (agent === "all" || item.agent_id === agent), + ); + return ( + <> + P2.1 · AUDITABLE AGENTS +

观察 → 建议 → 规则修正 → 执行 → 复盘

+
+ + +
+
+ {visible + .slice() + .reverse() + .map((item) => ( + + ))} + {!visible.length && ( +

当前筛选条件下暂无认知行动。

+ )} +
+ + ); +} +function AuditCard({ item }: { item: AgentActionAudit }) { + const fields = item.private_context_used.fields_used; + const fieldNames = Array.isArray(fields) ? fields.join("、") : ""; + return ( +
+ + Q{item.quarter} · {item.agent_id} · {item.action_type} + + + {item.provider} + {item.fallback ? " · 已降级" : " · 原生执行"} + +

+ 当时观察: + {brief(item.observation)} +

+

+ 私有信息: + {"redacted" in item.private_context_used + ? `默认脱敏;使用字段 ${fieldNames}` + : "已记录"} +

+

+ 模型/策略建议: + {brief(item.llm_suggestion)} +

+

+ 规则修正: + {brief(item.rule_adjustment)} +

+

+ 最终执行: + {brief(item.executed_action)} +

+

+ 理由: + {item.rationale} +

+

+ 复盘: + {item.reflection} +

+ + 诊断记录 {item.diagnostics.length} 条 · {item.outcome} + +
+ ); +} +function brief(value: unknown) { + const text = JSON.stringify(value); + return text.length > 240 ? `${text.slice(0, 237)}…` : text; +} +function CounterfactualLab({ + world, + remember, + refreshList, +}: { + world: World; + remember: (world: World) => void; + refreshList: () => Promise; +}) { const [quarter, setQuarter] = useState(Math.max(0, world.quarter - 1)); - const [text, setText] = useState("临江市财政收入下降30%,但上级提供5亿元专项资金,优先保障设备采购后的第二期补贴。"); + const [text, setText] = useState( + "临江市财政收入下降30%,但上级提供5亿元专项资金,优先保障设备采购后的第二期补贴。", + ); const [baseline, setBaseline] = useState(null); const [branch, setBranch] = useState(null); const [plan, setPlan] = useState(null); const [delta, setDelta] = useState | null>(null); const [message, setMessage] = useState(""); - async function makeBranch() { const result = await api.createPair(world.id, quarter); setBaseline(result.baseline); setBranch(result.branch); setPlan(null); setDelta(null); setMessage(`已从Q${quarter}生成基线/干预副本,两者随机状态完全一致。`); await refreshList(); } - async function parse() { if (!branch) return; setPlan(await api.draftIntervention(branch.id, text)); } - async function confirm() { if (!branch || !plan) return; const result = await api.confirmIntervention(branch.id, plan.id); setPlan(result.plan); setMessage(`干预已确认,将从Q${plan.effective_quarter}按规则执行。`); } - async function sync() { if (!baseline || !branch) return; const result = await api.syncPair(baseline.id, branch.id, 1); setBaseline(result.baseline); setBranch(result.branch); setDelta(result.comparison.delta); remember(result.branch); } - return <>P2.2 · COUNTERFACTUAL

历史节点复制与干预确认