Connect evidence-backed business model assessments to company categories

This commit is contained in:
2026-11-01 00:00:35 +08:00
parent c833aa56c1
commit 5b31fa5b77
7 changed files with 113 additions and 7 deletions
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/router";
import IncomeDistribution from "./IncomeDistribution";
import ArrDisclosures from "./ArrDisclosures";
import BusinessModelAssessment from "./BusinessModelAssessment";
import {
categorySummary, evidenceUrl, technicalReviewLabels, reviewCompanyIds, type ResearchEvidence, type ResearchReview, type ResearchSnapshot,
} from "@/lib/ai-research";
@@ -56,7 +57,7 @@ export default function AICompanyResearch() {
: 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 [tab, setTab] = useState<"companies" | "revenue" | "models" | "sources" | "reviews">("companies");
const companySection = useRef<HTMLElement>(null);
const detailSection = useRef<HTMLElement>(null);
useEffect(() => {
@@ -128,7 +129,7 @@ export default function AICompanyResearch() {
<section ref={companySection} className="research-section" aria-labelledby="research-records-title">
<h2 id="research-records-title">研究数据</h2>
<div className="research-tabs" role="group" aria-label="研究数据视图">
{([['companies','公司与产品'],['revenue','收入观测'],['sources','原始证据'],['reviews','核查记录']] as const).map(([id,label]) =>
{([['companies','公司与产品'],['revenue','收入观测'],['models','商业模式初评'],['sources','原始证据'],['reviews','核查记录']] as const).map(([id,label]) =>
<button key={id} aria-pressed={tab === id} onClick={() => setTab(id)}>{label}</button>)}
</div>
{tab === "companies" && <>
@@ -153,6 +154,7 @@ export default function AICompanyResearch() {
{!filtered.length && <p className="research-message">没有匹配公司,请调整筛选条件。</p>}
{filtered.length > pageSize && <nav className="research-pagination" aria-label="公司数据分页"><button disabled={page === 0} onClick={() => setPage((n) => n - 1)}>上一页</button><span>{page + 1} / {Math.ceil(filtered.length / pageSize)}</span><button disabled={(page + 1) * pageSize >= filtered.length} onClick={() => setPage((n) => n + 1)}>下一页</button></nav>}
</>}
{tab === "models" && <BusinessModelAssessment data={data} onSelect={setSelected} onChooseCategory={chooseCategory} />}
{tab === "revenue" && <>
<IncomeDistribution data={data} onSelect={setSelected} />
<ArrDisclosures data={data} onSelect={setSelected} />
@@ -0,0 +1,32 @@
import { businessModelReviewType, evidenceUrl, reviewCompanyIds, type ResearchSnapshot } from "@/lib/ai-research";
export default function BusinessModelAssessment({ data, onSelect, onChooseCategory }: {
data: ResearchSnapshot; onSelect: (id: string) => void; onChooseCategory: (id: string) => void;
}) {
const reviews = data.reviews.filter((review) => review.type === businessModelReviewType && review.scope === "companies");
const evidence = new Map(data.evidence.map((item) => [item.id, item]));
return <section aria-labelledby="research-models-title">
<h3 id="research-models-title">商业模式初评</h3>
<p>从已核个案提炼收费方式、成本约束与下一步验证条件。已知事实与研究判断分列;同一种收费方式可以跨业务分类,以下个案不能代表整个行业。</p>
<p className="research-muted">这是基于当前证据的初步评估,不是收入预测或盈利排名。判断能否用于我们的业务,还需用实际客户任务、付费留存与交付成本验证;ARR 或平台 MRR 单独不能证明利润。</p>
{reviews.length ? <div className="research-table-wrap"><table>
<caption>收费模式与业务分类、公司明细及原始证据相互关联</caption>
<thead><tr><th scope="col">模式 / 业务分类</th><th scope="col">已核事实</th><th scope="col">初步判断 / 验证条件</th><th scope="col">参考个案与证据</th></tr></thead>
<tbody>{reviews.map((review) => {
const companyIds = new Set(reviewCompanyIds(data, review));
const companies = data.companies.filter((company) => companyIds.has(company.id));
const categories = data.categories.filter((category) => companies.some((company) => company.cat === category.id));
return <tr key={review.id}>
<td><strong>{review.company}</strong><div>{categories.map((category) => <button key={category.id} onClick={() => onChooseCategory(category.id)}>{category.name}</button>)}</div></td>
<td>{review.summary}</td><td>{review.handling}</td>
<td><div>{companies.map((company) => <button key={company.id} className="research-company-name" onClick={() => onSelect(company.id)}>{company.name}</button>)}</div>
<div>{review.evidenceIds.map((id) => {
const item = evidence.get(id); const url = item && evidenceUrl(item.url);
return item && url ? <p key={id}><a href={url} target="_blank" rel="noopener noreferrer">{item.title} ↗</a></p> : null;
})}</div>
</td>
</tr>;
})}</tbody>
</table></div> : <p>当前数据版本尚未记录商业模式初评。</p>}
</section>;
}
+1
View File
@@ -1,4 +1,5 @@
export type ResearchCategory = { id: string; name: string; description: string };
export const businessModelReviewType = "商业模式初评";
export const technicalReviewLabels = {
unreviewed: "技术证据尚未专项评审",
description: "已读技术自述",