diff --git a/README.md b/README.md index 71ddfb1..5d86536 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,8 @@ MRR×12仅作为显示单位转换,不改写为公司ARR;公司ARR、估算 页面本身从同一SQLite中的公司与观测记录计算,不读取第二个数据库。 平台同步时间、连接过期状态、业务描述的可编辑性和产品/法律主体未去重限制均保留。 +收入快照报告位于 `/docs/other-research/ai-company-research/income-snapshot`,样本名称链接 `overview?company=` 的公司明细。 +`python3 scripts/summarize-ai-income.py --check` 独立复核统计与报告一致性;不触发编译或构建。数据变更后先审查方法,再运行不带参数的命令更新固定快照。 默认7日样本14个产品;90日窗口新增Gojiberry的8月历史点,营销样本的均值和CV随之明显变化。 这提示先核查时间与样本构成,不能将高离散度直接解释为造假。两个近名营养应用按不同App Store ID分别建档。 diff --git a/apps/docs/src/components/AICompanyResearch.tsx b/apps/docs/src/components/AICompanyResearch.tsx index 20f40a7..46171c1 100644 --- a/apps/docs/src/components/AICompanyResearch.tsx +++ b/apps/docs/src/components/AICompanyResearch.tsx @@ -1,4 +1,5 @@ import { useEffect, useMemo, useRef, useState } from "react"; +import { useRouter } from "next/router"; import IncomeDistribution from "./IncomeDistribution"; import { categorySummary, evidenceUrl, technicalReviewLabels, type ResearchEvidence, type ResearchSnapshot, @@ -30,6 +31,7 @@ function Evidence({ item, prefix = "evidence" }: { item: ResearchEvidence; prefi } export default function AICompanyResearch() { + const router = useRouter(); const [data, setData] = useState(null); const [error, setError] = useState(false); const [attempt, setAttempt] = useState(0); @@ -42,7 +44,12 @@ export default function AICompanyResearch() { const [depth, setDepth] = useState(""); const [teamBand, setTeamBand] = useState(""); const [technicalLevel, setTechnicalLevel] = useState(""); - const [selected, setSelected] = useState(null); + const [selection, setSelection] = useState<{ route: string; id: string | null } | null>(null); + const linkedCompany = router.query.company; + const selected = selection && selection.route === router.asPath ? selection.id + : router.isReady && typeof linkedCompany === "string" && data?.companies.some((c) => c.id === linkedCompany) + ? linkedCompany : null; + function setSelected(id: string | null) { setSelection({ route: router.asPath, id }); } const [tab, setTab] = useState<"companies" | "revenue" | "sources" | "reviews">("companies"); const companySection = useRef(null); const detailSection = useRef(null); diff --git a/content/catalog.json b/content/catalog.json index 86ed7ba..507f3fa 100644 --- a/content/catalog.json +++ b/content/catalog.json @@ -219,6 +219,13 @@ "pretext/12-chapter": { "slug": "source-studies/pretext/applying-the-design", "category": "源码研究" + }, + "ai-company-research/01-income-snapshot": { + "collection": "AI 公司研究", + "group": "公司与市场", + "description": "查看14个产品的平台MRR分布、均值与离散度、历史点敏感性和逐项证据入口。", + "slug": "other-research/ai-company-research/income-snapshot", + "category": "其他研究" } } } diff --git a/content/docs/ai-company-research/00-overview.md b/content/docs/ai-company-research/00-overview.md index d789d95..e51ff7b 100644 --- a/content/docs/ai-company-research/00-overview.md +++ b/content/docs/ai-company-research/00-overview.md @@ -4,6 +4,8 @@ 以下工作台读取研究数据库。首批为公开资料定向试样,成立、公开发布和转型发布分别记录。未披露收入的公司继续保留,避免把收入资料缺失误当成零收入。 +[收入样本分布快照](/docs/other-research/ai-company-research/income-snapshot)列出当前平台MRR的分组结果、逐项证据入口和历史样本敏感性。 + ## 商业模式初评:截至 2026-10-30 以下为研究假设,用于设计付费试验,尚不足以给行业排收益名次。规模、交付证据和收入来源必须一起看;目录条目数量不是市场需求,平台样本也存在自愿公开与幸存者偏差。 diff --git a/content/docs/ai-company-research/01-income-snapshot.md b/content/docs/ai-company-research/01-income-snapshot.md new file mode 100644 index 0000000..eb7dabe --- /dev/null +++ b/content/docs/ai-company-research/01-income-snapshot.md @@ -0,0 +1,64 @@ +# AI 公司收入样本分布快照 + +截至 2026-10-30,7日窗口内有 14 个产品的美元平台MRR可纳入分组描述。创意软件订阅组的样本均值较高、CV也较大;营销软件组与消费者个人应用组的CV接近。各组只有1至5个产品,尚不足以估计行业总体或造假概率。 + +这是已采集资料的固定快照。[动态工作台](/docs/other-research/ai-company-research/overview)提供筛选与原始证据。窗口按最后同步日计算,不保证所有点同日,也不是1,013条公司/产品记录的收入普查。 + +## 7日窗口的分组结果 + +MRR单位为美元每月。CV为样本标准差除以均值;最大值剔除降幅表示移除最高收入产品后均值下降的比例。单个样本不计算离散度。 + +| 业务及收费模式 | 平台 | 样本数 | 均值 | 中位数 | CV | 最大值剔除降幅 | +| --- | --- | --- | --- | --- | --- | --- | +| 客户与收入运营 · 软件订阅 | Stripe | 4 | 34,815 | 36,723 | 0.469 | 16.9% | +| 内容与创意 · 消费者订阅 | RevenueCat | 1 | 53,101 | 53,101 | 不计算 | 不计算 | +| 内容与创意 · 内容制作服务 | Stripe | 1 | 62,423 | 62,423 | 不计算 | 不计算 | +| 内容与创意 · 软件订阅 | Stripe | 3 | 41,693 | 49,069 | 0.674 | 28.4% | +| 消费者个人应用 · 消费者订阅 | RevenueCat | 5 | 25,349 | 20,483 | 0.471 | 20.9% | + +在至少3个样本的组内,内容创意软件订阅的均值较高,但移除最大样本后的下降也最大。营销软件与个人应用的CV仅有微小差别,不据此宣称哪个领域更稳定。两个内容类单样本组虽有更高数值,不能作为典型收入或领域排名。 + +均值和中位数接近只描述当前采集点,并不证明样本具有代表性。平台公开者、成立年、规模、获客渠道与收费方式均可能影响结果。 + +## 样本与证据入口 + +点击产品名称可打开公司明细,再查看平台记录及其限制。连接已过期的近期快照仍按所标日期保留,并不表示连接当前有效。 + +| 产品 | 成立年 | MRR美元每月 | 最后同步 | 平台 | 连接状态 | +| --- | --- | --- | --- | --- | --- | +| [Boothie](/docs/other-research/ai-company-research/overview?company=MR001#research-company-detail) | 2026 | 53,101 | 2026-10-29 | RevenueCat | 采集时未标过期 | +| [White Glove Content](/docs/other-research/ai-company-research/overview?company=MR002#research-company-detail) | 2026 | 62,423 | 2026-10-29 | Stripe | 采集时未标过期 | +| [Speel.co](/docs/other-research/ai-company-research/overview?company=MR003#research-company-detail) | 2025 | 65,381 | 2026-10-29 | Stripe | 采集时未标过期 | +| [Laper](/docs/other-research/ai-company-research/overview?company=MR004#research-company-detail) | 2025 | 49,069 | 2026-10-29 | Stripe | 采集时未标过期 | +| [GETebook.ai](/docs/other-research/ai-company-research/overview?company=MR005#research-company-detail) | 2025 | 10,629 | 2026-10-29 | Stripe | 采集时未标过期 | +| [TrackAI](/docs/other-research/ai-company-research/overview?company=MR006#research-company-detail) | 2025 | 19,437 | 2026-10-27 | RevenueCat | 已过期的日期快照 | +| [nutrIA(Nutria Inc)](/docs/other-research/ai-company-research/overview?company=MR007#research-company-detail) | 2025 | 18,198 | 2026-10-29 | RevenueCat | 采集时未标过期 | +| [Calyra(平台名Nutria 9e3eaa)](/docs/other-research/ai-company-research/overview?company=MR008#research-company-detail) | 2025 | 46,562 | 2026-10-29 | RevenueCat | 采集时未标过期 | +| [Verbi](/docs/other-research/ai-company-research/overview?company=MR009#research-company-detail) | 2026 | 22,066 | 2026-10-29 | RevenueCat | 采集时未标过期 | +| [AbMaxx](/docs/other-research/ai-company-research/overview?company=MR010#research-company-detail) | 2026 | 20,483 | 2026-10-29 | RevenueCat | 采集时未标过期 | +| [traxy.ai](/docs/other-research/ai-company-research/overview?company=MR011#research-company-detail) | 2026 | 52,438 | 2026-10-29 | Stripe | 采集时未标过期 | +| [NotFair](/docs/other-research/ai-company-research/overview?company=MR012#research-company-detail) | 2026 | 13,376 | 2026-10-29 | Stripe | 采集时未标过期 | +| [Rank Prompt](/docs/other-research/ai-company-research/overview?company=MR015#research-company-detail) | 2025 | 40,112 | 2026-10-29 | Stripe | 采集时未标过期 | +| [ChatSEO](/docs/other-research/ai-company-research/overview?company=MR016#research-company-detail) | 2025 | 33,334 | 2026-10-29 | Stripe | 采集时未标过期 | + +## 扩大窗口后的敏感性 + +营销软件订阅组从7日窗口的4个产品扩大到90日窗口的5个产品,均值从34,815升至112,726美元每月,中位数从36,723变为40,112,CV从0.469升至1.551。 + +新增点来自[Gojiberry AI](/docs/other-research/ai-company-research/overview?company=AI016#research-company-detail)的8月历史快照,连接已过期。移除该最大值后,均值回到34,815美元,下降69.1%。这是日期与样本构成的敏感性,不能解释为行业风险变化。 + +## 默认窗口排除的历史记录 + +| 产品 | MRR美元每月 | 最后同步 | 排除原因 | +| --- | --- | --- | --- | +| [Adspirer](/docs/other-research/ai-company-research/overview?company=MR013#research-company-detail) | 54,684 | 2026-06-29 | 超过7日窗口,保留历史观测 | +| [PROSP](/docs/other-research/ai-company-research/overview?company=MR014#research-company-detail) | 128,005 | 2026-02-05 | 超过7日窗口,保留历史观测 | +| [Gojiberry AI](/docs/other-research/ai-company-research/overview?company=AI016#research-company-detail) | 424,368 | 2026-08-14 | 超过7日窗口,保留历史观测 | + +## 对 ARR 与商业模式的含义 + +MRR乘12只是参考年化。公司声明的ARR、合同管线、近30日收款、一次性额度和服务交付收入仍需分开;支付平台重建指标也不等于财务审计。方法与平台限制见[TrustMRR FAQ](https://trustmrr.com/faq)。 + +当前更适合选择访谈对象和设计收费试验,不能据最高均值直接决定业务方向。软件订阅优先核留存与推理成本,服务交付优先核人工工时和返工,消费者应用优先核渠道成本、退款和平台抽成。 + +行业ARR比较仍需补齐同一业务截止日、币种、经常性定义及业务边界。收入日期不完整或同页数值冲突的声明,继续保留在[核查工作台](/docs/other-research/ai-company-research/overview),不补成零值或精确数字。 diff --git a/scripts/ci.py b/scripts/ci.py index 5fa476d..6f37b87 100644 --- a/scripts/ci.py +++ b/scripts/ci.py @@ -14,7 +14,7 @@ ROOT = Path(__file__).resolve().parents[1] APP = "endorphin-tabularium-docs" INPUTS = ["Dockerfile", "Dockerfile.ci", ".dockerignore", ".npmrc", "package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "apps/docs", "content", "tools/figures", - "scripts/check-container.sh", "scripts/ci.py"] + "scripts/check-container.sh", "scripts/ci.py", "scripts/summarize-ai-income.py"] def run(*args: str, capture: bool = False) -> str: @@ -63,6 +63,7 @@ def smoke_image(image: str, name: str) -> None: def main() -> None: os.chdir(ROOT) + run(sys.executable, "scripts/summarize-ai-income.py", "--check") run(sys.executable, "-m", "unittest", "discover", "-s", "scripts", "-p", "test_*.py") scope = os.environ.get("CI_CACHE_SCOPE", "main") if not re.fullmatch(r"main|pr-[0-9]+", scope): diff --git a/scripts/summarize-ai-income.py b/scripts/summarize-ai-income.py new file mode 100644 index 0000000..7cadb1c --- /dev/null +++ b/scripts/summarize-ai-income.py @@ -0,0 +1,153 @@ +"""Render a fixed research snapshot; --check performs data analysis only, no build.""" +import argparse +import datetime as dt +import json +from pathlib import Path +import statistics as st +import sys + +ROOT = Path(__file__).resolve().parents[1] +DATA = json.loads((ROOT / 'content/research/ai-companies.seed.json').read_text()) +REFERENCE = json.loads((ROOT / 'content/research/ai-income-analysis.json').read_text()) +TARGET = ROOT / 'content/docs/ai-company-research/01-income-snapshot.md' +BASE = '/docs/other-research/ai-company-research/overview' +COMPANIES = {c['id']: c for c in DATA['companies']} +CATEGORIES = {c['id']: c['name'] for c in DATA['categories']} +END = dt.date.fromisoformat(DATA['asOf']) + + +def sample(days): + latest, excluded = {}, [] + for o in DATA['observations']: + if o.get('selection') != 'platform' or o.get('metric') != 'mrr': + continue + c = COMPANIES[o['company']] + day = o.get('observedAt') + try: + parsed = dt.date.fromisoformat(day) + valid_date = parsed.isoformat() == day + except (TypeError, ValueError): + valid_date = False + valid = (c['year'] in (2025, 2026) and o.get('origin') == 'payment-platform' + and o['unit'] == 'USD' and o['bound'] == '=' + and o.get('comparisonGroup') == 'trustmrr-usd-mrr' + and o.get('provider') and isinstance(o['value'], (int, float)) + and 0 <= o['value'] < float('inf') and valid_date + and 0 <= (END - parsed).days <= days) + if not valid: + excluded.append(o) + continue + prior = latest.get(c['id']) + if prior is None or day > prior['observedAt']: + latest[c['id']] = o + return latest, excluded + + +def groups(points): + result = {} + for cid, o in points.items(): + c = COMPANIES[cid] + key = (c['cat'], o['provider'], c.get('businessModel', '收费模式未核验')) + result.setdefault(key, []).append(o['value']) + return result + + +def stats(values): + mean = st.mean(values) + sd = st.stdev(values) if len(values) > 1 else None + reduced = st.mean(sorted(values)[:-1]) if len(values) > 1 else None + return dict(n=len(values), mean=mean, median=st.median(values), + sampleDeviation=sd, cv=sd / mean if sd is not None and mean else None, + meanWithoutLargest=reduced, + largestRemovalDrop=(mean - reduced) / mean if reduced is not None and mean else None) + + +def verify(): + assert REFERENCE['sourceVersion'] == DATA['version'], 'Reference revision mismatch' + for window in REFERENCE['windows']: + points, _ = sample(window['days']) + assert set(points) == set(window['products']), 'Sample selection mismatch' + computed = groups(points) + assert len(computed) == len(window['groups']) + for group in window['groups']: + values = computed[(group['category'], group['provider'], group['model'])] + for key, value in stats(values).items(): + expected = group[key] + assert (value is None and expected is None) or ( + value is not None and expected is not None + and abs(value - expected) <= 1e-8 * max(1, abs(expected))), key + + +def money(value): + return f'{value:,.0f}' if value is not None else '不计算' + + +def link(cid): + return f"[{COMPANIES[cid]['name']}]({BASE}?company={cid}#research-company-detail)" + + +def render(): + points, excluded = sample(7) + lines = [ + '# AI 公司收入样本分布快照', '', + f"截至 {DATA['asOf']},7日窗口内有 {len(points)} 个产品的美元平台MRR可纳入分组描述。创意软件订阅组的样本均值较高、CV也较大;营销软件组与消费者个人应用组的CV接近。各组只有1至5个产品,尚不足以估计行业总体或造假概率。", '', + f"这是已采集资料的固定快照。[动态工作台]({BASE})提供筛选与原始证据。窗口按最后同步日计算,不保证所有点同日,也不是{len(DATA['companies']):,}条公司/产品记录的收入普查。", '', + '## 7日窗口的分组结果', '', + 'MRR单位为美元每月。CV为样本标准差除以均值;最大值剔除降幅表示移除最高收入产品后均值下降的比例。单个样本不计算离散度。', '', + '| 业务及收费模式 | 平台 | 样本数 | 均值 | 中位数 | CV | 最大值剔除降幅 |', + '| --- | --- | --- | --- | --- | --- | --- |', + ] + for key, values in sorted(groups(points).items()): + cat, provider, model = key + v = stats(values) + cv = f"{v['cv']:.3f}" if v['cv'] is not None else '不计算' + drop = f"{v['largestRemovalDrop']:.1%}" if v['largestRemovalDrop'] is not None else '不计算' + lines.append(f"| {CATEGORIES[cat]} · {model} | {provider} | {v['n']} | {money(v['mean'])} | {money(v['median'])} | {cv} | {drop} |") + lines += ['', + '在至少3个样本的组内,内容创意软件订阅的均值较高,但移除最大样本后的下降也最大。营销软件与个人应用的CV仅有微小差别,不据此宣称哪个领域更稳定。两个内容类单样本组虽有更高数值,不能作为典型收入或领域排名。', '', + '均值和中位数接近只描述当前采集点,并不证明样本具有代表性。平台公开者、成立年、规模、获客渠道与收费方式均可能影响结果。', '', + '## 样本与证据入口', '', + '点击产品名称可打开公司明细,再查看平台记录及其限制。连接已过期的近期快照仍按所标日期保留,并不表示连接当前有效。', '', + '| 产品 | 成立年 | MRR美元每月 | 最后同步 | 平台 | 连接状态 |', + '| --- | --- | --- | --- | --- | --- |', + ] + for cid, o in sorted(points.items()): + status = '已过期的日期快照' if o.get('connectionExpired') else '采集时未标过期' + lines.append(f"| {link(cid)} | {COMPANIES[cid]['year']} | {money(o['value'])} | {o['observedAt']} | {o['provider']} | {status} |") + fresh = stats(groups(points)[('C06', 'Stripe', '软件订阅')]) + broad, _ = sample(90) + older = stats(groups(broad)[('C06', 'Stripe', '软件订阅')]) + lines += ['', '## 扩大窗口后的敏感性', '', + f"营销软件订阅组从7日窗口的{fresh['n']}个产品扩大到90日窗口的{older['n']}个产品,均值从{money(fresh['mean'])}升至{money(older['mean'])}美元每月,中位数从{money(fresh['median'])}变为{money(older['median'])},CV从{fresh['cv']:.3f}升至{older['cv']:.3f}。", '', + f"新增点来自{link('AI016')}的8月历史快照,连接已过期。移除该最大值后,均值回到{money(older['meanWithoutLargest'])}美元,下降{older['largestRemovalDrop']:.1%}。这是日期与样本构成的敏感性,不能解释为行业风险变化。", '', + '## 默认窗口排除的历史记录', '', + '| 产品 | MRR美元每月 | 最后同步 | 排除原因 |', + '| --- | --- | --- | --- |', + ] + for o in excluded: + lines.append(f"| {link(o['company'])} | {money(o['value'])} | {o.get('observedAt', '未知')} | 超过7日窗口,保留历史观测 |") + lines += ['', '## 对 ARR 与商业模式的含义', '', + 'MRR乘12只是参考年化。公司声明的ARR、合同管线、近30日收款、一次性额度和服务交付收入仍需分开;支付平台重建指标也不等于财务审计。方法与平台限制见[TrustMRR FAQ](https://trustmrr.com/faq)。', '', + '当前更适合选择访谈对象和设计收费试验,不能据最高均值直接决定业务方向。软件订阅优先核留存与推理成本,服务交付优先核人工工时和返工,消费者应用优先核渠道成本、退款和平台抽成。', '', + f"行业ARR比较仍需补齐同一业务截止日、币种、经常性定义及业务边界。收入日期不完整或同页数值冲突的声明,继续保留在[核查工作台]({BASE}),不补成零值或精确数字。", '', + ] + return '\n'.join(lines) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--check', action='store_true') + args = parser.parse_args() + verify() + content = render() + if args.check: + if not TARGET.exists() or TARGET.read_text() != content: + sys.exit('Income snapshot differs; regenerate after reviewing data changes') + print('Income reference, samples and report are consistent') + else: + TARGET.write_text(content) + print(TARGET.relative_to(ROOT)) + + +if __name__ == '__main__': + main()