crewly 1.20.193 → 1.20.194
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.
- package/config/skills/agent/core/experiment-card/SKILL.md +96 -0
- package/config/skills/agent/core/experiment-card/execute.sh +147 -0
- package/config/skills/agent/core/experiment-card/execute.test.sh +91 -0
- package/config/skills/registry.json +6 -5
- package/dist/backend/backend/src/constants.d.ts +58 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +61 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.d.ts +27 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.js +96 -0
- package/dist/backend/backend/src/controllers/experiments/experiments.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +39 -3
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.js +3 -0
- package/dist/backend/backend/src/services/agent/auditor-scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/canva/canva-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/canva/canva-token.service.js +5 -0
- package/dist/backend/backend/src/services/canva/canva-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +14 -3
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +28 -4
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/experiments/experiment-verdict.d.ts +88 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.js +226 -0
- package/dist/backend/backend/src/services/experiments/experiment-verdict.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.d.ts +301 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.js +754 -0
- package/dist/backend/backend/src/services/experiments/experiment.service.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.d.ts +73 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.js +142 -0
- package/dist/backend/backend/src/services/experiments/experiment.wiring.js.map +1 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.d.ts +60 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.d.ts.map +1 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.js +113 -0
- package/dist/backend/backend/src/services/experiments/seo-ops-metric.fetcher.js.map +1 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +5 -0
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts +11 -3
- package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js +24 -7
- package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.js +5 -0
- package/dist/backend/backend/src/services/microsoft/microsoft-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-status-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-status-router.service.js +3 -2
- package/dist/backend/backend/src/services/orc/orc-status-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/people/acting-for.service.d.ts +42 -5
- package/dist/backend/backend/src/services/people/acting-for.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/people/acting-for.service.js +80 -5
- package/dist/backend/backend/src/services/people/acting-for.service.js.map +1 -1
- package/dist/backend/backend/src/services/people/grant-sharing.d.ts +5 -4
- package/dist/backend/backend/src/services/people/grant-sharing.d.ts.map +1 -1
- package/dist/backend/backend/src/services/people/grant-sharing.js +12 -4
- package/dist/backend/backend/src/services/people/grant-sharing.js.map +1 -1
- package/dist/backend/backend/src/services/people/people-directory.service.d.ts +71 -13
- package/dist/backend/backend/src/services/people/people-directory.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/people/people-directory.service.js +164 -27
- package/dist/backend/backend/src/services/people/people-directory.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-bot-ids.d.ts +33 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.js +56 -0
- package/dist/backend/backend/src/services/slack/slack-bot-ids.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +12 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +27 -8
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +6 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +8 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js +3 -0
- package/dist/backend/backend/src/services/workflow/message-scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/scheduler.service.js +3 -0
- package/dist/backend/backend/src/services/workflow/scheduler.service.js.map +1 -1
- package/dist/backend/backend/src/types/experiment.types.d.ts +153 -0
- package/dist/backend/backend/src/types/experiment.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/experiment.types.js +32 -0
- package/dist/backend/backend/src/types/experiment.types.js.map +1 -0
- package/dist/backend/backend/src/types/messaging.types.d.ts +6 -0
- package/dist/backend/backend/src/types/messaging.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/messaging.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +58 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +61 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js +3 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +14 -3
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +28 -4
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +11 -3
- package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js +24 -7
- package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -1
- package/dist/cli/backend/src/services/people/acting-for.service.d.ts +42 -5
- package/dist/cli/backend/src/services/people/acting-for.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/people/acting-for.service.js +80 -5
- package/dist/cli/backend/src/services/people/acting-for.service.js.map +1 -1
- package/dist/cli/backend/src/services/people/people-directory.service.d.ts +71 -13
- package/dist/cli/backend/src/services/people/people-directory.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/people/people-directory.service.js +164 -27
- package/dist/cli/backend/src/services/people/people-directory.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +12 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +27 -8
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +6 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +8 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts +3 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/wiki/wiki-ingest.service.js.map +1 -1
- package/dist/cli/backend/src/types/messaging.types.d.ts +6 -0
- package/dist/cli/backend/src/types/messaging.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/messaging.types.js.map +1 -1
- package/frontend/dist/assets/{index-465d1f8f.js → index-800118ca.js} +274 -274
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment windows and verdicts (issue #986).
|
|
3
|
+
*
|
|
4
|
+
* Windows: the baseline is the `windowDays` days that had settled when the
|
|
5
|
+
* change shipped (ending `lag` days before the ship day); the observation
|
|
6
|
+
* window is the `windowDays` days after the ship day (the ship day itself is
|
|
7
|
+
* a partial day and is left out). The result is due once the observation
|
|
8
|
+
* window's last day has settled (`lag` days later).
|
|
9
|
+
*
|
|
10
|
+
* Verdicts use simple rules with a minimum volume:
|
|
11
|
+
* - counts (clicks, impressions, sessions, events): equal-length Poisson
|
|
12
|
+
* comparison, z = (b − a) / √(a + b), with at least MIN_COUNT_VOLUME events
|
|
13
|
+
* across both windows;
|
|
14
|
+
* - ctr: two-proportion z-test, with enough impressions in each window and
|
|
15
|
+
* enough clicks overall;
|
|
16
|
+
* - position: Welch's t on the daily positions, with enough days that had
|
|
17
|
+
* impressions in each window (lower is better).
|
|
18
|
+
* |z| ≥ SIGNIFICANCE_Z in the hypothesis's direction → worked; enough volume
|
|
19
|
+
* but no such change → didn't; too little volume → inconclusive.
|
|
20
|
+
*
|
|
21
|
+
* specs/experiment-cards.md
|
|
22
|
+
*
|
|
23
|
+
* @module services/experiments/experiment-verdict
|
|
24
|
+
*/
|
|
25
|
+
import type { ExperimentDirection, ExperimentMeasure, ExperimentSource, ExperimentVerdict, Measurement } from '../../types/experiment.types.js';
|
|
26
|
+
/** A date range (inclusive, YYYY-MM-DD). */
|
|
27
|
+
export interface DateRange {
|
|
28
|
+
start: string;
|
|
29
|
+
end: string;
|
|
30
|
+
}
|
|
31
|
+
/** The two windows of an experiment and when it can be measured. */
|
|
32
|
+
export interface ExperimentWindows {
|
|
33
|
+
baseline: DateRange;
|
|
34
|
+
observation: DateRange;
|
|
35
|
+
/** ISO time the result can be fetched (observation end + lag, start of the next day UTC) */
|
|
36
|
+
dueAt: string;
|
|
37
|
+
}
|
|
38
|
+
/** A verdict with its numbers. */
|
|
39
|
+
export interface VerdictOutcome {
|
|
40
|
+
verdict: ExperimentVerdict;
|
|
41
|
+
/** One line with the numbers */
|
|
42
|
+
reason: string;
|
|
43
|
+
/** Test statistic (z or Welch t), when there was enough volume to compute it */
|
|
44
|
+
statistic?: number;
|
|
45
|
+
/** Relative change of the headline value (null when the baseline is 0 / unknown) */
|
|
46
|
+
lift?: number | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Baseline / observation windows and the due time for a shipped experiment.
|
|
50
|
+
*
|
|
51
|
+
* @param shippedAt - When the change went live (ISO)
|
|
52
|
+
* @param windowDays - Days per window
|
|
53
|
+
* @param source - Metric source (sets the lag)
|
|
54
|
+
* @returns Windows
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* experimentWindows('2026-10-10T15:00:00Z', 14, 'gsc');
|
|
59
|
+
* // baseline 2026-09-24..2026-10-07, observation 2026-10-11..2026-10-24, due 2026-10-28
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function experimentWindows(shippedAt: string, windowDays: number, source: ExperimentSource): ExperimentWindows;
|
|
63
|
+
/**
|
|
64
|
+
* The direction a hypothesis means when it doesn't say: lower is better for position.
|
|
65
|
+
*
|
|
66
|
+
* @param measure - Measure
|
|
67
|
+
* @returns Default direction
|
|
68
|
+
*/
|
|
69
|
+
export declare function defaultDirection(measure: ExperimentMeasure): ExperimentDirection;
|
|
70
|
+
/**
|
|
71
|
+
* Format a number for a verdict line.
|
|
72
|
+
*
|
|
73
|
+
* @param v - Value
|
|
74
|
+
* @param measure - Measure (ctr as a percentage, position to one decimal)
|
|
75
|
+
* @returns Text
|
|
76
|
+
*/
|
|
77
|
+
export declare function formatValue(v: number | null | undefined, measure: ExperimentMeasure): string;
|
|
78
|
+
/**
|
|
79
|
+
* Label a measured experiment.
|
|
80
|
+
*
|
|
81
|
+
* @param measure - What was measured
|
|
82
|
+
* @param direction - Which way the hypothesis says it moves
|
|
83
|
+
* @param baseline - Baseline window
|
|
84
|
+
* @param result - Observation window
|
|
85
|
+
* @returns Verdict, one-line reason and the statistic
|
|
86
|
+
*/
|
|
87
|
+
export declare function decideVerdict(measure: ExperimentMeasure, direction: ExperimentDirection, baseline: Measurement, result: Measurement): VerdictOutcome;
|
|
88
|
+
//# sourceMappingURL=experiment-verdict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment-verdict.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/experiments/experiment-verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAIhJ,4CAA4C;AAC5C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,oEAAoE;AACpE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,EAAE,SAAS,CAAC;IACvB,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAyBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,CAWpH;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,mBAAmB,CAEhF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAK5F;AA+DD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,mBAAmB,EAC9B,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,WAAW,GAClB,cAAc,CA2DhB"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment windows and verdicts (issue #986).
|
|
3
|
+
*
|
|
4
|
+
* Windows: the baseline is the `windowDays` days that had settled when the
|
|
5
|
+
* change shipped (ending `lag` days before the ship day); the observation
|
|
6
|
+
* window is the `windowDays` days after the ship day (the ship day itself is
|
|
7
|
+
* a partial day and is left out). The result is due once the observation
|
|
8
|
+
* window's last day has settled (`lag` days later).
|
|
9
|
+
*
|
|
10
|
+
* Verdicts use simple rules with a minimum volume:
|
|
11
|
+
* - counts (clicks, impressions, sessions, events): equal-length Poisson
|
|
12
|
+
* comparison, z = (b − a) / √(a + b), with at least MIN_COUNT_VOLUME events
|
|
13
|
+
* across both windows;
|
|
14
|
+
* - ctr: two-proportion z-test, with enough impressions in each window and
|
|
15
|
+
* enough clicks overall;
|
|
16
|
+
* - position: Welch's t on the daily positions, with enough days that had
|
|
17
|
+
* impressions in each window (lower is better).
|
|
18
|
+
* |z| ≥ SIGNIFICANCE_Z in the hypothesis's direction → worked; enough volume
|
|
19
|
+
* but no such change → didn't; too little volume → inconclusive.
|
|
20
|
+
*
|
|
21
|
+
* specs/experiment-cards.md
|
|
22
|
+
*
|
|
23
|
+
* @module services/experiments/experiment-verdict
|
|
24
|
+
*/
|
|
25
|
+
import { EXPERIMENT_CONSTANTS } from '../../constants.js';
|
|
26
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
27
|
+
/**
|
|
28
|
+
* Format a UTC date as YYYY-MM-DD.
|
|
29
|
+
*
|
|
30
|
+
* @param ms - Epoch millis
|
|
31
|
+
* @returns Day string
|
|
32
|
+
*/
|
|
33
|
+
function day(ms) {
|
|
34
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Midnight UTC of the day an instant falls on.
|
|
38
|
+
*
|
|
39
|
+
* @param iso - ISO time
|
|
40
|
+
* @returns Epoch millis
|
|
41
|
+
* @throws Error on an invalid time
|
|
42
|
+
*/
|
|
43
|
+
function dayStart(iso) {
|
|
44
|
+
const t = Date.parse(iso);
|
|
45
|
+
if (Number.isNaN(t))
|
|
46
|
+
throw new Error(`Invalid time: ${iso}`);
|
|
47
|
+
return Math.floor(t / DAY_MS) * DAY_MS;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Baseline / observation windows and the due time for a shipped experiment.
|
|
51
|
+
*
|
|
52
|
+
* @param shippedAt - When the change went live (ISO)
|
|
53
|
+
* @param windowDays - Days per window
|
|
54
|
+
* @param source - Metric source (sets the lag)
|
|
55
|
+
* @returns Windows
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* experimentWindows('2026-10-10T15:00:00Z', 14, 'gsc');
|
|
60
|
+
* // baseline 2026-09-24..2026-10-07, observation 2026-10-11..2026-10-24, due 2026-10-28
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function experimentWindows(shippedAt, windowDays, source) {
|
|
64
|
+
const ship = dayStart(shippedAt);
|
|
65
|
+
const lag = EXPERIMENT_CONSTANTS.SOURCE_LAG_DAYS[source] ?? 0;
|
|
66
|
+
const baseEnd = ship - lag * DAY_MS;
|
|
67
|
+
const obsStart = ship + DAY_MS;
|
|
68
|
+
const obsEnd = ship + windowDays * DAY_MS;
|
|
69
|
+
return {
|
|
70
|
+
baseline: { start: day(baseEnd - (windowDays - 1) * DAY_MS), end: day(baseEnd) },
|
|
71
|
+
observation: { start: day(obsStart), end: day(obsEnd) },
|
|
72
|
+
dueAt: new Date(obsEnd + (lag + 1) * DAY_MS).toISOString(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The direction a hypothesis means when it doesn't say: lower is better for position.
|
|
77
|
+
*
|
|
78
|
+
* @param measure - Measure
|
|
79
|
+
* @returns Default direction
|
|
80
|
+
*/
|
|
81
|
+
export function defaultDirection(measure) {
|
|
82
|
+
return EXPERIMENT_CONSTANTS.LOWER_IS_BETTER.includes(measure) ? 'decrease' : 'increase';
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Format a number for a verdict line.
|
|
86
|
+
*
|
|
87
|
+
* @param v - Value
|
|
88
|
+
* @param measure - Measure (ctr as a percentage, position to one decimal)
|
|
89
|
+
* @returns Text
|
|
90
|
+
*/
|
|
91
|
+
export function formatValue(v, measure) {
|
|
92
|
+
if (v === null || v === undefined || !Number.isFinite(v))
|
|
93
|
+
return 'n/a';
|
|
94
|
+
if (measure === 'ctr')
|
|
95
|
+
return `${(v * 100).toFixed(2)}%`;
|
|
96
|
+
if (measure === 'position')
|
|
97
|
+
return v.toFixed(1);
|
|
98
|
+
return String(Math.round(v * 100) / 100);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Relative change.
|
|
102
|
+
*
|
|
103
|
+
* @param from - Baseline
|
|
104
|
+
* @param to - Result
|
|
105
|
+
* @returns (to − from) / from, or null when from is 0 / missing
|
|
106
|
+
*/
|
|
107
|
+
function liftOf(from, to) {
|
|
108
|
+
if (from === null || from === undefined || to === null || to === undefined || from === 0)
|
|
109
|
+
return null;
|
|
110
|
+
return (to - from) / from;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* "+40%" / "−12%" / "".
|
|
114
|
+
*
|
|
115
|
+
* @param lift - Relative change
|
|
116
|
+
* @returns Text with a leading space, or empty
|
|
117
|
+
*/
|
|
118
|
+
function liftText(lift) {
|
|
119
|
+
if (lift === null)
|
|
120
|
+
return '';
|
|
121
|
+
const pct = Math.round(lift * 1000) / 10;
|
|
122
|
+
return ` (${pct >= 0 ? '+' : ''}${pct}%)`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Label from a statistic: worked when it is significant in the hypothesis's direction.
|
|
126
|
+
*
|
|
127
|
+
* @param stat - z or t (positive = the metric rose)
|
|
128
|
+
* @param direction - Hypothesis direction
|
|
129
|
+
* @returns worked / didnt and the wording
|
|
130
|
+
*/
|
|
131
|
+
function judge(stat, direction) {
|
|
132
|
+
const z = EXPERIMENT_CONSTANTS.SIGNIFICANCE_Z;
|
|
133
|
+
const wanted = direction === 'increase' ? stat >= z : stat <= -z;
|
|
134
|
+
if (wanted)
|
|
135
|
+
return { verdict: 'worked', words: `a real ${direction}` };
|
|
136
|
+
const wrongWay = direction === 'increase' ? stat <= -z : stat >= z;
|
|
137
|
+
return { verdict: 'didnt', words: wrongWay ? 'it moved the wrong way' : 'no real change' };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Daily values of the days that had volume.
|
|
141
|
+
*
|
|
142
|
+
* @param m - Measurement
|
|
143
|
+
* @returns Values
|
|
144
|
+
*/
|
|
145
|
+
function dailyValues(m) {
|
|
146
|
+
return m.days.filter((d) => d.volume > 0 && d.value !== null && Number.isFinite(d.value)).map((d) => d.value);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Mean and sample variance.
|
|
150
|
+
*
|
|
151
|
+
* @param xs - Values (at least 2)
|
|
152
|
+
* @returns Mean and variance
|
|
153
|
+
*/
|
|
154
|
+
function meanVar(xs) {
|
|
155
|
+
const mean = xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
156
|
+
const variance = xs.reduce((a, b) => a + (b - mean) ** 2, 0) / Math.max(1, xs.length - 1);
|
|
157
|
+
return { mean, variance };
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Label a measured experiment.
|
|
161
|
+
*
|
|
162
|
+
* @param measure - What was measured
|
|
163
|
+
* @param direction - Which way the hypothesis says it moves
|
|
164
|
+
* @param baseline - Baseline window
|
|
165
|
+
* @param result - Observation window
|
|
166
|
+
* @returns Verdict, one-line reason and the statistic
|
|
167
|
+
*/
|
|
168
|
+
export function decideVerdict(measure, direction, baseline, result) {
|
|
169
|
+
const C = EXPERIMENT_CONSTANTS;
|
|
170
|
+
if (measure === 'ctr') {
|
|
171
|
+
const n1 = baseline.impressions ?? baseline.volume;
|
|
172
|
+
const n2 = result.impressions ?? result.volume;
|
|
173
|
+
const c1 = baseline.clicks ?? 0;
|
|
174
|
+
const c2 = result.clicks ?? 0;
|
|
175
|
+
const head = `CTR ${formatValue(n1 ? c1 / n1 : null, measure)} → ${formatValue(n2 ? c2 / n2 : null, measure)}`;
|
|
176
|
+
const lift = liftOf(n1 ? c1 / n1 : null, n2 ? c2 / n2 : null);
|
|
177
|
+
if (n1 < C.MIN_IMPRESSIONS_PER_WINDOW || n2 < C.MIN_IMPRESSIONS_PER_WINDOW || c1 + c2 < C.MIN_CTR_CLICKS) {
|
|
178
|
+
return {
|
|
179
|
+
verdict: 'inconclusive',
|
|
180
|
+
reason: `${head}: too little data (${n1} / ${n2} impressions, ${c1 + c2} clicks; need ${C.MIN_IMPRESSIONS_PER_WINDOW} impressions per window and ${C.MIN_CTR_CLICKS} clicks)`,
|
|
181
|
+
lift,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const p = (c1 + c2) / (n1 + n2);
|
|
185
|
+
const se = Math.sqrt(p * (1 - p) * (1 / n1 + 1 / n2));
|
|
186
|
+
const stat = se > 0 ? (c2 / n2 - c1 / n1) / se : 0;
|
|
187
|
+
const j = judge(stat, direction);
|
|
188
|
+
return { verdict: j.verdict, reason: `${head}${liftText(lift)}, z = ${stat.toFixed(2)}: ${j.words}`, statistic: stat, lift };
|
|
189
|
+
}
|
|
190
|
+
if (measure === 'position') {
|
|
191
|
+
const a = dailyValues(baseline);
|
|
192
|
+
const b = dailyValues(result);
|
|
193
|
+
const head = `Average position ${formatValue(baseline.total, measure)} → ${formatValue(result.total, measure)}`;
|
|
194
|
+
const lift = liftOf(baseline.total, result.total);
|
|
195
|
+
const imps1 = baseline.impressions ?? baseline.volume;
|
|
196
|
+
const imps2 = result.impressions ?? result.volume;
|
|
197
|
+
if (a.length < C.MIN_POSITION_DAYS || b.length < C.MIN_POSITION_DAYS || imps1 < C.MIN_IMPRESSIONS_PER_WINDOW || imps2 < C.MIN_IMPRESSIONS_PER_WINDOW) {
|
|
198
|
+
return {
|
|
199
|
+
verdict: 'inconclusive',
|
|
200
|
+
reason: `${head}: too little data (${a.length} / ${b.length} days with impressions, ${imps1} / ${imps2} impressions; need ${C.MIN_POSITION_DAYS} days and ${C.MIN_IMPRESSIONS_PER_WINDOW} impressions per window)`,
|
|
201
|
+
lift,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const s1 = meanVar(a);
|
|
205
|
+
const s2 = meanVar(b);
|
|
206
|
+
const se = Math.sqrt(s1.variance / a.length + s2.variance / b.length);
|
|
207
|
+
const diff = s2.mean - s1.mean;
|
|
208
|
+
const stat = se > 0 ? diff / se : diff === 0 ? 0 : Math.sign(diff) * Infinity;
|
|
209
|
+
const j = judge(stat, direction);
|
|
210
|
+
const statText = Number.isFinite(stat) ? stat.toFixed(2) : stat > 0 ? '∞' : '−∞';
|
|
211
|
+
return { verdict: j.verdict, reason: `${head}, t = ${statText}: ${j.words}`, statistic: stat, lift };
|
|
212
|
+
}
|
|
213
|
+
// Counts: clicks, impressions, sessions, events.
|
|
214
|
+
const a = baseline.total ?? 0;
|
|
215
|
+
const b = result.total ?? 0;
|
|
216
|
+
const min = C.MIN_COUNT_VOLUME[measure] ?? 0;
|
|
217
|
+
const head = `${measure} ${formatValue(a, measure)} → ${formatValue(b, measure)}`;
|
|
218
|
+
const lift = liftOf(a, b);
|
|
219
|
+
if (a + b < min) {
|
|
220
|
+
return { verdict: 'inconclusive', reason: `${head}: too little data (${a + b} across both windows; need ${min})`, lift };
|
|
221
|
+
}
|
|
222
|
+
const stat = (b - a) / Math.sqrt(a + b);
|
|
223
|
+
const j = judge(stat, direction);
|
|
224
|
+
return { verdict: j.verdict, reason: `${head}${liftText(lift)}, z = ${stat.toFixed(2)}: ${j.words}`, statistic: stat, lift };
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=experiment-verdict.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment-verdict.js","sourceRoot":"","sources":["../../../../../../backend/src/services/experiments/experiment-verdict.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment cards (issue #986, epic #982).
|
|
3
|
+
*
|
|
4
|
+
* An optimisation ticket carries an experiment: a hypothesis ("change X →
|
|
5
|
+
* metric Y from a to b"), a metric (Search Console / GA4 through the seo-ops
|
|
6
|
+
* skill), a baseline captured when the change ships and an observation
|
|
7
|
+
* window (default 14 days). When the window has settled the harness fetches
|
|
8
|
+
* the metric, labels the result worked / didn't / inconclusive (simple
|
|
9
|
+
* significance rules plus a minimum volume, see experiment-verdict.ts),
|
|
10
|
+
* resolves the agent's prediction, appends the result to the wiki
|
|
11
|
+
* experiment log and tells the owner.
|
|
12
|
+
*
|
|
13
|
+
* The experiment is a trace root: `traceId` (`exp:EXP-n`) names the run, and
|
|
14
|
+
* the timeline records every step (created, shipped, baseline, each fetch
|
|
15
|
+
* failure, measured, logged, reported), so the whole run can be reviewed.
|
|
16
|
+
*
|
|
17
|
+
* State: `~/.crewly/experiments.json`. A tick (every 15 min) moves
|
|
18
|
+
* experiments forward, so restarts lose nothing.
|
|
19
|
+
*
|
|
20
|
+
* specs/experiment-cards.md
|
|
21
|
+
*
|
|
22
|
+
* @module services/experiments/experiment.service
|
|
23
|
+
*/
|
|
24
|
+
import { type ComponentLogger } from '../core/logger.service.js';
|
|
25
|
+
import { type Experiment, type ExperimentMetric, type ExperimentStatus, type ExperimentTicketLink } from '../../types/experiment.types.js';
|
|
26
|
+
import type { MetricFetcher } from './seo-ops-metric.fetcher.js';
|
|
27
|
+
/** A notice for the owner (same shape as the ticket autopilot's). */
|
|
28
|
+
export interface ExperimentOwnerNotice {
|
|
29
|
+
title: string;
|
|
30
|
+
message: string;
|
|
31
|
+
urgent: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The prediction calls the service makes (record-prediction / resolve-prediction). */
|
|
34
|
+
export interface ExperimentPredictions {
|
|
35
|
+
make(session: string, statement: string, confidence: number, resolveBy: string): Promise<{
|
|
36
|
+
id: string;
|
|
37
|
+
}>;
|
|
38
|
+
resolve(session: string, id: string, outcome: string, accurate: boolean): Promise<unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** Dependencies. */
|
|
41
|
+
export interface ExperimentServiceDeps {
|
|
42
|
+
/** JSON store */
|
|
43
|
+
storeFile: string;
|
|
44
|
+
/** Fetch a metric over a window (seo-ops) */
|
|
45
|
+
fetchMetric: MetricFetcher;
|
|
46
|
+
/** When the linked ticket shipped (ISO), or null while it has not */
|
|
47
|
+
ticketShippedAt?: (link: ExperimentTicketLink) => Promise<string | null>;
|
|
48
|
+
/** Note the experiment on its ticket (best effort) */
|
|
49
|
+
noteOnTicket?: (link: ExperimentTicketLink, note: string) => Promise<void>;
|
|
50
|
+
predictions?: ExperimentPredictions;
|
|
51
|
+
/** Append a result to the wiki experiment log; false = not written (no vault) */
|
|
52
|
+
writeLog?: (experiment: Experiment, entry: string) => Promise<boolean>;
|
|
53
|
+
/** Tell the owner; false = not sent (Slack not connected) */
|
|
54
|
+
notifyOwner?: (notice: ExperimentOwnerNotice) => Promise<boolean>;
|
|
55
|
+
/** Does a file exist (the seo-ops config) */
|
|
56
|
+
fileExists?: (file: string) => Promise<boolean>;
|
|
57
|
+
now?: () => Date;
|
|
58
|
+
logger?: ComponentLogger;
|
|
59
|
+
}
|
|
60
|
+
/** Input of {@link ExperimentService.create}. */
|
|
61
|
+
export interface CreateExperimentInput {
|
|
62
|
+
title?: unknown;
|
|
63
|
+
hypothesis?: unknown;
|
|
64
|
+
direction?: unknown;
|
|
65
|
+
expected?: unknown;
|
|
66
|
+
metric?: unknown;
|
|
67
|
+
windowDays?: unknown;
|
|
68
|
+
ticket?: unknown;
|
|
69
|
+
confidence?: unknown;
|
|
70
|
+
}
|
|
71
|
+
/** An error with an HTTP status. */
|
|
72
|
+
export declare class ExperimentError extends Error {
|
|
73
|
+
readonly status: number;
|
|
74
|
+
/**
|
|
75
|
+
* @param status - HTTP status
|
|
76
|
+
* @param message - What went wrong
|
|
77
|
+
*/
|
|
78
|
+
constructor(status: number, message: string);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validate a metric definition.
|
|
82
|
+
*
|
|
83
|
+
* @param raw - Body
|
|
84
|
+
* @returns Metric
|
|
85
|
+
* @throws ExperimentError(400) with what is wrong
|
|
86
|
+
*/
|
|
87
|
+
export declare function validateMetric(raw: unknown): ExperimentMetric;
|
|
88
|
+
/**
|
|
89
|
+
* Validate a ticket link.
|
|
90
|
+
*
|
|
91
|
+
* @param raw - Body
|
|
92
|
+
* @returns Link or undefined
|
|
93
|
+
* @throws ExperimentError(400)
|
|
94
|
+
*/
|
|
95
|
+
export declare function validateTicketLink(raw: unknown): ExperimentTicketLink | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* How a ticket link reads.
|
|
98
|
+
*
|
|
99
|
+
* @param link - Link
|
|
100
|
+
* @returns e.g. "ce-site/T-12" or "TKT-40"
|
|
101
|
+
*/
|
|
102
|
+
export declare function ticketLabel(link: ExperimentTicketLink): string;
|
|
103
|
+
/**
|
|
104
|
+
* The metric as one line.
|
|
105
|
+
*
|
|
106
|
+
* @param m - Metric
|
|
107
|
+
* @returns e.g. "gsc clicks · page https://… · query visa"
|
|
108
|
+
*/
|
|
109
|
+
export declare function metricLabel(m: ExperimentMetric): string;
|
|
110
|
+
/**
|
|
111
|
+
* The result as markdown (wiki log entry and owner message share it).
|
|
112
|
+
*
|
|
113
|
+
* @param e - A measured experiment
|
|
114
|
+
* @returns Markdown
|
|
115
|
+
*/
|
|
116
|
+
export declare function resultSummary(e: Experiment): string;
|
|
117
|
+
/**
|
|
118
|
+
* Experiment cards: store, lifecycle and the measuring tick.
|
|
119
|
+
*/
|
|
120
|
+
export declare class ExperimentService {
|
|
121
|
+
private readonly deps;
|
|
122
|
+
private static instance;
|
|
123
|
+
private readonly logger;
|
|
124
|
+
private readonly now;
|
|
125
|
+
private chain;
|
|
126
|
+
private timer;
|
|
127
|
+
/** A tick is running (fetches can be slow; ticks never overlap) */
|
|
128
|
+
private ticking;
|
|
129
|
+
/**
|
|
130
|
+
* @param deps - Dependencies
|
|
131
|
+
*/
|
|
132
|
+
constructor(deps: ExperimentServiceDeps);
|
|
133
|
+
/**
|
|
134
|
+
* The installed service (null before boot).
|
|
135
|
+
*
|
|
136
|
+
* @returns Service or null
|
|
137
|
+
*/
|
|
138
|
+
static getInstance(): ExperimentService | null;
|
|
139
|
+
/**
|
|
140
|
+
* Install the process service.
|
|
141
|
+
*
|
|
142
|
+
* @param service - Service, or null to clear
|
|
143
|
+
*/
|
|
144
|
+
static setInstance(service: ExperimentService | null): void;
|
|
145
|
+
/**
|
|
146
|
+
* Start the periodic tick (runs once right away).
|
|
147
|
+
*
|
|
148
|
+
* @param intervalMs - Interval (0 = no timer; tests call {@link tick})
|
|
149
|
+
*/
|
|
150
|
+
start(intervalMs?: number): void;
|
|
151
|
+
/** Stop the tick. */
|
|
152
|
+
stop(): void;
|
|
153
|
+
/**
|
|
154
|
+
* Run one store operation at a time.
|
|
155
|
+
*
|
|
156
|
+
* @param fn - Operation
|
|
157
|
+
* @returns Its result
|
|
158
|
+
*/
|
|
159
|
+
private serial;
|
|
160
|
+
/**
|
|
161
|
+
* Read the store.
|
|
162
|
+
*
|
|
163
|
+
* @returns Store data (empty when missing)
|
|
164
|
+
*/
|
|
165
|
+
private load;
|
|
166
|
+
/**
|
|
167
|
+
* Write the store atomically.
|
|
168
|
+
*
|
|
169
|
+
* @param data - Store data
|
|
170
|
+
*/
|
|
171
|
+
private save;
|
|
172
|
+
/**
|
|
173
|
+
* Load, change one experiment, save.
|
|
174
|
+
*
|
|
175
|
+
* @param id - Experiment id
|
|
176
|
+
* @param fn - Change (mutates the experiment)
|
|
177
|
+
* @returns The experiment after the change
|
|
178
|
+
* @throws ExperimentError(404) when not found
|
|
179
|
+
*/
|
|
180
|
+
private mutate;
|
|
181
|
+
/**
|
|
182
|
+
* Add a timeline entry.
|
|
183
|
+
*
|
|
184
|
+
* @param e - Experiment
|
|
185
|
+
* @param event - Event name
|
|
186
|
+
* @param detail - Optional detail
|
|
187
|
+
*/
|
|
188
|
+
private record;
|
|
189
|
+
/**
|
|
190
|
+
* All experiments, newest first.
|
|
191
|
+
*
|
|
192
|
+
* @param filter - Optional status / ticket filter
|
|
193
|
+
* @returns Experiments
|
|
194
|
+
*/
|
|
195
|
+
list(filter?: {
|
|
196
|
+
status?: string;
|
|
197
|
+
ticket?: string;
|
|
198
|
+
}): Promise<Experiment[]>;
|
|
199
|
+
/**
|
|
200
|
+
* One experiment.
|
|
201
|
+
*
|
|
202
|
+
* @param id - EXP-n
|
|
203
|
+
* @returns Experiment or null
|
|
204
|
+
*/
|
|
205
|
+
get(id: string): Promise<Experiment | null>;
|
|
206
|
+
/**
|
|
207
|
+
* Create an experiment card.
|
|
208
|
+
*
|
|
209
|
+
* @param input - Hypothesis, metric, window, ticket, confidence
|
|
210
|
+
* @param caller - Agent session, or `owner`
|
|
211
|
+
* @returns The new experiment (status planned)
|
|
212
|
+
* @throws ExperimentError(400) on invalid input
|
|
213
|
+
*/
|
|
214
|
+
create(input: CreateExperimentInput, caller: string): Promise<Experiment>;
|
|
215
|
+
/**
|
|
216
|
+
* Mark an experiment shipped: fix its windows, record the prediction and
|
|
217
|
+
* capture the baseline.
|
|
218
|
+
*
|
|
219
|
+
* @param id - EXP-n
|
|
220
|
+
* @param caller - Who shipped it
|
|
221
|
+
* @param shippedAt - When it went live (default now; not in the future)
|
|
222
|
+
* @returns The experiment
|
|
223
|
+
* @throws ExperimentError(404 / 409 / 400)
|
|
224
|
+
*/
|
|
225
|
+
ship(id: string, caller: string, shippedAt?: string): Promise<Experiment>;
|
|
226
|
+
/**
|
|
227
|
+
* Cancel an experiment that has not been measured.
|
|
228
|
+
*
|
|
229
|
+
* @param id - EXP-n
|
|
230
|
+
* @param caller - Who
|
|
231
|
+
* @param reason - Why
|
|
232
|
+
* @returns The experiment
|
|
233
|
+
* @throws ExperimentError(404 / 409)
|
|
234
|
+
*/
|
|
235
|
+
cancel(id: string, caller: string, reason?: string): Promise<Experiment>;
|
|
236
|
+
/**
|
|
237
|
+
* Measure a due experiment now (instead of waiting for the tick).
|
|
238
|
+
*
|
|
239
|
+
* @param id - EXP-n
|
|
240
|
+
* @returns The experiment
|
|
241
|
+
* @throws ExperimentError(404 / 409) when it is not running or not yet due
|
|
242
|
+
*/
|
|
243
|
+
measureNow(id: string): Promise<Experiment>;
|
|
244
|
+
/**
|
|
245
|
+
* Move every experiment forward: ship planned ones whose ticket shipped,
|
|
246
|
+
* retry missing baselines, measure due ones, finish follow-ups.
|
|
247
|
+
*
|
|
248
|
+
* @returns Counts of what happened (zeros when a tick is already running)
|
|
249
|
+
*/
|
|
250
|
+
tick(): Promise<{
|
|
251
|
+
shipped: number;
|
|
252
|
+
measured: number;
|
|
253
|
+
}>;
|
|
254
|
+
/**
|
|
255
|
+
* One pass over all experiments (see {@link tick}).
|
|
256
|
+
*
|
|
257
|
+
* @returns Counts of what happened
|
|
258
|
+
*/
|
|
259
|
+
private runTick;
|
|
260
|
+
/**
|
|
261
|
+
* Record the creating agent's prediction that the hypothesis holds.
|
|
262
|
+
*
|
|
263
|
+
* @param id - EXP-n
|
|
264
|
+
*/
|
|
265
|
+
private recordPrediction;
|
|
266
|
+
/**
|
|
267
|
+
* A fetch failed: count it, and tell the owner once when it keeps failing.
|
|
268
|
+
*
|
|
269
|
+
* @param id - EXP-n
|
|
270
|
+
* @param step - baseline / result
|
|
271
|
+
* @param err - The error
|
|
272
|
+
*/
|
|
273
|
+
private fetchFailed;
|
|
274
|
+
/**
|
|
275
|
+
* Fetch the baseline window.
|
|
276
|
+
*
|
|
277
|
+
* @param id - EXP-n
|
|
278
|
+
*/
|
|
279
|
+
private captureBaseline;
|
|
280
|
+
/**
|
|
281
|
+
* Fetch the observation window and label the result.
|
|
282
|
+
*
|
|
283
|
+
* @param id - EXP-n
|
|
284
|
+
*/
|
|
285
|
+
private measure;
|
|
286
|
+
/**
|
|
287
|
+
* After a result: resolve the prediction, write the wiki log, tell the
|
|
288
|
+
* owner. Each step is retried by later ticks until it lands.
|
|
289
|
+
*
|
|
290
|
+
* @param id - EXP-n
|
|
291
|
+
*/
|
|
292
|
+
private followUp;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Is this a status filter value?
|
|
296
|
+
*
|
|
297
|
+
* @param v - Query value
|
|
298
|
+
* @returns The status or undefined
|
|
299
|
+
*/
|
|
300
|
+
export declare function statusFilter(v: unknown): ExperimentStatus | undefined;
|
|
301
|
+
//# sourceMappingURL=experiment.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/experiments/experiment.service.ts"],"names":[],"mappings":"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"}
|