@agentsean/measure 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +661 -0
  2. package/dist/analyze.d.ts +19 -0
  3. package/dist/analyze.d.ts.map +1 -0
  4. package/dist/analyze.js +151 -0
  5. package/dist/analyze.js.map +1 -0
  6. package/dist/claims.d.ts +24 -0
  7. package/dist/claims.d.ts.map +1 -0
  8. package/dist/claims.js +59 -0
  9. package/dist/claims.js.map +1 -0
  10. package/dist/engine.d.ts +20 -0
  11. package/dist/engine.d.ts.map +1 -0
  12. package/dist/engine.js +108 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/estimator.d.ts +17 -0
  15. package/dist/estimator.d.ts.map +1 -0
  16. package/dist/estimator.js +91 -0
  17. package/dist/estimator.js.map +1 -0
  18. package/dist/index.d.ts +14 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +13 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/ladder.d.ts +41 -0
  23. package/dist/ladder.d.ts.map +1 -0
  24. package/dist/ladder.js +99 -0
  25. package/dist/ladder.js.map +1 -0
  26. package/dist/peek.d.ts +19 -0
  27. package/dist/peek.d.ts.map +1 -0
  28. package/dist/peek.js +31 -0
  29. package/dist/peek.js.map +1 -0
  30. package/dist/persist.d.ts +66 -0
  31. package/dist/persist.d.ts.map +1 -0
  32. package/dist/persist.js +265 -0
  33. package/dist/persist.js.map +1 -0
  34. package/dist/power.d.ts +36 -0
  35. package/dist/power.d.ts.map +1 -0
  36. package/dist/power.js +130 -0
  37. package/dist/power.js.map +1 -0
  38. package/dist/register.d.ts +17 -0
  39. package/dist/register.d.ts.map +1 -0
  40. package/dist/register.js +86 -0
  41. package/dist/register.js.map +1 -0
  42. package/dist/suppress.d.ts +24 -0
  43. package/dist/suppress.d.ts.map +1 -0
  44. package/dist/suppress.js +60 -0
  45. package/dist/suppress.js.map +1 -0
  46. package/dist/track.d.ts +10 -0
  47. package/dist/track.d.ts.map +1 -0
  48. package/dist/track.js +30 -0
  49. package/dist/track.js.map +1 -0
  50. package/dist/types.d.ts +89 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +38 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +52 -0
package/dist/ladder.js ADDED
@@ -0,0 +1,99 @@
1
+ import { DEFAULT_EVIDENCE_TIER, EVIDENCE_MEANING, EVIDENCE_TIERS, } from "./types.js";
2
+ export { EVIDENCE_MEANING, EVIDENCE_TIERS, DEFAULT_EVIDENCE_TIER };
3
+ /** Realised MDE at or below this is "sufficient power" for tier A. */
4
+ export const TIER_A_MDE_MAX = 0.25;
5
+ /** Rule S8: refuse to start a test whose MDE exceeds 40%. */
6
+ export const UNDERPOWERED_MDE = 0.4;
7
+ export function isEvidenceTier(value) {
8
+ return EVIDENCE_TIERS.includes(value);
9
+ }
10
+ /**
11
+ * PLAN Phase 7 ladder. Most rows on agency sites land in E.
12
+ * Causation is only allowed for A.
13
+ */
14
+ export function assignEvidenceTier(input) {
15
+ if (input.peeking)
16
+ return DEFAULT_EVIDENCE_TIER;
17
+ if (input.underpowered || input.suppressed.includes("S8"))
18
+ return "E";
19
+ if (!input.applied && !input.preRegistered)
20
+ return "E";
21
+ const effect = input.lift;
22
+ const mde = input.realisedMde;
23
+ const exceedsMde = effect !== null &&
24
+ mde !== null &&
25
+ Number.isFinite(effect) &&
26
+ Number.isFinite(mde) &&
27
+ Math.abs(effect) >= mde;
28
+ const directional = effect !== null &&
29
+ Number.isFinite(effect) &&
30
+ effect !== 0 &&
31
+ (mde === null || Math.abs(effect) < mde);
32
+ if (input.hasControl &&
33
+ input.preRegistered &&
34
+ input.powerOk &&
35
+ input.suppressed.filter((s) => s !== "S6").length === 0) {
36
+ return "A";
37
+ }
38
+ if (input.hasControl && !input.preRegistered && exceedsMde)
39
+ return "B";
40
+ if (!input.hasControl && input.googleUpdateJoined && exceedsMde)
41
+ return "C";
42
+ if (directional)
43
+ return "D";
44
+ if (input.applied)
45
+ return "E";
46
+ return DEFAULT_EVIDENCE_TIER;
47
+ }
48
+ export function claimCausation(tier) {
49
+ if (tier === "A") {
50
+ return {
51
+ allowed: true,
52
+ reason: EVIDENCE_MEANING.A,
53
+ };
54
+ }
55
+ return {
56
+ allowed: false,
57
+ reason: `Causation is not supported at evidence tier ${tier}: ${EVIDENCE_MEANING[tier]}`,
58
+ };
59
+ }
60
+ /** Per-URL "this change generated N clicks" must never exist. */
61
+ export function refuseUrlAttribution(url) {
62
+ throw new Error(`Refused per-URL attribution for ${url}. The unit of causal claim is the cohort, never the URL.`);
63
+ }
64
+ export function statementFor(tier, opts) {
65
+ const liftPct = opts?.lift !== null && opts?.lift !== undefined ? pct(opts.lift) : null;
66
+ const mdePct = opts?.realisedMde !== null && opts?.realisedMde !== undefined
67
+ ? pct(opts.realisedMde)
68
+ : null;
69
+ switch (tier) {
70
+ case "A":
71
+ return liftPct
72
+ ? `Measured in a pre-registered controlled experiment. Relative lift ${liftPct} on clicks. ${EVIDENCE_MEANING.A}.`
73
+ : `Measured in a pre-registered controlled experiment. ${EVIDENCE_MEANING.A}.`;
74
+ case "B":
75
+ return `Matched-cohort observational comparison. Effect ${liftPct ?? "observed"} exceeds the MDE${mdePct ? ` (${mdePct})` : ""}. Not a pre-registered experiment.`;
76
+ case "C": {
77
+ const updates = opts?.incidentTitles?.length
78
+ ? ` Google updates in the window: ${opts.incidentTitles.join("; ")}.`
79
+ : " Google-update annotations are joined; other things also moved.";
80
+ return `Clicks moved ${liftPct ?? "directionally"} after this change.${updates} We cannot isolate the change.`;
81
+ }
82
+ case "D":
83
+ return `Directional signal only (${liftPct ?? "below the detectable floor"}). The test could only have detected changes larger than ${mdePct ?? "the MDE"}. Not a causal claim.`;
84
+ case "E":
85
+ default: {
86
+ const need = opts?.neededClicksPerArm
87
+ ? ` We'd need roughly ${Math.round(opts.neededClicksPerArm)} clicks per group over 8 weeks`
88
+ : " We'd need roughly 1,900 clicks per group over 8 weeks";
89
+ const have = opts?.monthlyClicks !== undefined
90
+ ? ` and this site has ~${Math.round(opts.monthlyClicks)} clicks/month.`
91
+ : ".";
92
+ return `Applied. Not measurable at this site's traffic volume.${need}${have} This is true of every SEO tool; only Sean says so.`;
93
+ }
94
+ }
95
+ }
96
+ function pct(n) {
97
+ return `${Math.round(n * 1000) / 10}%`;
98
+ }
99
+ //# sourceMappingURL=ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ladder.js","sourceRoot":"","sources":["../src/ladder.ts"],"names":[],"mappings":"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"}
package/dist/peek.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Peeking takes the null false-positive rate from 4.7% to 22.9%.
3
+ * planned_end is immutable once running; no verdict before that date.
4
+ */
5
+ export type PeekDecision = {
6
+ allowed: true;
7
+ provisional: false;
8
+ } | {
9
+ allowed: false;
10
+ provisional: true;
11
+ reason: string;
12
+ };
13
+ export declare function analysisDateReached(plannedEnd: string, now: Date): boolean;
14
+ export declare function guardPeeking(plannedEnd: string, now: Date, peekingBlocked?: boolean): PeekDecision;
15
+ export declare class PlannedEndImmutableError extends Error {
16
+ readonly name = "PlannedEndImmutableError";
17
+ constructor();
18
+ }
19
+ //# sourceMappingURL=peek.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peek.d.ts","sourceRoot":"","sources":["../src/peek.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GACpB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,KAAK,CAAA;CAAE,GACrC;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAE1E;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,IAAI,EACT,cAAc,UAAO,GACpB,YAAY,CAgBd;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,SAAkB,IAAI,8BAA8B;;CAMrD"}
package/dist/peek.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Peeking takes the null false-positive rate from 4.7% to 22.9%.
3
+ * planned_end is immutable once running; no verdict before that date.
4
+ */
5
+ export function analysisDateReached(plannedEnd, now) {
6
+ return now.toISOString().slice(0, 10) >= plannedEnd;
7
+ }
8
+ export function guardPeeking(plannedEnd, now, peekingBlocked = true) {
9
+ if (analysisDateReached(plannedEnd, now)) {
10
+ return { allowed: true, provisional: false };
11
+ }
12
+ if (!peekingBlocked) {
13
+ return {
14
+ allowed: false,
15
+ provisional: true,
16
+ reason: `Provisional — not a decision. Analysis date is ${plannedEnd}.`,
17
+ };
18
+ }
19
+ return {
20
+ allowed: false,
21
+ provisional: true,
22
+ reason: `Peeking blocked until ${plannedEnd}. Daily peeking raises the null false-positive rate from 4.7% to 22.9%.`,
23
+ };
24
+ }
25
+ export class PlannedEndImmutableError extends Error {
26
+ name = "PlannedEndImmutableError";
27
+ constructor() {
28
+ super("planned_end is immutable once the experiment is running. Extending a running test manufactures false positives.");
29
+ }
30
+ }
31
+ //# sourceMappingURL=peek.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peek.js","sourceRoot":"","sources":["../src/peek.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,UAAU,mBAAmB,CAAC,UAAkB,EAAE,GAAS;IAC/D,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,GAAS,EACT,cAAc,GAAG,IAAI;IAErB,IAAI,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,KAAK;YACd,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,kDAAkD,UAAU,GAAG;SACxE,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,yBAAyB,UAAU,yEAAyE;KACrH,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IAC/B,IAAI,GAAG,0BAA0B,CAAC;IACpD;QACE,KAAK,CACH,iHAAiH,CAClH,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,66 @@
1
+ import { type SqliteDatabase } from "@agentsean/db";
2
+ import type { ClaimRecord, EvidenceTier, ExperimentSpec } from "./types.js";
3
+ export type StoredExperiment = {
4
+ id: string;
5
+ siteId: string;
6
+ hypothesis: string;
7
+ interventionKind: string;
8
+ status: string;
9
+ design: string;
10
+ unit: string;
11
+ randomisationSeed: number;
12
+ clusterKey: string | null;
13
+ preStart: string;
14
+ preEnd: string;
15
+ postStart: string;
16
+ plannedEnd: string;
17
+ primaryMetric: string;
18
+ secondaryMetric: string | null;
19
+ plannedMde: number;
20
+ powerTarget: number;
21
+ alpha: number;
22
+ evidenceTier: string | null;
23
+ peekingBlocked: number;
24
+ createdAt: string;
25
+ concludedAt: string | null;
26
+ };
27
+ export declare function seedDataAnomalies(db: SqliteDatabase): number;
28
+ export declare function insertExperiment(db: SqliteDatabase, spec: ExperimentSpec, plannedMde: number, status: string, now: Date): string;
29
+ export declare function getExperiment(db: SqliteDatabase, id: string): StoredExperiment | null;
30
+ export declare function listExperiments(db: SqliteDatabase, siteId: string): StoredExperiment[];
31
+ export declare function setExperimentStatus(db: SqliteDatabase, id: string, status: string, extra?: {
32
+ evidenceTier?: string | null | undefined;
33
+ concludedAt?: string | null | undefined;
34
+ }): void;
35
+ export declare function assertPlannedEndImmutable(current: StoredExperiment, nextPlannedEnd: string): void;
36
+ export declare function saveResult(db: SqliteDatabase, experimentId: string, row: {
37
+ metric: string;
38
+ pointEstimate: number | null;
39
+ ciLow: number | null;
40
+ ciHigh: number | null;
41
+ ciLevel: number | null;
42
+ probPositive: number | null;
43
+ realisedMde: number | null;
44
+ nBoot: number | null;
45
+ suppressedBy: string[];
46
+ evidenceTier: EvidenceTier;
47
+ statement: string;
48
+ causationClaimed: boolean;
49
+ analysedAt: string;
50
+ }): void;
51
+ export declare function saveClaim(db: SqliteDatabase, row: Omit<ClaimRecord, "id" | "createdAt"> & {
52
+ id?: string | undefined;
53
+ createdAt?: string | undefined;
54
+ }): ClaimRecord;
55
+ export declare function listClaims(db: SqliteDatabase, siteId: string): ClaimRecord[];
56
+ export declare function claimForChange(db: SqliteDatabase, changeId: string): ClaimRecord | null;
57
+ export declare function listCohortUrls(db: SqliteDatabase, experimentId: string): {
58
+ treatment: string[];
59
+ control: string[];
60
+ reserve: string[];
61
+ };
62
+ export declare function overlappingAnomalies(db: SqliteDatabase, start: string, end: string): Array<{
63
+ id: string;
64
+ metrics: string[];
65
+ }>;
66
+ //# sourceMappingURL=persist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AA+BF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CA0B5D;AAED,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,IAAI,GACR,MAAM,CAoDR;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAGrF;AAED,wBAAgB,eAAe,CAC7B,EAAE,EAAE,cAAc,EAClB,MAAM,EAAE,MAAM,GACb,gBAAgB,EAAE,CAEpB;AAED,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,cAAc,EAClB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACzC,GACA,IAAI,CASN;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,EACzB,cAAc,EAAE,MAAM,GACrB,IAAI,CAGN;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,cAAc,EAClB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE;IACH,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB,GACA,IAAI,CA+BN;AAED,wBAAgB,SAAS,CACvB,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG;IAC3C,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,GACA,WAAW,CA6Bb;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAkB5E;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,MAAM,GACf,WAAW,GAAG,IAAI,CAepB;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,cAAc,EAClB,YAAY,EAAE,MAAM,GACnB;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAuB/D;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAa1C"}
@@ -0,0 +1,265 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { eq } from "drizzle-orm";
3
+ import { claims, cohortMembers, cohorts, dataAnomalies, experimentResults, experiments, } from "@agentsean/db";
4
+ import { PlannedEndImmutableError } from "./peek.js";
5
+ const HARDCODED_ANOMALIES = [
6
+ {
7
+ id: "gsc-impressions-logging-error-2025",
8
+ description: "GSC impressions logging error. Clicks unaffected.",
9
+ startDate: "2025-05-13",
10
+ endDate: "2026-04-27",
11
+ metrics: ["impressions", "ctr", "position"],
12
+ surfaces: ["web"],
13
+ source: "google_official",
14
+ },
15
+ {
16
+ id: "gsc-num100-removal-2025",
17
+ description: "&num=100 parameter removed. Impression/position step-change.",
18
+ startDate: "2025-09-12",
19
+ endDate: "2025-09-12",
20
+ metrics: ["impressions", "position"],
21
+ surfaces: ["web"],
22
+ source: "hardcoded",
23
+ },
24
+ ];
25
+ export function seedDataAnomalies(db) {
26
+ let n = 0;
27
+ for (const row of HARDCODED_ANOMALIES) {
28
+ const existing = db
29
+ .select()
30
+ .from(dataAnomalies)
31
+ .where(eq(dataAnomalies.id, row.id))
32
+ .get();
33
+ const values = {
34
+ description: row.description,
35
+ startDate: row.startDate,
36
+ endDate: row.endDate,
37
+ affectedMetrics: JSON.stringify(row.metrics),
38
+ affectedSurfaces: JSON.stringify(row.surfaces),
39
+ source: row.source,
40
+ };
41
+ if (existing) {
42
+ db.update(dataAnomalies).set(values).where(eq(dataAnomalies.id, row.id)).run();
43
+ }
44
+ else {
45
+ db.insert(dataAnomalies)
46
+ .values({ id: row.id, ...values })
47
+ .run();
48
+ }
49
+ n++;
50
+ }
51
+ return n;
52
+ }
53
+ export function insertExperiment(db, spec, plannedMde, status, now) {
54
+ const id = randomUUID();
55
+ const ts = now.toISOString();
56
+ db.insert(experiments)
57
+ .values({
58
+ id,
59
+ siteId: spec.siteId,
60
+ hypothesis: spec.hypothesis,
61
+ interventionKind: spec.interventionKind,
62
+ status,
63
+ design: spec.design,
64
+ unit: spec.unit,
65
+ randomisationSeed: spec.randomisationSeed ?? 1,
66
+ clusterKey: spec.clusterKey ?? null,
67
+ preStart: spec.preStart,
68
+ preEnd: spec.preEnd,
69
+ postStart: spec.postStart,
70
+ plannedEnd: spec.plannedEnd,
71
+ primaryMetric: spec.primaryMetric ?? "clicks",
72
+ secondaryMetric: null,
73
+ plannedMde,
74
+ powerTarget: spec.powerTarget ?? 0.8,
75
+ alpha: spec.alpha ?? 0.05,
76
+ evidenceTier: null,
77
+ peekingBlocked: 1,
78
+ createdAt: ts,
79
+ concludedAt: null,
80
+ })
81
+ .run();
82
+ const arms = [
83
+ { arm: "treatment", urls: spec.treatmentUrls },
84
+ { arm: "control", urls: spec.controlUrls },
85
+ { arm: "reserve", urls: spec.reserveUrls ?? [] },
86
+ ];
87
+ for (const a of arms) {
88
+ const cohortId = randomUUID();
89
+ db.insert(cohorts).values({ id: cohortId, experimentId: id, arm: a.arm }).run();
90
+ for (const url of a.urls) {
91
+ db.insert(cohortMembers)
92
+ .values({
93
+ id: randomUUID(),
94
+ cohortId,
95
+ url,
96
+ preClicks: spec.preClicks?.[url] ?? 0,
97
+ preImpressions: spec.preImpressions?.[url] ?? 0,
98
+ contentHashAtStart: spec.contentHashAtStart?.[url] ?? null,
99
+ })
100
+ .run();
101
+ }
102
+ }
103
+ return id;
104
+ }
105
+ export function getExperiment(db, id) {
106
+ const row = db.select().from(experiments).where(eq(experiments.id, id)).get();
107
+ return row ?? null;
108
+ }
109
+ export function listExperiments(db, siteId) {
110
+ return db.select().from(experiments).where(eq(experiments.siteId, siteId)).all();
111
+ }
112
+ export function setExperimentStatus(db, id, status, extra) {
113
+ const patch = { status };
114
+ if (extra && "evidenceTier" in extra)
115
+ patch.evidenceTier = extra.evidenceTier ?? null;
116
+ if (extra && "concludedAt" in extra)
117
+ patch.concludedAt = extra.concludedAt ?? null;
118
+ db.update(experiments).set(patch).where(eq(experiments.id, id)).run();
119
+ }
120
+ export function assertPlannedEndImmutable(current, nextPlannedEnd) {
121
+ if (current.status === "planned")
122
+ return;
123
+ if (current.plannedEnd !== nextPlannedEnd)
124
+ throw new PlannedEndImmutableError();
125
+ }
126
+ export function saveResult(db, experimentId, row) {
127
+ const existing = db
128
+ .select()
129
+ .from(experimentResults)
130
+ .where(eq(experimentResults.experimentId, experimentId))
131
+ .get();
132
+ const values = {
133
+ metric: row.metric,
134
+ pointEstimate: row.pointEstimate,
135
+ ciLow: row.ciLow,
136
+ ciHigh: row.ciHigh,
137
+ ciLevel: row.ciLevel,
138
+ probPositive: row.probPositive,
139
+ realisedMde: row.realisedMde,
140
+ nBoot: row.nBoot,
141
+ suppressedBy: JSON.stringify(row.suppressedBy),
142
+ evidenceTier: row.evidenceTier,
143
+ statement: row.statement,
144
+ causationClaimed: row.causationClaimed ? 1 : 0,
145
+ analysedAt: row.analysedAt,
146
+ };
147
+ if (existing) {
148
+ db.update(experimentResults)
149
+ .set(values)
150
+ .where(eq(experimentResults.experimentId, experimentId))
151
+ .run();
152
+ return;
153
+ }
154
+ db.insert(experimentResults)
155
+ .values({ experimentId, ...values })
156
+ .run();
157
+ }
158
+ export function saveClaim(db, row) {
159
+ const id = row.id ?? randomUUID();
160
+ const createdAt = row.createdAt ?? new Date().toISOString();
161
+ const values = {
162
+ siteId: row.siteId,
163
+ changeId: row.changeId,
164
+ experimentId: row.experimentId,
165
+ evidenceTier: row.evidenceTier,
166
+ statement: row.statement,
167
+ metric: row.metric,
168
+ causationClaimed: row.causationClaimed ? 1 : 0,
169
+ refusedReason: row.refusedReason,
170
+ createdAt,
171
+ };
172
+ db.insert(claims)
173
+ .values({ id, ...values })
174
+ .run();
175
+ return {
176
+ id,
177
+ siteId: row.siteId,
178
+ changeId: row.changeId,
179
+ experimentId: row.experimentId,
180
+ evidenceTier: row.evidenceTier,
181
+ statement: row.statement,
182
+ metric: row.metric,
183
+ causationClaimed: row.causationClaimed,
184
+ refusedReason: row.refusedReason,
185
+ createdAt,
186
+ };
187
+ }
188
+ export function listClaims(db, siteId) {
189
+ return db
190
+ .select()
191
+ .from(claims)
192
+ .where(eq(claims.siteId, siteId))
193
+ .all()
194
+ .map((r) => ({
195
+ id: r.id,
196
+ siteId: r.siteId,
197
+ changeId: r.changeId,
198
+ experimentId: r.experimentId,
199
+ evidenceTier: r.evidenceTier,
200
+ statement: r.statement,
201
+ metric: r.metric,
202
+ causationClaimed: r.causationClaimed === 1,
203
+ refusedReason: r.refusedReason,
204
+ createdAt: r.createdAt,
205
+ }));
206
+ }
207
+ export function claimForChange(db, changeId) {
208
+ const row = db.select().from(claims).where(eq(claims.changeId, changeId)).get();
209
+ if (!row)
210
+ return null;
211
+ return {
212
+ id: row.id,
213
+ siteId: row.siteId,
214
+ changeId: row.changeId,
215
+ experimentId: row.experimentId,
216
+ evidenceTier: row.evidenceTier,
217
+ statement: row.statement,
218
+ metric: row.metric,
219
+ causationClaimed: row.causationClaimed === 1,
220
+ refusedReason: row.refusedReason,
221
+ createdAt: row.createdAt,
222
+ };
223
+ }
224
+ export function listCohortUrls(db, experimentId) {
225
+ const rows = db
226
+ .select()
227
+ .from(cohorts)
228
+ .where(eq(cohorts.experimentId, experimentId))
229
+ .all();
230
+ const out = {
231
+ treatment: [],
232
+ control: [],
233
+ reserve: [],
234
+ };
235
+ for (const c of rows) {
236
+ const members = db
237
+ .select()
238
+ .from(cohortMembers)
239
+ .where(eq(cohortMembers.cohortId, c.id))
240
+ .all();
241
+ const urls = members.map((m) => m.url);
242
+ if (c.arm === "treatment")
243
+ out.treatment.push(...urls);
244
+ else if (c.arm === "control")
245
+ out.control.push(...urls);
246
+ else if (c.arm === "reserve")
247
+ out.reserve.push(...urls);
248
+ }
249
+ return out;
250
+ }
251
+ export function overlappingAnomalies(db, start, end) {
252
+ return db
253
+ .select()
254
+ .from(dataAnomalies)
255
+ .all()
256
+ .filter((r) => {
257
+ const e = r.endDate ?? r.startDate;
258
+ return r.startDate <= end && e >= start;
259
+ })
260
+ .map((r) => ({
261
+ id: r.id,
262
+ metrics: JSON.parse(r.affectedMetrics),
263
+ }));
264
+ }
265
+ //# sourceMappingURL=persist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persist.js","sourceRoot":"","sources":["../src/persist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { type PowerBrief } from "./types.js";
2
+ /** PLAN / gap-04 Monte-Carlo floors. Traffic does not move pre/post MDE. */
3
+ export declare const PRE_POST_MDE_28D = 0.8;
4
+ export declare const PRE_POST_MDE_56D = 0.55;
5
+ export declare const PRE_POST_MDE_91D = 0.41;
6
+ /** 200 pages, 2,000 clicks/mo, 100 vs 100, 56 days → ~18% (PLAN). */
7
+ export declare const SPLIT_MDE_2K_56D = 0.18;
8
+ /** Daily peeking vs fixed-horizon null false-positive rate. */
9
+ export declare const PEEKING_FP_FIXED = 0.047;
10
+ export declare const PEEKING_FP_DAILY = 0.229;
11
+ /** Naive α=0.05 tests: P(≥1 false win) at 20 changes/month. */
12
+ export declare const FABRICATED_WIN_20 = 0.64;
13
+ export declare const SEARCHPILOT_MIN_SESSIONS = 30000;
14
+ export declare const SEMRUSH_MIN_CLICKS_100D = 100000;
15
+ export declare const DEFAULT_WINDOW_DAYS = 56;
16
+ export declare const DEFAULT_SPLIT = 0.5;
17
+ /** Pre/post MDE is a function of window length, not traffic. */
18
+ export declare function prePostMde(windowDays: number): number;
19
+ export declare function clicksPerArmInWindow(monthlyClicks: number, windowDays: number, pagesPerArm: number, sitePages: number): number;
20
+ /**
21
+ * Concurrent-control MDE. Binding constraints are clicks/arm and pages/arm;
22
+ * the floor is ~11% even at huge traffic with only 100 pages/arm.
23
+ */
24
+ export declare function splitMde(opts: {
25
+ monthlyClicks: number;
26
+ pageCount: number;
27
+ windowDays?: number | undefined;
28
+ pagesPerArm?: number | undefined;
29
+ }): number;
30
+ export declare function naiveFalseWinProbability(testsPerMonth: number, alpha?: number): number;
31
+ export declare function sitePowerBrief(opts: {
32
+ monthlyClicks: number;
33
+ pageCount: number;
34
+ windowDays?: number | undefined;
35
+ }): PowerBrief;
36
+ //# sourceMappingURL=power.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power.d.ts","sourceRoot":"","sources":["../src/power.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGpE,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AACtC,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAC/C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,aAAa,MAAM,CAAC;AAsCjC,gEAAgE;AAChE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,MAAM,CAoBT;AAED,wBAAgB,wBAAwB,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,SAAO,GAAG,MAAM,CAGpF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,GAAG,UAAU,CA8Bb"}