Files
endorphin-tabularium-docs/apps/docs/src/lib/ai-research.ts
T

168 lines
9.4 KiB
TypeScript

export type ResearchCategory = { id: string; name: string; description: string };
export const technicalReviewLabels = {
unreviewed: "技术证据尚未专项评审",
description: "已读技术自述",
"public-artifact": "公开代码/模型材料可访问",
"independent-test": "有第三方独立评测记录",
};
export type ResearchCompany = {
id: string; name: string; url: string; cat: string; year: number | null;
team: number | null; teamBound?: ">" | "≥"; location: string | null; business: string; buyer: string;
delivery: string; notes: string; cohort: string; teamBasis: string | null;
price: string; stage: string; sourceType: string; evidenceIds: string[];
revenueStatus: "self" | "estimate" | "platform" | "unknown";
extraUrl?: string; pricingEvidence?: string;
businessModel?: string; entityBasis?: string;
profileOnly?: boolean; classificationBasis?: string; directoryTags?: string[];
directoryOrigin?: "yc-2025-2026";
technicalReview?: {
level: "description" | "public-artifact" | "independent-test";
reviewedAt: string; summary: string; evidenceIds: string[]; testScope?: string;
};
};
export type ResearchObservation = {
id: string; company: string; name: string; kind: string; value: number;
unit: string; timing: string; evidence: string; scope: string;
selection: string; reason: string; bound: string; comparisonGroup: string | null;
metric?: "mrr" | "arr" | "revenue-30d" | "annualized-revenue";
observedAt?: string; provider?: string; connectionExpired?: boolean; origin?: "payment-platform";
};
export type ResearchEvidence = {
id: string; companyId: string | null; title: string; url: string;
publishedAt: string | null; accessedAt: string; kind: string;
summary: string; limitation: string;
};
export type ResearchReview = {
id: string; type: string; company: string; summary: string;
evidenceIds: string[]; handling: string;
scope?: "companies" | "dataset" | "excluded-candidate"; companyIds?: string[];
};
export type ResearchCoverage = {
source: string; accessedAt: string; candidateRecords: number; profilesRead: number;
matchingProfiles: number; existingProfiles: number; newProfiles: number;
otherOrMissingYear: number; unclearAI: number; dateConflicts: number; failedProfiles: number;
discoverySha256: string;
};
export type ResearchCandidateBatch = {
id: string; sourceId: string; url: string; accessedAt: string; scope: string; complete: boolean;
records: {
id: string; name: string; url: string | null; categoryId: string;
kind: "product" | "promotion"; profileStatus: "readable" | "unavailable" | "archive-only";
status: "pending-year" | "linked" | "existing-outside-cohort" | "outside-cohort" | "promotion";
companyId?: string; evidenceIds?: string[]; note: string;
}[];
};
export type ResearchSnapshot = {
version: string; asOf: string; scope: string; limitations: string[];
categories: ResearchCategory[]; companies: ResearchCompany[];
observations: ResearchObservation[]; evidence: ResearchEvidence[];
reviews: ResearchReview[]; coverage?: ResearchCoverage; candidateBatches?: ResearchCandidateBatch[];
};
export function evidenceUrl(value: string): string | undefined {
try {
const url = new URL(value);
return ["https:", "http:"].includes(url.protocol) ? url.href : undefined;
} catch { return undefined; }
}
export function validateResearch(snapshot: ResearchSnapshot) {
const unique = (values: string[], label: string) => {
if (new Set(values).size !== values.length || values.some((v) => !v))
throw new Error(`Duplicate or missing research ${label}`);
return new Set(values);
};
if (!snapshot.version || !/^\d{4}-\d{2}-\d{2}$/.test(snapshot.asOf))
throw new Error("Invalid research dataset metadata");
const categories = unique(snapshot.categories.map((c) => c.id), "categories");
const companies = unique(snapshot.companies.map((c) => c.id), "companies");
const evidence = unique(snapshot.evidence.map((e) => e.id), "evidence");
unique(snapshot.observations.map((o) => o.id), "observations");
unique(snapshot.reviews.map((r) => r.id), "reviews");
for (const c of snapshot.companies) {
const technical = c.technicalReview;
if (technical && (!["description", "public-artifact", "independent-test"].includes(technical.level) ||
!/^\d{4}-\d{2}-\d{2}$/.test(technical.reviewedAt) ||
Number.isNaN(Date.parse(technical.reviewedAt)) ||
new Date(technical.reviewedAt).toISOString().slice(0, 10) !== technical.reviewedAt ||
technical.reviewedAt > snapshot.asOf || !technical.summary.trim() ||
!technical.evidenceIds.length || technical.evidenceIds.some((id) => !evidence.has(id)) ||
(technical.level === "independent-test" && !technical.testScope?.trim())))
throw new Error(`Invalid technical review: ${c.id}`);
if (c.profileOnly && (c.revenueStatus !== "unknown" || !c.classificationBasis))
throw new Error(`Invalid profile-only research record: ${c.id}`);
if (c.directoryOrigin && (c.directoryOrigin !== "yc-2025-2026" || !/^YC\d+$/.test(c.id)))
throw new Error(`Invalid research directory origin: ${c.id}`);
if (!categories.has(c.cat) || !evidenceUrl(c.url) ||
c.evidenceIds.some((id) => !evidence.has(id)) ||
(c.pricingEvidence && !evidence.has(c.pricingEvidence)))
throw new Error(`Invalid research company reference: ${c.id}`);
}
for (const e of snapshot.evidence) {
if (!evidenceUrl(e.url) || (e.companyId && !companies.has(e.companyId)))
throw new Error(`Invalid research evidence: ${e.id}`);
}
for (const o of snapshot.observations) {
if (!companies.has(o.company) || !evidence.has(o.evidence) ||
!Number.isFinite(o.value))
throw new Error(`Invalid research observation: ${o.id}`);
}
unique((snapshot.candidateBatches ?? []).map((b) => b.id), "candidate batches");
unique((snapshot.candidateBatches ?? []).flatMap((b) => b.records.map((r) => r.id)), "candidates");
for (const batch of snapshot.candidateBatches ?? []) {
if (!/^S\d{2}$/.test(batch.sourceId) || !evidenceUrl(batch.url) || !batch.scope.trim() ||
typeof batch.complete !== "boolean" || !/^\d{4}-\d{2}-\d{2}$/.test(batch.accessedAt) ||
Number.isNaN(Date.parse(batch.accessedAt)) ||
new Date(batch.accessedAt).toISOString().slice(0, 10) !== batch.accessedAt ||
batch.accessedAt > snapshot.asOf)
throw new Error(`Invalid research candidate batch: ${batch.id}`);
for (const row of batch.records) {
const linked = ["linked", "existing-outside-cohort"].includes(row.status);
if (!row.name.trim() || !row.note.trim() || !categories.has(row.categoryId) ||
!["product", "promotion"].includes(row.kind) ||
!["readable", "unavailable", "archive-only"].includes(row.profileStatus) ||
!["pending-year", "linked", "existing-outside-cohort", "outside-cohort", "promotion"].includes(row.status) ||
(row.kind === "product" && (!row.url || !evidenceUrl(row.url))) ||
(row.url !== null && !evidenceUrl(row.url)) ||
linked !== !!row.companyId || (row.companyId && !companies.has(row.companyId)) ||
(row.kind === "promotion") !== (row.status === "promotion") ||
(row.evidenceIds ?? []).some((id) => !evidence.has(id)) ||
(row.status === "outside-cohort" && !row.evidenceIds?.length))
throw new Error(`Invalid research candidate: ${row.id}`);
}
}
const coverage = snapshot.coverage;
if (coverage && (coverage.profilesRead + coverage.failedProfiles !== coverage.candidateRecords ||
coverage.matchingProfiles !== coverage.existingProfiles + coverage.newProfiles ||
coverage.matchingProfiles + coverage.otherOrMissingYear + coverage.unclearAI + coverage.dateConflicts !== coverage.profilesRead ||
snapshot.companies.filter((c) => c.profileOnly || c.directoryOrigin === "yc-2025-2026").length !== coverage.newProfiles || !evidenceUrl(coverage.source)))
throw new Error("Invalid research coverage accounting");
for (const r of snapshot.reviews)
if (r.evidenceIds.some((id) => !evidence.has(id)) ||
(r.companyIds && (new Set(r.companyIds).size !== r.companyIds.length ||
r.companyIds.some((id) => !companies.has(id)))) ||
(r.scope && !["companies", "dataset", "excluded-candidate"].includes(r.scope)) ||
(r.scope === "companies" && !r.companyIds?.length) ||
(r.scope && r.scope !== "companies" && !!r.companyIds?.length))
throw new Error(`Invalid research review: ${r.id}`);
}
/** Explicit reviewed associations take priority; older revisions use evidence ownership. */
export function reviewCompanyIds(snapshot: ResearchSnapshot, review: ResearchReview): string[] {
if (review.companyIds) return review.companyIds;
const evidenceIds = new Set(review.evidenceIds);
return [...new Set(snapshot.evidence.flatMap((e) =>
e.companyId && evidenceIds.has(e.id) ? [e.companyId] : []))];
}
export function categorySummary(snapshot: ResearchSnapshot) {
return snapshot.categories.map((category) => {
const companies = snapshot.companies.filter((c) => c.cat === category.id);
return { ...category, count: companies.length,
self: companies.filter((c) => c.revenueStatus === "self").length,
platform: companies.filter((c) => c.revenueStatus === "platform").length,
estimate: companies.filter((c) => c.revenueStatus === "estimate").length,
unknown: companies.filter((c) => c.revenueStatus === "unknown").length };
});
}