登记Product Hunt候选并补充Kilo公司个案

This commit is contained in:
2026-10-30 15:11:59 +08:00
parent f27b82b6a5
commit dca7c81889
11 changed files with 616 additions and 35 deletions
+32 -1
View File
@@ -43,11 +43,20 @@ export type ResearchCoverage = {
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" | "promotion";
companyId?: 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;
reviews: ResearchReview[]; coverage?: ResearchCoverage; candidateBatches?: ResearchCandidateBatch[];
};
export function evidenceUrl(value: string): string | undefined {
@@ -98,6 +107,28 @@ export function validateResearch(snapshot: ResearchSnapshot) {
!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", "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"))
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 ||