feat: explain income dispersion within comparable sample groups

This commit is contained in:
2026-10-31 21:34:38 +08:00
parent 3c631a43a6
commit 21fe8ded32
3 changed files with 65 additions and 2 deletions
+28 -1
View File
@@ -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 { arrDisclosureCoverage, arrDisclosures, describeIncome, groupIncome, platformIncomeSample } from "../src/lib/research-statistics";
import { arrDisclosureCoverage, arrDisclosures, compareIncomeGroups, describeIncome, groupIncome, platformIncomeSample } from "../src/lib/research-statistics";
test("income interpretations compare sufficient samples within a provider and known billing model", () => {
const snapshot = structuredClone(seed) as ResearchSnapshot;
const makePoints = (cat: string, values: number[], provider = "Stripe", model = "软件订阅") => values.map((value, index) => ({
company: { ...snapshot.companies[0], id: `${cat}-${provider}-${model}-${index}`, cat, businessModel: model },
observation: { ...snapshot.observations[0], provider }, value,
}));
const points = [
...makePoints("C04", [100, 100, 100]),
...makePoints("C06", [10, 10, 1000]),
...makePoints("C08", [1, 10000]),
...makePoints("C09", [1, 10, 10000], "RevenueCat"),
...makePoints("C10", [1, 10, 10000], "Stripe", "硬件服务"),
...makePoints("C07", [0, 0, 0]),
...makePoints("C01", [1, 10, 100], "Stripe", "收费模式未核验"),
];
const comparisons = compareIncomeGroups(groupIncome(points));
assert.equal(comparisons.length, 1);
assert.equal(comparisons[0].leastDispersed.category, "C04");
assert.equal(comparisons[0].mostDispersed.category, "C06");
assert.equal(comparisons[0].leastSensitive.category, "C04");
assert.equal(comparisons[0].mostSensitive.category, "C06");
assert.deepEqual(compareIncomeGroups(groupIncome(points, false)), [], "merged billing models are not comparable here");
const annualized = compareIncomeGroups(groupIncome(points.map((point) => ({ ...point, value: point.value * 12 }))));
assert.equal(annualized[0].mostSensitive.category, comparisons[0].mostSensitive.category);
assert.ok(Math.abs(annualized[0].mostDispersed.stats.cv! - comparisons[0].mostDispersed.stats.cv!) < 1e-12);
});
test("ARR coverage counts companies once and separates missing disclosures from zero income", () => {
const snapshot = structuredClone(seed) as ResearchSnapshot;