From 43308171384e22ddc9bf1b89d97189965ee00982 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sat, 31 Oct 2026 21:18:11 +0800 Subject: [PATCH] feat: show ARR disclosure coverage by business category --- apps/docs/src/components/ArrDisclosures.tsx | 13 ++++++++- apps/docs/src/lib/research-statistics.ts | 16 ++++++++++++ apps/docs/tests/research.test.ts | 29 ++++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/components/ArrDisclosures.tsx b/apps/docs/src/components/ArrDisclosures.tsx index 36957df..1bd564f 100644 --- a/apps/docs/src/components/ArrDisclosures.tsx +++ b/apps/docs/src/components/ArrDisclosures.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { evidenceUrl, type ResearchSnapshot } from "@/lib/ai-research"; -import { arrDisclosures } from "@/lib/research-statistics"; +import { arrDisclosureCoverage, arrDisclosures } from "@/lib/research-statistics"; const labels: Record = { signal: "企业或当事方披露", estimate: "第三方估算", @@ -11,6 +11,7 @@ export default function ArrDisclosures({ data, onSelect }: { data: ResearchSnaps const [category, setCategory] = useState(""); const [year, setYear] = useState(""); const rows = arrDisclosures(data, { category, foundedYear: year ? Number(year) : undefined }); + const coverage = arrDisclosureCoverage(data, year ? Number(year) : undefined); const companies = new Set(rows.map((row) => row.company.id)); const categories = new Map(data.categories.map((item) => [item.id, item.name])); const evidence = new Map(data.evidence.map((item) => [item.id, item])); @@ -21,6 +22,16 @@ export default function ArrDisclosures({ data, onSelect }: { data: ResearchSnaps +
+ + + {coverage.filter((item) => item.companies > 0).map((item) => + + + + )} +
ARR 披露覆盖:按所选成立年份、每家公司计一次。点击分类筛选下方证据表。
业务分类已采集公司企业或当事方披露仅第三方估算缺少 ARR披露覆盖率
{item.companies}{item.reported}{item.estimatedOnly}{item.missing}{new Intl.NumberFormat("zh-CN", { style: "percent", maximumFractionDigits: 1 }).format(item.disclosed / item.companies)}
+

分母含仅有目录资料的公司,覆盖率反映本次采集深度,不代表行业披露率。企业披露含历史里程碑,同一公司兼有披露和估算时归企业披露;缺少 ARR 的公司仍可能有其他收入。披露时点、币种与合同定义未对齐,不能将已披露少数公司的均值外推至整个分类,也不能由离散性判断财务造假。

diff --git a/apps/docs/src/lib/research-statistics.ts b/apps/docs/src/lib/research-statistics.ts index 28d5466..ffd2ee9 100644 --- a/apps/docs/src/lib/research-statistics.ts +++ b/apps/docs/src/lib/research-statistics.ts @@ -15,6 +15,22 @@ export function arrDisclosures(snapshot: ResearchSnapshot, options: { category?: return [{ company, observation }]; }); } + +/** Count companies once; missing ARR is a coverage gap, never zero revenue. */ +export function arrDisclosureCoverage(snapshot: ResearchSnapshot, foundedYear?: number) { + const rows = arrDisclosures(snapshot, { foundedYear }); + return snapshot.categories.map((category) => { + const companies = snapshot.companies.filter((company) => company.cat === category.id && + [2025, 2026].includes(company.year ?? 0) && (!foundedYear || company.year === foundedYear)); + const claims = rows.filter((row) => row.company.cat === category.id); + const disclosed = new Set(claims.map((row) => row.company.id)); + const reported = new Set(claims.filter((row) => + ["signal", "historical"].includes(row.observation.selection)).map((row) => row.company.id)); + return { id: category.id, name: category.name, companies: companies.length, + disclosed: disclosed.size, reported: reported.size, estimatedOnly: disclosed.size - reported.size, + missing: companies.length - disclosed.size, records: claims.length }; + }); +} export type IncomeStats = { n: number; mean: number; median: number; min: number; max: number; sampleDeviation: number | null; cv: number | null; meanMedianRatio: number | null; diff --git a/apps/docs/tests/research.test.ts b/apps/docs/tests/research.test.ts index c243215..5923b1a 100644 --- a/apps/docs/tests/research.test.ts +++ b/apps/docs/tests/research.test.ts @@ -11,7 +11,34 @@ import { openSiteDatabase } from "../src/lib/site-database"; import { categorySummary, evidenceUrl, reviewCompanyIds, validateResearch, type ResearchSnapshot } from "../src/lib/ai-research"; import { consumeCode, initializeStore } from "../src/lib/auth-store"; import { newAuthConfig } from "../src/lib/auth"; -import { arrDisclosures, describeIncome, groupIncome, platformIncomeSample } from "../src/lib/research-statistics"; +import { arrDisclosureCoverage, arrDisclosures, describeIncome, groupIncome, platformIncomeSample } from "../src/lib/research-statistics"; + +test("ARR coverage counts companies once and separates missing disclosures from zero income", () => { + const snapshot = structuredClone(seed) as ResearchSnapshot; + snapshot.categories = [{ id: "C07", name: "行业应用", description: "" }]; + const template = snapshot.companies[0]; + snapshot.companies = [ + { ...template, id: "reported", cat: "C07", year: 2025 }, + { ...template, id: "estimated", cat: "C07", year: 2026 }, + { ...template, id: "missing", cat: "C07", year: 2025 }, + { ...template, id: "old", cat: "C07", year: 2024 }, + { ...template, id: "undated", cat: "C07", year: null }, + ]; + const observation = snapshot.observations[0]; + snapshot.observations = [ + { ...observation, id: "zero", company: "reported", metric: "arr", selection: "signal", value: 0 }, + { ...observation, id: "past", company: "reported", metric: "arr", selection: "historical", value: 10 }, + { ...observation, id: "estimate", company: "reported", metric: "arr", selection: "estimate", value: 20 }, + { ...observation, id: "only-estimate", company: "estimated", metric: "arr", selection: "historical-estimate", value: 30 }, + { ...observation, id: "goal", company: "missing", metric: "arr", selection: "excluded", value: 40 }, + { ...observation, id: "mrr", company: "missing", metric: "mrr", selection: "platform", value: 50 }, + { ...observation, id: "old-arr", company: "old", metric: "arr", selection: "signal", value: 60 }, + ]; + assert.deepEqual(arrDisclosureCoverage(snapshot), [{ id: "C07", name: "行业应用", + companies: 3, disclosed: 2, reported: 1, estimatedOnly: 1, missing: 1, records: 4 }]); + assert.deepEqual(arrDisclosureCoverage(snapshot, 2025), [{ id: "C07", name: "行业应用", + companies: 2, disclosed: 1, reported: 1, estimatedOnly: 0, missing: 1, records: 3 }]); +}); test("ARR ledger preserves historical estimates without promoting targets, run rates or outside-cohort claims", () => { const snapshot = structuredClone(seed) as ResearchSnapshot;
金额保留原币种、下界和估算身份;日期栏保留业务截至日缺项
公司 / 分类收费与交付模式原始 ARR 声明时点身份 / 限制证据