Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Changelog

本项目遵循语义化版本。日期使用北京时间。

## [0.5.0] - 2026-08-13

### Added

- 合肥—蔚来六步实时典型博弈回放与共同随机数财政冲击分支;
- 联合产业基金实体、独立审核、分期承诺和资金池结算;
- 15%—35%财政可动用比例 × 联合基金开关敏感性;
- 首页实验报告中心,支持策略、消融、seed、失败案例和归档世界下钻;
- 演示、报告仓库和真实案例机制的自动化测试与复现命令。

### Changed

- 产品文档、路线图和案例参数卡统一到v0.5实际能力;
- Web端版本和API健康信息升级到0.5.0;
- 财政成本与外部股权资金来源分账展示,避免把联合基金误算成本级财政现金。

### Fixed

- 移除Web入口未使用的分支/干预函数,修复GitHub Actions ESLint失败;
- 联合基金回合只在有外部融资目标时生成,保持通用世界的协商协议兼容。

## [0.4.0] - 2026-08-12

- P2.2—P2.4反事实、解释、材料闭环;
- 统一政企双边协商与政府内部协调;
- 合肥—蔚来公开案例参数卡和独立可视化实验报告。
17 changes: 12 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
.PHONY: install api demo test lint web-dev web-build
.PHONY: install api demo demo-stack test lint web-dev web-build web-check verify

install:
python3 -m pip install -e '.[dev]'

api:
uvicorn insidegov.api:app --reload --port 8000
uv run uvicorn insidegov.api:app --reload --port 8000

demo:
PYTHONPATH=src python3 -m insidegov.cli run --quarters 16
uv run insidegov demo --seed 42 --fiscal-multiplier 0.5

demo-stack:
./scripts/start-demo.sh

test:
PYTHONPATH=src python3 -m pytest -q
uv run pytest -q

lint:
ruff check src tests
uv run ruff check src tests

web-dev:
cd apps/web && npm run dev

web-build:
cd apps/web && npm run build

web-check:
cd apps/web && npm run lint && npm run typecheck && npm test && npm run build

verify: lint test web-check
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ InsideGov 是一个面向政企互动研究与政策演示的多智能体实验
推荐使用 `uv` 安装与运行:

```bash
# 五分钟评委演示:实时生成基线、反事实、审计链和敏感性结果
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
uv run insidegov compare
uv run insidegov matrix --no-llm
Expand Down Expand Up @@ -55,6 +60,10 @@ npm install
npm run dev
```

Web 端要求 Node.js 22 或更高版本。也可以运行 `make demo-stack` 同时启动 API 与前端,
然后在首页点击“进入五分钟演示”。演示使用确定性认知层,断网时也能完成,不消耗
模型 Token。详细讲解词和验收步骤见 [docs/DEMO.md](docs/DEMO.md)。

默认使用确定性认知层。需使用 DeepSeek 时,复制 `.env.example` 的变量到本地 `.env`,将凭据放在 `DEEPSEEK_API_KEY`,然后在控制台“研究说明”中创建 LLM 世界。密钥不应提交到 Git。

## 仓库结构
Expand All @@ -79,6 +88,10 @@ tests/ 可复现性和关键约束测试
- DeepSeek OpenAI 兼容接口,支持 `deepseek-v4-flash` / `deepseek-v4-pro`,异常时单步自动降级;
- 四类 Agent 独立校准任务;确定性与两种 DeepSeek 的多种子矩阵;五组机制消融;均值、方差和失败案例报告;
- 私有信息、内部治理、信用扩散、供应链溢出均进入状态转移公式;消融会改变入园门槛、履约数和集群规模,不只是更改提示词。
- **五分钟典型博弈回放**:从公开事实与参数来源,到企业表达、政府内部审核、联合基金、分期合同、跨期履约和财政冲击反事实;所有 ID 和结果均由本次运行生成,不是前端预写结局。
- **实验报告中心**:策略与消融均值、单个 seed、失败/降级原因和归档世界下钻,支持 JSON 导出。
- **P2 产品闭环**:历史季度复制、自然语言干预人工确认、证据约束的 Agent 访谈、HTML/JSON 自动报告、文本材料参数抽取与溯源。
- **真实案例校准**:合肥—蔚来公开参数卡,本级财政与三类联合产业基金分账结算,以及15%—35%财政空间敏感性。
- **人才对接场景**:企业需求向量化表达、政策工具包与语言模式(官话/人话)设计、人才解读协商(理解度/信任/覆盖度)、可选平台翻译撮合、合同分期兑现与知识外溢,以及 2x2 反事实矩阵。
- **政企协商机制实验室**:企业双层需求(真实/表达错位)与政府信念分离、七种协商机制严格反事实对比、理解差距与语义/激励对齐度量、低质项目签约前识别(veto)、分阶段履约结算,以及多种子矩阵(均值/方差 + 帕累托前沿 + H1-H7 假设检验 + 语言探针)。

Expand All @@ -94,4 +107,5 @@ tests/ 可复现性和关键约束测试

代码采用 MIT License。提交贡献前请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。书籍 PDF 等受版权保护材料不会纳入公开仓库。

公开前可先运行 `uv run pytest` 和 `uv run ruff check .`。`.env`、`.insidegov/`、本地检查点、运行日志与参考文献 PDF 均已默认排除。如果密钥曾在聊天、终端或截图中暴露,应在开源前到服务商控制台轮换;仅从 Git 删除它不等于失效。
公开前可运行 `make verify`,一次检查 Python 测试/Ruff 与 Web lint/typecheck/test/build。
`.env`、`.insidegov/`、本地检查点、运行日志与参考文献 PDF 均已默认排除。如果密钥曾在聊天、终端或截图中暴露,应在开源前到服务商控制台轮换;仅从 Git 删除它不等于失效。
4 changes: 3 additions & 1 deletion apps/web/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# InsideGov Console

InsideGov 的交互式推演控制台。页面数据全部来自 Python API,支持推进、干预、分支、恢复与导出。
InsideGov 的交互式推演控制台。页面数据全部来自 Python API,支持推进、干预、分支、恢复与导出。v0.5 首页提供“进入五分钟演示”和“实验结果”两个主入口:前者实时生成典型博弈与反事实,后者支持矩阵均值、seed、消融和失败案例下钻。

```bash
npm install
Expand All @@ -9,4 +9,6 @@ npm run dev

启动前需在仓库根目录运行 `uv run uvicorn insidegov.api:app --reload`。如 API 不在 `http://localhost:8000`,设置 `NEXT_PUBLIC_INSIDEGOV_API_URL`。

也可在仓库根目录运行 `make demo-stack` 一键启动前后端。确定性演示不需要API Key。

需要 Node.js 22.13 或更高版本。
152 changes: 152 additions & 0 deletions apps/web/app/components/DemoReplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use client";

import { useState } from "react";
import { API_BASE, api, type DemoBundle, type World } from "../lib/api";

type Props = {
remember: (world: World) => void;
refreshList: () => Promise<void>;
};

export function DemoReplay({ remember, refreshList }: Props) {
const [bundle, setBundle] = useState<DemoBundle | null>(null);
const [step, setStep] = useState(0);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");

async function runDemo() {
setLoading(true);
setError("");
try {
const result = await api.createDemo();
setBundle(result);
setStep(0);
remember(result.baseline);
await refreshList();
} catch (caught) {
setError(`演示生成失败:${String(caught)}`);
} finally {
setLoading(false);
}
}

if (!bundle) {
return <div className="demo-intro">
<small>LIVE · FIVE-MINUTE EVIDENCE CHAIN</small>
<h2>五分钟典型政企博弈回放</h2>
<p>系统将现场运行合肥—蔚来校准世界:先推进共同历史,再从 Q3 复制基线与财政冲击分支,保持随机状态一致并运行至 Q16。</p>
<ul>
<li>不是前端预写剧情,每次都会生成新的世界 ID 和完整快照。</li>
<li>展示企业表达、政府内部会商、独立产业基金、条件承诺与规则结算。</li>
<li>确定性模式无需网络和模型 Key,比赛现场可稳定复现。</li>
</ul>
<button className="wide-button accent" data-testid="run-guided-demo" disabled={loading} onClick={() => void runDemo()}>
{loading ? "正在运行 29 个世界季度…" : "现场生成并进入六步回放"}
</button>
{error && <p className="error-text">{error}</p>}
</div>;
}

const current = bundle.replay.steps[step];
const meeting = bundle.replay.internal_negotiation;
const outward = bundle.replay.external_negotiation;
const comparison = bundle.comparison.delta;

return <div className="demo-replay" data-testid="guided-demo">
<div className="replay-proof">
<span>实时规则运行</span>
<code>{bundle.baseline.id}</code>
<code>{bundle.branch.id}</code>
<b>Seed {bundle.seed} · 共同祖先 Q{bundle.common_ancestor_quarter}</b>
</div>
<ol className="replay-steps" aria-label="演示步骤">
{bundle.replay.steps.map((item, index) => <li key={item.id} className={index === step ? "active" : index < step ? "done" : ""}>
<button onClick={() => setStep(index)}><i>{index + 1}</i><span>{item.title}<small>Q{item.quarter}</small></span></button>
</li>)}
</ol>

<section className="replay-stage">
<header><small>STEP {step + 1} / 6</small><h3>{current.title}</h3></header>
{current.id === "world" && <div className="replay-grid">
<article><strong>公开资料</strong><p>合肥一般公共预算收入、企业资产与现金投入、12 万辆年产能、70 亿元历史股权投资均保留来源。</p></article>
<article><strong>专家映射</strong><p>可调度财力默认取财政收入 25%;供应链、行政能力和信用指数均明确标为映射,不冒充公开事实。</p></article>
<article><strong>模型假设</strong><p>就业容量和供应商进入窗口缺少企业级公开数据,报告中始终标注外推边界。</p></article>
</div>}
{current.id === "external" && <div className="evidence-stack">
<p><b>企业表面诉求:</b>{outward.stated_need}</p>
<p><b>政府追问:</b>{outward.government_questions.join("、")}</p>
<p><b>认知更新:</b>{compact(outward.belief_before)} → {compact(outward.belief_after)}</p>
<p><b>企业回应:</b>{outward.enterprise_response} · {outward.enterprise_rationale}</p>
</div>}
{current.id === "internal" && <>
<div className="offer-chain">
<article><small>招商局提案</small><strong>现金 {money(meeting.proposal_tools.subsidy)}</strong><strong>本级股权 {money(meeting.proposal_tools.equity)}</strong><span>联合基金申请 {money(meeting.proposal_tools.external_equity)}</span></article>
<b>→</b>
<article><small>财政局硬约束</small><strong>现金上限 {money(meeting.finance_tool_limits.subsidy)}</strong><strong>本级股权上限 {money(meeting.finance_tool_limits.equity)}</strong><span>{meeting.finance_approved ? "直接通过" : "要求重组"}</span></article>
<b>→</b>
<article><small>市领导协调后</small><strong>现金 {money(meeting.final_tools.subsidy)}</strong><strong>总股权 {money(meeting.final_tools.total_equity_support)}</strong><span>{meeting.payment_schedule.length} 个条件性支付节点</span></article>
</div>
<div className="private-audit">
<h4>演示世界私有真值审计</h4>
{bundle.replay.synthetic_private_audit.map((item) => <details key={item.audit_id}>
<summary>{item.agent_id} · {item.provider} · {item.fallback ? "发生降级" : "原生执行"}</summary>
<p><b>私有信息:</b>{compact(item.private_context_used)}</p>
<p><b>当时观察:</b>{compact(item.observation)}</p>
<p><b>认知建议:</b>{compact(item.llm_suggestion)}</p>
<p><b>规则修正:</b>{compact(item.rule_adjustment)}</p>
<p><b>最终执行:</b>{compact(item.executed_action)}</p>
</details>)}
</div>
</>}
{current.id === "contract" && <>
<div className="fund-grid">{bundle.replay.funding_partners.map((fund) => <article key={fund.id}><small>{fund.source_level}</small><strong>{fund.name}</strong><p>独立风控阈值 {(fund.due_diligence_threshold * 100).toFixed(0)}%</p></article>)}</div>
<div className="promise-table">{bundle.replay.promises.map((promise) => <div key={promise.id}><span>{promise.funding_source_id ? "联合基金股权" : toolName(promise.item)}</span><b>{money(promise.amount)}</b><span>Q{promise.due_quarter} · {promise.condition}</span><em className={promise.status}>{promise.status}</em></div>)}</div>
</>}
{current.id === "delivery" && <div className="event-chain">
{bundle.replay.causal_events.slice(0, 12).map((event, index) => <article key={`${event.quarter}-${event.title}-${index}`}><time>Q{event.quarter}</time><div><strong>{event.title}</strong><p>{event.detail}</p></div></article>)}
</div>}
{current.id === "counterfactual" && <>
<div className="comparison-cards">
<article><small>基线 Q16</small><strong>{bundle.replay.baseline_outcome.cluster_size} 家集群</strong><span>{bundle.replay.baseline_outcome.total_employment.toLocaleString()} 个就业</span></article>
<article className="warning"><small>Q4 财力减半</small><strong>{bundle.replay.branch_outcome.cluster_size} 家集群</strong><span>{bundle.replay.branch_outcome.total_employment.toLocaleString()} 个就业</span></article>
<article><small>规则产生的差异</small><strong>{signed(comparison.cluster_size)} 家</strong><span>{signed(comparison.total_employment)} 个就业</span></article>
</div>
<p className="boundary">财力冲击 → 本级承诺延期 → 客观信用与企业感知下降 → 边缘供应商未达到进入门槛。差值是模型内部机制结果,不是现实政策因果效应。</p>
<SensitivityTable bundle={bundle} />
</>}
</section>

<footer className="replay-actions">
<button disabled={step === 0} onClick={() => setStep((value) => value - 1)}>← 上一步</button>
<button onClick={() => remember(bundle.baseline)}>打开基线世界</button>
<button onClick={() => remember(bundle.branch)}>打开冲击分支</button>
<a href={`${API_BASE}/worlds/${bundle.branch.id}/export`}>导出分支 JSON</a>
{step < bundle.replay.steps.length - 1
? <button className="primary" onClick={() => setStep((value) => value + 1)}>下一步 →</button>
: <button className="primary" onClick={() => void runDemo()}>重置并重新运行</button>}
</footer>
</div>;
}

function SensitivityTable({ bundle }: { bundle: DemoBundle }) {
return <div className="sensitivity-table">
<h4>财政空间 × 联合基金敏感性</h4>
<div className="table-row header"><span>可调度财力</span><span>联合基金</span><span>总股权支持</span><span>与历史 70 亿差距</span><span>Q16 集群</span></div>
{bundle.sensitivity.runs.map((run) => <div className="table-row" key={`${run.available_budget_share}-${run.joint_investment}`}>
<span>{(run.available_budget_share * 100).toFixed(0)}%</span>
<span>{run.joint_investment ? "开启" : "关闭"}</span>
<b>{money(run.total_equity_support)}</b>
<span>{money(run.historical_equity_gap)}</span>
<span>{run.cluster_size} 家</span>
</div>)}
</div>;
}

function compact(value: unknown) {
const text = JSON.stringify(value);
return text.length > 420 ? `${text.slice(0, 417)}…` : text;
}

function money(value?: number) { return value == null ? "—" : `${value.toFixed(2)} 亿`; }
function signed(value?: number) { return value == null ? "—" : `${value > 0 ? "+" : ""}${value.toLocaleString()}`; }
function toolName(item: string) { return ({ subsidy: "现金补贴", equity: "本级股权", credit_support_cost: "信贷支持成本" } as Record<string, string>)[item] ?? item; }
Loading
Loading