@mrtdown/core 2.0.0-alpha.5 → 2.0.0-alpha.7
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/dist/index.d.ts +0 -7
- package/dist/index.js +0 -7
- package/dist/index.js.map +1 -1
- package/dist/schema/Landmark.js.map +1 -1
- package/dist/schema/Line.d.ts +6 -0
- package/dist/schema/Line.js +2 -1
- package/dist/schema/Line.js.map +1 -1
- package/dist/schema/Operator.js.map +1 -1
- package/dist/schema/Service.js.map +1 -1
- package/dist/schema/Station.js.map +1 -1
- package/dist/schema/Town.js.map +1 -1
- package/dist/schema/common.js.map +1 -1
- package/dist/schema/issue/bundle.js.map +1 -1
- package/dist/schema/issue/cause.js.map +1 -1
- package/dist/schema/issue/claim.js.map +1 -1
- package/dist/schema/issue/entity.js.map +1 -1
- package/dist/schema/issue/evidence.js.map +1 -1
- package/dist/schema/issue/facilityEffect.js.map +1 -1
- package/dist/schema/issue/id.js.map +1 -1
- package/dist/schema/issue/impactEvent.js.map +1 -1
- package/dist/schema/issue/issue.js.map +1 -1
- package/dist/schema/issue/issueType.js.map +1 -1
- package/dist/schema/issue/period.js.map +1 -1
- package/dist/schema/issue/serviceEffect.js.map +1 -1
- package/dist/schema/issue/serviceScope.js.map +1 -1
- package/package.json +19 -44
- package/README.md +0 -107
- package/dist/cli/commands/create.d.ts +0 -30
- package/dist/cli/commands/create.js +0 -189
- package/dist/cli/commands/create.js.map +0 -1
- package/dist/cli/commands/list.d.ts +0 -6
- package/dist/cli/commands/list.js +0 -106
- package/dist/cli/commands/list.js.map +0 -1
- package/dist/cli/commands/show.d.ts +0 -6
- package/dist/cli/commands/show.js +0 -156
- package/dist/cli/commands/show.js.map +0 -1
- package/dist/cli/commands/validate.d.ts +0 -6
- package/dist/cli/commands/validate.js +0 -19
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -162
- package/dist/cli/index.js.map +0 -1
- package/dist/constants.d.ts +0 -10
- package/dist/constants.js +0 -11
- package/dist/constants.js.map +0 -1
- package/dist/helpers/calculateDurationWithinServiceHours.d.ts +0 -2
- package/dist/helpers/calculateDurationWithinServiceHours.js +0 -13
- package/dist/helpers/calculateDurationWithinServiceHours.js.map +0 -1
- package/dist/helpers/calculateDurationWithinServiceHours.test.d.ts +0 -1
- package/dist/helpers/calculateDurationWithinServiceHours.test.js +0 -83
- package/dist/helpers/calculateDurationWithinServiceHours.test.js.map +0 -1
- package/dist/helpers/computeImpactFromEvidenceClaims.d.ts +0 -21
- package/dist/helpers/computeImpactFromEvidenceClaims.js +0 -293
- package/dist/helpers/computeImpactFromEvidenceClaims.js.map +0 -1
- package/dist/helpers/computeImpactFromEvidenceClaims.test.d.ts +0 -1
- package/dist/helpers/computeImpactFromEvidenceClaims.test.js +0 -544
- package/dist/helpers/computeImpactFromEvidenceClaims.test.js.map +0 -1
- package/dist/helpers/computeStartOfDaysWithinInterval.d.ts +0 -2
- package/dist/helpers/computeStartOfDaysWithinInterval.js +0 -15
- package/dist/helpers/computeStartOfDaysWithinInterval.js.map +0 -1
- package/dist/helpers/computeStartOfDaysWithinInterval.test.d.ts +0 -1
- package/dist/helpers/computeStartOfDaysWithinInterval.test.js +0 -126
- package/dist/helpers/computeStartOfDaysWithinInterval.test.js.map +0 -1
- package/dist/helpers/estimateOpenAICost.d.ts +0 -40
- package/dist/helpers/estimateOpenAICost.js +0 -55
- package/dist/helpers/estimateOpenAICost.js.map +0 -1
- package/dist/helpers/keyForAffectedEntity.d.ts +0 -7
- package/dist/helpers/keyForAffectedEntity.js +0 -14
- package/dist/helpers/keyForAffectedEntity.js.map +0 -1
- package/dist/helpers/normalizeRecurringPeriod.d.ts +0 -7
- package/dist/helpers/normalizeRecurringPeriod.js +0 -118
- package/dist/helpers/normalizeRecurringPeriod.js.map +0 -1
- package/dist/helpers/normalizeRecurringPeriod.test.d.ts +0 -1
- package/dist/helpers/normalizeRecurringPeriod.test.js +0 -93
- package/dist/helpers/normalizeRecurringPeriod.test.js.map +0 -1
- package/dist/helpers/resolvePeriods.d.ts +0 -224
- package/dist/helpers/resolvePeriods.js +0 -207
- package/dist/helpers/resolvePeriods.js.map +0 -1
- package/dist/helpers/resolvePeriods.test.d.ts +0 -1
- package/dist/helpers/resolvePeriods.test.js +0 -239
- package/dist/helpers/resolvePeriods.test.js.map +0 -1
- package/dist/helpers/splitIntervalByServiceHours.d.ts +0 -2
- package/dist/helpers/splitIntervalByServiceHours.js +0 -30
- package/dist/helpers/splitIntervalByServiceHours.js.map +0 -1
- package/dist/helpers/splitIntervalByServiceHours.test.d.ts +0 -1
- package/dist/helpers/splitIntervalByServiceHours.test.js +0 -152
- package/dist/helpers/splitIntervalByServiceHours.test.js.map +0 -1
- package/dist/helpers/sumIntervalDuration.d.ts +0 -2
- package/dist/helpers/sumIntervalDuration.js +0 -9
- package/dist/helpers/sumIntervalDuration.js.map +0 -1
- package/dist/llm/client.d.ts +0 -2
- package/dist/llm/client.js +0 -5
- package/dist/llm/client.js.map +0 -1
- package/dist/llm/common/MemoryStore.d.ts +0 -21
- package/dist/llm/common/MemoryStore.js +0 -100
- package/dist/llm/common/MemoryStore.js.map +0 -1
- package/dist/llm/common/MemoryStore.test.d.ts +0 -1
- package/dist/llm/common/MemoryStore.test.js +0 -225
- package/dist/llm/common/MemoryStore.test.js.map +0 -1
- package/dist/llm/common/formatCurrentState.d.ts +0 -10
- package/dist/llm/common/formatCurrentState.js +0 -342
- package/dist/llm/common/formatCurrentState.js.map +0 -1
- package/dist/llm/common/tool.d.ts +0 -32
- package/dist/llm/common/tool.js +0 -6
- package/dist/llm/common/tool.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.d.ts +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js +0 -433
- package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/index.d.ts +0 -18
- package/dist/llm/functions/extractClaimsFromNewEvidence/index.js +0 -153
- package/dist/llm/functions/extractClaimsFromNewEvidence/index.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.d.ts +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js +0 -168
- package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.d.ts +0 -19
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js +0 -65
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.d.ts +0 -21
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js +0 -115
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js.map +0 -1
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.d.ts +0 -24
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js +0 -110
- package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js.map +0 -1
- package/dist/llm/functions/generateIssueTitleAndSlug/index.d.ts +0 -14
- package/dist/llm/functions/generateIssueTitleAndSlug/index.js +0 -38
- package/dist/llm/functions/generateIssueTitleAndSlug/index.js.map +0 -1
- package/dist/llm/functions/generateIssueTitleAndSlug/prompt.d.ts +0 -1
- package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js +0 -23
- package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js.map +0 -1
- package/dist/llm/functions/translate/index.d.ts +0 -1
- package/dist/llm/functions/translate/index.js +0 -59
- package/dist/llm/functions/translate/index.js.map +0 -1
- package/dist/llm/functions/triageNewEvidence/eval.test.d.ts +0 -1
- package/dist/llm/functions/triageNewEvidence/eval.test.js +0 -139
- package/dist/llm/functions/triageNewEvidence/eval.test.js.map +0 -1
- package/dist/llm/functions/triageNewEvidence/index.d.ts +0 -37
- package/dist/llm/functions/triageNewEvidence/index.js +0 -121
- package/dist/llm/functions/triageNewEvidence/index.js.map +0 -1
- package/dist/llm/functions/triageNewEvidence/prompt.d.ts +0 -1
- package/dist/llm/functions/triageNewEvidence/prompt.js +0 -60
- package/dist/llm/functions/triageNewEvidence/prompt.js.map +0 -1
- package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.d.ts +0 -19
- package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js +0 -65
- package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js.map +0 -1
- package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.d.ts +0 -19
- package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js +0 -37
- package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js.map +0 -1
- package/dist/repo/MRTDownRepository.d.ts +0 -23
- package/dist/repo/MRTDownRepository.js +0 -28
- package/dist/repo/MRTDownRepository.js.map +0 -1
- package/dist/repo/common/FileStore.d.ts +0 -12
- package/dist/repo/common/FileStore.js +0 -27
- package/dist/repo/common/FileStore.js.map +0 -1
- package/dist/repo/common/StandardRepository.d.ts +0 -32
- package/dist/repo/common/StandardRepository.js +0 -58
- package/dist/repo/common/StandardRepository.js.map +0 -1
- package/dist/repo/common/store.d.ts +0 -29
- package/dist/repo/common/store.js +0 -2
- package/dist/repo/common/store.js.map +0 -1
- package/dist/repo/issue/IssueRepository.d.ts +0 -36
- package/dist/repo/issue/IssueRepository.js +0 -177
- package/dist/repo/issue/IssueRepository.js.map +0 -1
- package/dist/repo/issue/helpers/deriveCurrentState.d.ts +0 -51
- package/dist/repo/issue/helpers/deriveCurrentState.js +0 -113
- package/dist/repo/issue/helpers/deriveCurrentState.js.map +0 -1
- package/dist/repo/issue/helpers/deriveCurrentState.test.d.ts +0 -1
- package/dist/repo/issue/helpers/deriveCurrentState.test.js +0 -477
- package/dist/repo/issue/helpers/deriveCurrentState.test.js.map +0 -1
- package/dist/repo/landmark/LandmarkRepository.d.ts +0 -7
- package/dist/repo/landmark/LandmarkRepository.js +0 -12
- package/dist/repo/landmark/LandmarkRepository.js.map +0 -1
- package/dist/repo/line/LineRepository.d.ts +0 -13
- package/dist/repo/line/LineRepository.js +0 -32
- package/dist/repo/line/LineRepository.js.map +0 -1
- package/dist/repo/operator/OperatorRepository.d.ts +0 -7
- package/dist/repo/operator/OperatorRepository.js +0 -12
- package/dist/repo/operator/OperatorRepository.js.map +0 -1
- package/dist/repo/service/ServiceRepository.d.ts +0 -19
- package/dist/repo/service/ServiceRepository.js +0 -39
- package/dist/repo/service/ServiceRepository.js.map +0 -1
- package/dist/repo/station/StationRepository.d.ts +0 -13
- package/dist/repo/station/StationRepository.js +0 -30
- package/dist/repo/station/StationRepository.js.map +0 -1
- package/dist/repo/town/TownRepository.d.ts +0 -7
- package/dist/repo/town/TownRepository.js +0 -12
- package/dist/repo/town/TownRepository.js.map +0 -1
- package/dist/scripts/ingestViaWebhook.d.ts +0 -1
- package/dist/scripts/ingestViaWebhook.js +0 -9
- package/dist/scripts/ingestViaWebhook.js.map +0 -1
- package/dist/util/assert.d.ts +0 -1
- package/dist/util/assert.js +0 -6
- package/dist/util/assert.js.map +0 -1
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.d.ts +0 -7
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js +0 -24
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js.map +0 -1
- package/dist/util/ingestContent/index.d.ts +0 -12
- package/dist/util/ingestContent/index.js +0 -171
- package/dist/util/ingestContent/index.js.map +0 -1
- package/dist/util/ingestContent/types.d.ts +0 -32
- package/dist/util/ingestContent/types.js +0 -2
- package/dist/util/ingestContent/types.js.map +0 -1
- package/dist/validators/buildContext.d.ts +0 -7
- package/dist/validators/buildContext.js +0 -164
- package/dist/validators/buildContext.js.map +0 -1
- package/dist/validators/index.d.ts +0 -17
- package/dist/validators/index.js +0 -58
- package/dist/validators/index.js.map +0 -1
- package/dist/validators/issue.d.ts +0 -13
- package/dist/validators/issue.js +0 -220
- package/dist/validators/issue.js.map +0 -1
- package/dist/validators/landmark.d.ts +0 -7
- package/dist/validators/landmark.js +0 -43
- package/dist/validators/landmark.js.map +0 -1
- package/dist/validators/line.d.ts +0 -8
- package/dist/validators/line.js +0 -87
- package/dist/validators/line.js.map +0 -1
- package/dist/validators/operator.d.ts +0 -7
- package/dist/validators/operator.js +0 -43
- package/dist/validators/operator.js.map +0 -1
- package/dist/validators/service.d.ts +0 -8
- package/dist/validators/service.js +0 -87
- package/dist/validators/service.js.map +0 -1
- package/dist/validators/station.d.ts +0 -8
- package/dist/validators/station.js +0 -93
- package/dist/validators/station.js.map +0 -1
- package/dist/validators/town.d.ts +0 -7
- package/dist/validators/town.js +0 -43
- package/dist/validators/town.js.map +0 -1
- package/dist/validators/types.d.ts +0 -19
- package/dist/validators/types.js +0 -2
- package/dist/validators/types.js.map +0 -1
- package/dist/validators/utils.d.ts +0 -2
- package/dist/validators/utils.js +0 -9
- package/dist/validators/utils.js.map +0 -1
- package/dist/write/MRTDownWriter.d.ts +0 -27
- package/dist/write/MRTDownWriter.js +0 -27
- package/dist/write/MRTDownWriter.js.map +0 -1
- package/dist/write/common/FileWriteStore.d.ts +0 -13
- package/dist/write/common/FileWriteStore.js +0 -31
- package/dist/write/common/FileWriteStore.js.map +0 -1
- package/dist/write/common/StandardWriter.d.ts +0 -14
- package/dist/write/common/StandardWriter.js +0 -17
- package/dist/write/common/StandardWriter.js.map +0 -1
- package/dist/write/common/store.d.ts +0 -32
- package/dist/write/common/store.js +0 -2
- package/dist/write/common/store.js.map +0 -1
- package/dist/write/id/IdGenerator.d.ts +0 -18
- package/dist/write/id/IdGenerator.js +0 -23
- package/dist/write/id/IdGenerator.js.map +0 -1
- package/dist/write/issue/IssueWriter.d.ts +0 -12
- package/dist/write/issue/IssueWriter.js +0 -33
- package/dist/write/issue/IssueWriter.js.map +0 -1
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import type { Period } from '../schema/issue/period.js';
|
|
2
|
-
/**
|
|
3
|
-
* Optional inference tuning for operational mode.
|
|
4
|
-
*/
|
|
5
|
-
type ResolvePeriodsOperationalModeConfig = {
|
|
6
|
-
/**
|
|
7
|
-
* Minutes after `lastEvidenceAt` before an open period is considered stale.
|
|
8
|
-
*
|
|
9
|
-
* @default 120
|
|
10
|
-
*/
|
|
11
|
-
evidenceStaleAfterMinutes?: number;
|
|
12
|
-
/**
|
|
13
|
-
* Grace minutes after crowd activity decays before inferring resolution.
|
|
14
|
-
*
|
|
15
|
-
* @default 30
|
|
16
|
-
*/
|
|
17
|
-
crowdExitGraceMinutes?: number;
|
|
18
|
-
/**
|
|
19
|
-
* Hard cap for inferred period length from `startAt`.
|
|
20
|
-
*
|
|
21
|
-
* @default 1080
|
|
22
|
-
*/
|
|
23
|
-
maxInferredDurationMinutes?: number;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Crowd-derived signal used as a positive indicator of ongoing disruption.
|
|
27
|
-
*/
|
|
28
|
-
type ResolvePeriodsCrowdSignal = {
|
|
29
|
-
/**
|
|
30
|
-
* Whether crowd reports currently indicate active disruption.
|
|
31
|
-
*/
|
|
32
|
-
activeNow: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Most recent timestamp when crowd activity was observed.
|
|
35
|
-
*/
|
|
36
|
-
lastActiveAt?: string | null;
|
|
37
|
-
/**
|
|
38
|
-
* Explicit timestamp when crowd reports indicate resolution.
|
|
39
|
-
*/
|
|
40
|
-
exitedAt?: string | null;
|
|
41
|
-
/**
|
|
42
|
-
* Optional model confidence for `activeNow` in the [0, 1] range.
|
|
43
|
-
*/
|
|
44
|
-
confidenceNow?: number | null;
|
|
45
|
-
};
|
|
46
|
-
export type ResolvePeriodsMode = {
|
|
47
|
-
kind: 'canonical';
|
|
48
|
-
} | {
|
|
49
|
-
kind: 'operational';
|
|
50
|
-
/**
|
|
51
|
-
* Timestamp of the most recent evidence supporting an ongoing state
|
|
52
|
-
* for this entity.
|
|
53
|
-
*
|
|
54
|
-
* If provided and endAt is null:
|
|
55
|
-
* - May be used to infer an end time after a configured staleness window.
|
|
56
|
-
*
|
|
57
|
-
* If null or undefined:
|
|
58
|
-
* - No evidence-timeout inference will occur.
|
|
59
|
-
*/
|
|
60
|
-
lastEvidenceAt?: string | null;
|
|
61
|
-
/**
|
|
62
|
-
* Optional crowd signal state for this entity.
|
|
63
|
-
*
|
|
64
|
-
* Crowd data is treated as a positive signal:
|
|
65
|
-
* - activeNow = true -> disruption likely ongoing.
|
|
66
|
-
* - exitedAt or lastActiveAt may be used to infer resolution.
|
|
67
|
-
*/
|
|
68
|
-
crowd?: ResolvePeriodsCrowdSignal | null;
|
|
69
|
-
/**
|
|
70
|
-
* Optional configuration overrides for inference behavior.
|
|
71
|
-
*
|
|
72
|
-
* If omitted, sensible defaults are used.
|
|
73
|
-
*
|
|
74
|
-
* @default { evidenceStaleAfterMinutes: 120, crowdExitGraceMinutes: 30, maxInferredDurationMinutes: 1080 }
|
|
75
|
-
*/
|
|
76
|
-
config?: ResolvePeriodsOperationalModeConfig;
|
|
77
|
-
};
|
|
78
|
-
export type ResolvePeriodsEndAtSource = 'fact' | 'inferred' | 'none';
|
|
79
|
-
export type ResolvePeriodsEndAtReason = 'crowd_decay' | 'evidence_timeout';
|
|
80
|
-
/**
|
|
81
|
-
* Parameters for resolvePeriods().
|
|
82
|
-
*
|
|
83
|
-
* These inputs provide:
|
|
84
|
-
* - The canonical periods to resolve
|
|
85
|
-
* - The evaluation timestamp (`asOf`)
|
|
86
|
-
* - The normalization strategy (`mode`)
|
|
87
|
-
* - Optional contextual signals used for inference (evidence + crowd)
|
|
88
|
-
*
|
|
89
|
-
* None of these inputs modify canonical storage. They are used only to
|
|
90
|
-
* derive a view suitable for UI or analytics.
|
|
91
|
-
*/
|
|
92
|
-
export type ResolvePeriodsParams = {
|
|
93
|
-
/**
|
|
94
|
-
* Canonical periods for a single entity (service or facility).
|
|
95
|
-
*
|
|
96
|
-
* Requirements:
|
|
97
|
-
* - startAt and endAt must be ISO 8601 strings with timezone offsets.
|
|
98
|
-
* - endAt may be null when resolution was not explicitly recorded.
|
|
99
|
-
*
|
|
100
|
-
* These are treated as factual inputs. resolvePeriods() does not
|
|
101
|
-
* mutate or rewrite them.
|
|
102
|
-
*/
|
|
103
|
-
periods: Period[];
|
|
104
|
-
/**
|
|
105
|
-
* The timestamp at which normalization is evaluated.
|
|
106
|
-
*
|
|
107
|
-
* Must be an ISO 8601 string with timezone offset (e.g. +08:00).
|
|
108
|
-
*
|
|
109
|
-
* Examples:
|
|
110
|
-
* - Determines whether a period is currently active.
|
|
111
|
-
* - Prevents inferred end times from extending into the future.
|
|
112
|
-
*/
|
|
113
|
-
asOf: string;
|
|
114
|
-
/**
|
|
115
|
-
* Controls how open-ended periods are interpreted.
|
|
116
|
-
*/
|
|
117
|
-
mode: ResolvePeriodsMode;
|
|
118
|
-
};
|
|
119
|
-
/**
|
|
120
|
-
* Normalized periods returned by `resolvePeriods()`.
|
|
121
|
-
*
|
|
122
|
-
* Each item preserves canonical `startAt`/`endAt` values and adds mode-aware
|
|
123
|
-
* resolution metadata for consumers that need either factual timelines or
|
|
124
|
-
* operational "active now" behavior.
|
|
125
|
-
*/
|
|
126
|
-
type ResolvePeriodsResult = {
|
|
127
|
-
/**
|
|
128
|
-
* Start timestamp from canonical period data.
|
|
129
|
-
*/
|
|
130
|
-
startAt: string;
|
|
131
|
-
/**
|
|
132
|
-
* Canonical end timestamp as stored in source data.
|
|
133
|
-
*
|
|
134
|
-
* This remains null for open-ended canonical periods.
|
|
135
|
-
*/
|
|
136
|
-
endAt: string | null;
|
|
137
|
-
/**
|
|
138
|
-
* Effective end timestamp for the selected mode.
|
|
139
|
-
*
|
|
140
|
-
* - "canonical": equals `endAt`
|
|
141
|
-
* - "operational": may be inferred (end of day when inferred)
|
|
142
|
-
*/
|
|
143
|
-
endAtResolved: string | null;
|
|
144
|
-
/**
|
|
145
|
-
* Origin of `endAtResolved`.
|
|
146
|
-
*/
|
|
147
|
-
endAtSource: ResolvePeriodsEndAtSource;
|
|
148
|
-
/**
|
|
149
|
-
* Heuristic used when `endAtSource` is "inferred".
|
|
150
|
-
*/
|
|
151
|
-
endAtReason?: ResolvePeriodsEndAtReason;
|
|
152
|
-
}[];
|
|
153
|
-
/**
|
|
154
|
-
* Resolves canonical Period[] into a view suitable for UI or statistics.
|
|
155
|
-
*
|
|
156
|
-
* This function does NOT mutate canonical period data. It derives a view over
|
|
157
|
-
* stored periods depending on the selected normalization mode.
|
|
158
|
-
*
|
|
159
|
-
* The core problem this solves:
|
|
160
|
-
* - In real operations, disruption "end" is often not explicitly reported.
|
|
161
|
-
* - Crowd reports are positive-only (people report problems more than resolution).
|
|
162
|
-
* - Canonical logs should not fabricate timestamps, but the product still needs:
|
|
163
|
-
* - a usable "active now" experience, and
|
|
164
|
-
* - honest uptime/statistics.
|
|
165
|
-
*
|
|
166
|
-
* ---------------------------------------------------------------------
|
|
167
|
-
* MODES
|
|
168
|
-
* ---------------------------------------------------------------------
|
|
169
|
-
*
|
|
170
|
-
* 1) "canonical" (truth / audit)
|
|
171
|
-
*
|
|
172
|
-
* Intended for:
|
|
173
|
-
* - Issue detail timelines and audit views ("what do we actually know?")
|
|
174
|
-
* - Debugging and deterministic replay
|
|
175
|
-
* - Data exports and downstream processing
|
|
176
|
-
*
|
|
177
|
-
* Behavior:
|
|
178
|
-
* - Returns periods exactly as stored.
|
|
179
|
-
* - endAtresolved === endAt.
|
|
180
|
-
* - Open-ended periods (endAt = null) remain open.
|
|
181
|
-
* - No inferred end times are introduced.
|
|
182
|
-
*
|
|
183
|
-
* Use this when you want maximum factual integrity and reproducibility.
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* 2) "operational" (live UX)
|
|
187
|
-
*
|
|
188
|
-
* Intended for:
|
|
189
|
-
* - Live disruption UI (homepage banners, "active now", notifications)
|
|
190
|
-
* - User-facing duration display ("likely ended around ...")
|
|
191
|
-
* - Operational dashboards where preventing "zombie incidents" is important
|
|
192
|
-
*
|
|
193
|
-
* Behavior:
|
|
194
|
-
* - If a period has a factual endAt, use it.
|
|
195
|
-
* - If endAt is null, attempt to infer an end time using heuristics such as:
|
|
196
|
-
* - crowd signal decay (preferred when available)
|
|
197
|
-
* - evidence staleness timeout (fallback)
|
|
198
|
-
* - Inferred ends are annotated:
|
|
199
|
-
* endAtSource = "inferred"
|
|
200
|
-
* endAtReason = "crowd_decay" | "evidence_timeout"
|
|
201
|
-
* - If inference would produce an end time later than `asOf`,
|
|
202
|
-
* the period remains open (still active).
|
|
203
|
-
*
|
|
204
|
-
* IMPORTANT:
|
|
205
|
-
* - Inferred ends are derived and reversible.
|
|
206
|
-
* - They must NOT be written back into canonical storage.
|
|
207
|
-
*
|
|
208
|
-
* Inferred ends are set to end of day (00:00 next day, exclusive) in Singapore
|
|
209
|
-
* timezone, not duration-based. This avoids artificially shortening disruption.
|
|
210
|
-
*
|
|
211
|
-
* Use this when you want a stable, user-friendly view of "what's happening now"
|
|
212
|
-
* even when reporting is incomplete.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* ---------------------------------------------------------------------
|
|
216
|
-
* DESIGN PRINCIPLE
|
|
217
|
-
* ---------------------------------------------------------------------
|
|
218
|
-
*
|
|
219
|
-
* Canonical data must remain factually correct and append-only.
|
|
220
|
-
* Heuristics (timeouts, crowd decay, assumptions) belong in derived views,
|
|
221
|
-
* not in canonical period storage.
|
|
222
|
-
*/
|
|
223
|
-
export declare function resolvePeriods(params: ResolvePeriodsParams): ResolvePeriodsResult;
|
|
224
|
-
export {};
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { DateTime } from 'luxon';
|
|
2
|
-
import { assert } from '../util/assert.js';
|
|
3
|
-
import { normalizeRecurringPeriod } from './normalizeRecurringPeriod.js';
|
|
4
|
-
const DEFAULTS = {
|
|
5
|
-
evidenceStaleAfterMinutes: 120,
|
|
6
|
-
crowdExitGraceMinutes: 30,
|
|
7
|
-
maxInferredDurationMinutes: 18 * 60,
|
|
8
|
-
};
|
|
9
|
-
function resolveByMode(args) {
|
|
10
|
-
const { period, mode, asOf, lastEvidenceAt, crowd, config } = args;
|
|
11
|
-
const startAtDt = DateTime.fromISO(period.startAt, { setZone: true });
|
|
12
|
-
assert(startAtDt.isValid, `Invalid ISO datetime: ${period.startAt}`);
|
|
13
|
-
if (mode === 'canonical') {
|
|
14
|
-
return {
|
|
15
|
-
...period,
|
|
16
|
-
endAtResolved: period.endAt,
|
|
17
|
-
endAtSource: period.endAt ? 'fact' : 'none',
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
if (period.endAt) {
|
|
21
|
-
return {
|
|
22
|
-
...period,
|
|
23
|
-
endAtResolved: period.endAt,
|
|
24
|
-
endAtSource: 'fact',
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
const evidenceTimeoutEnd = lastEvidenceAt
|
|
28
|
-
? (() => {
|
|
29
|
-
const parsedLastEvidenceAt = DateTime.fromISO(lastEvidenceAt, {
|
|
30
|
-
setZone: true,
|
|
31
|
-
});
|
|
32
|
-
assert(parsedLastEvidenceAt.isValid, `Invalid ISO datetime: ${lastEvidenceAt}`);
|
|
33
|
-
return parsedLastEvidenceAt.plus({
|
|
34
|
-
minutes: config.evidenceStaleAfterMinutes,
|
|
35
|
-
});
|
|
36
|
-
})()
|
|
37
|
-
: null;
|
|
38
|
-
let crowdDecayEnd = null;
|
|
39
|
-
if (crowd) {
|
|
40
|
-
if (crowd.exitedAt) {
|
|
41
|
-
crowdDecayEnd = DateTime.fromISO(crowd.exitedAt, { setZone: true });
|
|
42
|
-
assert(crowdDecayEnd.isValid, `Invalid ISO datetime: ${crowd.exitedAt}`);
|
|
43
|
-
}
|
|
44
|
-
else if (!crowd.activeNow && crowd.lastActiveAt) {
|
|
45
|
-
const parsedLastActiveAt = DateTime.fromISO(crowd.lastActiveAt, {
|
|
46
|
-
setZone: true,
|
|
47
|
-
});
|
|
48
|
-
assert(parsedLastActiveAt.isValid, `Invalid ISO datetime: ${crowd.lastActiveAt}`);
|
|
49
|
-
crowdDecayEnd = parsedLastActiveAt.plus({
|
|
50
|
-
minutes: config.crowdExitGraceMinutes,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const inferredCandidate = crowdDecayEnd ?? evidenceTimeoutEnd;
|
|
55
|
-
const inferredReason = crowdDecayEnd
|
|
56
|
-
? 'crowd_decay'
|
|
57
|
-
: evidenceTimeoutEnd
|
|
58
|
-
? 'evidence_timeout'
|
|
59
|
-
: undefined;
|
|
60
|
-
if (!inferredCandidate || !inferredReason) {
|
|
61
|
-
return {
|
|
62
|
-
...period,
|
|
63
|
-
endAtResolved: null,
|
|
64
|
-
endAtSource: 'none',
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const maxInferredEnd = startAtDt.plus({
|
|
68
|
-
minutes: config.maxInferredDurationMinutes,
|
|
69
|
-
});
|
|
70
|
-
// Inferred end = end of day (00:00 next day, exclusive) in Singapore timezone.
|
|
71
|
-
let inferredEnd = inferredCandidate
|
|
72
|
-
.setZone('Asia/Singapore')
|
|
73
|
-
.startOf('day')
|
|
74
|
-
.plus({ days: 1 });
|
|
75
|
-
// Never infer an end before the period starts.
|
|
76
|
-
if (inferredEnd < startAtDt) {
|
|
77
|
-
inferredEnd = startAtDt;
|
|
78
|
-
}
|
|
79
|
-
// Never infer beyond the configured operational maximum window.
|
|
80
|
-
if (inferredEnd > maxInferredEnd) {
|
|
81
|
-
inferredEnd = maxInferredEnd;
|
|
82
|
-
}
|
|
83
|
-
// If inferred close time is in the future relative to asOf, keep it open.
|
|
84
|
-
if (inferredEnd > asOf) {
|
|
85
|
-
return {
|
|
86
|
-
...period,
|
|
87
|
-
endAtResolved: null,
|
|
88
|
-
endAtSource: 'none',
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
...period,
|
|
93
|
-
endAtResolved: inferredEnd.toISO(),
|
|
94
|
-
endAtSource: 'inferred',
|
|
95
|
-
endAtReason: inferredReason,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Resolves canonical Period[] into a view suitable for UI or statistics.
|
|
100
|
-
*
|
|
101
|
-
* This function does NOT mutate canonical period data. It derives a view over
|
|
102
|
-
* stored periods depending on the selected normalization mode.
|
|
103
|
-
*
|
|
104
|
-
* The core problem this solves:
|
|
105
|
-
* - In real operations, disruption "end" is often not explicitly reported.
|
|
106
|
-
* - Crowd reports are positive-only (people report problems more than resolution).
|
|
107
|
-
* - Canonical logs should not fabricate timestamps, but the product still needs:
|
|
108
|
-
* - a usable "active now" experience, and
|
|
109
|
-
* - honest uptime/statistics.
|
|
110
|
-
*
|
|
111
|
-
* ---------------------------------------------------------------------
|
|
112
|
-
* MODES
|
|
113
|
-
* ---------------------------------------------------------------------
|
|
114
|
-
*
|
|
115
|
-
* 1) "canonical" (truth / audit)
|
|
116
|
-
*
|
|
117
|
-
* Intended for:
|
|
118
|
-
* - Issue detail timelines and audit views ("what do we actually know?")
|
|
119
|
-
* - Debugging and deterministic replay
|
|
120
|
-
* - Data exports and downstream processing
|
|
121
|
-
*
|
|
122
|
-
* Behavior:
|
|
123
|
-
* - Returns periods exactly as stored.
|
|
124
|
-
* - endAtresolved === endAt.
|
|
125
|
-
* - Open-ended periods (endAt = null) remain open.
|
|
126
|
-
* - No inferred end times are introduced.
|
|
127
|
-
*
|
|
128
|
-
* Use this when you want maximum factual integrity and reproducibility.
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* 2) "operational" (live UX)
|
|
132
|
-
*
|
|
133
|
-
* Intended for:
|
|
134
|
-
* - Live disruption UI (homepage banners, "active now", notifications)
|
|
135
|
-
* - User-facing duration display ("likely ended around ...")
|
|
136
|
-
* - Operational dashboards where preventing "zombie incidents" is important
|
|
137
|
-
*
|
|
138
|
-
* Behavior:
|
|
139
|
-
* - If a period has a factual endAt, use it.
|
|
140
|
-
* - If endAt is null, attempt to infer an end time using heuristics such as:
|
|
141
|
-
* - crowd signal decay (preferred when available)
|
|
142
|
-
* - evidence staleness timeout (fallback)
|
|
143
|
-
* - Inferred ends are annotated:
|
|
144
|
-
* endAtSource = "inferred"
|
|
145
|
-
* endAtReason = "crowd_decay" | "evidence_timeout"
|
|
146
|
-
* - If inference would produce an end time later than `asOf`,
|
|
147
|
-
* the period remains open (still active).
|
|
148
|
-
*
|
|
149
|
-
* IMPORTANT:
|
|
150
|
-
* - Inferred ends are derived and reversible.
|
|
151
|
-
* - They must NOT be written back into canonical storage.
|
|
152
|
-
*
|
|
153
|
-
* Inferred ends are set to end of day (00:00 next day, exclusive) in Singapore
|
|
154
|
-
* timezone, not duration-based. This avoids artificially shortening disruption.
|
|
155
|
-
*
|
|
156
|
-
* Use this when you want a stable, user-friendly view of "what's happening now"
|
|
157
|
-
* even when reporting is incomplete.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* ---------------------------------------------------------------------
|
|
161
|
-
* DESIGN PRINCIPLE
|
|
162
|
-
* ---------------------------------------------------------------------
|
|
163
|
-
*
|
|
164
|
-
* Canonical data must remain factually correct and append-only.
|
|
165
|
-
* Heuristics (timeouts, crowd decay, assumptions) belong in derived views,
|
|
166
|
-
* not in canonical period storage.
|
|
167
|
-
*/
|
|
168
|
-
export function resolvePeriods(params) {
|
|
169
|
-
const { periods, asOf, mode } = params;
|
|
170
|
-
const lastEvidenceAt = mode.kind === 'operational' ? mode.lastEvidenceAt : undefined;
|
|
171
|
-
const crowd = mode.kind === 'operational' ? (mode.crowd ?? null) : null;
|
|
172
|
-
const config = mode.kind === 'operational' ? mode.config : undefined;
|
|
173
|
-
const normalizedPeriods = periods.flatMap((period) => {
|
|
174
|
-
switch (period.kind) {
|
|
175
|
-
case 'fixed':
|
|
176
|
-
return [period];
|
|
177
|
-
case 'recurring':
|
|
178
|
-
return normalizeRecurringPeriod(period);
|
|
179
|
-
default:
|
|
180
|
-
// @ts-expect-error - we only support fixed and recurring periods for now
|
|
181
|
-
throw new Error(`Invalid period kind: ${period.kind}`);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
const effectiveConfig = {
|
|
185
|
-
evidenceStaleAfterMinutes: config?.evidenceStaleAfterMinutes ?? DEFAULTS.evidenceStaleAfterMinutes,
|
|
186
|
-
crowdExitGraceMinutes: config?.crowdExitGraceMinutes ?? DEFAULTS.crowdExitGraceMinutes,
|
|
187
|
-
maxInferredDurationMinutes: config?.maxInferredDurationMinutes ?? DEFAULTS.maxInferredDurationMinutes,
|
|
188
|
-
};
|
|
189
|
-
const asOfDt = DateTime.fromISO(asOf, { setZone: true });
|
|
190
|
-
assert(asOfDt.isValid, `Invalid ISO datetime: ${asOf}`);
|
|
191
|
-
const sorted = [...normalizedPeriods].sort((a, b) => {
|
|
192
|
-
const aStart = DateTime.fromISO(a.startAt, { setZone: true });
|
|
193
|
-
const bStart = DateTime.fromISO(b.startAt, { setZone: true });
|
|
194
|
-
assert(aStart.isValid, `Invalid ISO datetime: ${a.startAt}`);
|
|
195
|
-
assert(bStart.isValid, `Invalid ISO datetime: ${b.startAt}`);
|
|
196
|
-
return aStart.toMillis() - bStart.toMillis();
|
|
197
|
-
});
|
|
198
|
-
return sorted.map((period) => resolveByMode({
|
|
199
|
-
period: { ...period },
|
|
200
|
-
mode: mode.kind,
|
|
201
|
-
asOf: asOfDt,
|
|
202
|
-
lastEvidenceAt,
|
|
203
|
-
crowd,
|
|
204
|
-
config: effectiveConfig,
|
|
205
|
-
}));
|
|
206
|
-
}
|
|
207
|
-
//# sourceMappingURL=resolvePeriods.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolvePeriods.js","sourceRoot":"/","sources":["helpers/resolvePeriods.ts"],"names":[],"mappings":"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","sourcesContent":["import { DateTime } from 'luxon';\nimport type { Period, PeriodFixed } from '../schema/issue/period.js';\nimport { assert } from '../util/assert.js';\nimport { normalizeRecurringPeriod } from './normalizeRecurringPeriod.js';\n\nconst DEFAULTS: ResolvePeriodsOperationalModeConfig = {\n evidenceStaleAfterMinutes: 120,\n crowdExitGraceMinutes: 30,\n maxInferredDurationMinutes: 18 * 60,\n};\n\n/**\n * Optional inference tuning for operational mode.\n */\ntype ResolvePeriodsOperationalModeConfig = {\n /**\n * Minutes after `lastEvidenceAt` before an open period is considered stale.\n *\n * @default 120\n */\n evidenceStaleAfterMinutes?: number;\n /**\n * Grace minutes after crowd activity decays before inferring resolution.\n *\n * @default 30\n */\n crowdExitGraceMinutes?: number;\n /**\n * Hard cap for inferred period length from `startAt`.\n *\n * @default 1080\n */\n maxInferredDurationMinutes?: number;\n};\n\n/**\n * Crowd-derived signal used as a positive indicator of ongoing disruption.\n */\ntype ResolvePeriodsCrowdSignal = {\n /**\n * Whether crowd reports currently indicate active disruption.\n */\n activeNow: boolean;\n /**\n * Most recent timestamp when crowd activity was observed.\n */\n lastActiveAt?: string | null;\n /**\n * Explicit timestamp when crowd reports indicate resolution.\n */\n exitedAt?: string | null;\n /**\n * Optional model confidence for `activeNow` in the [0, 1] range.\n */\n confidenceNow?: number | null;\n};\n\nexport type ResolvePeriodsMode =\n | { kind: 'canonical' }\n | {\n kind: 'operational';\n /**\n * Timestamp of the most recent evidence supporting an ongoing state\n * for this entity.\n *\n * If provided and endAt is null:\n * - May be used to infer an end time after a configured staleness window.\n *\n * If null or undefined:\n * - No evidence-timeout inference will occur.\n */\n lastEvidenceAt?: string | null;\n /**\n * Optional crowd signal state for this entity.\n *\n * Crowd data is treated as a positive signal:\n * - activeNow = true -> disruption likely ongoing.\n * - exitedAt or lastActiveAt may be used to infer resolution.\n */\n crowd?: ResolvePeriodsCrowdSignal | null;\n /**\n * Optional configuration overrides for inference behavior.\n *\n * If omitted, sensible defaults are used.\n *\n * @default { evidenceStaleAfterMinutes: 120, crowdExitGraceMinutes: 30, maxInferredDurationMinutes: 1080 }\n */\n config?: ResolvePeriodsOperationalModeConfig;\n };\n\nexport type ResolvePeriodsEndAtSource = 'fact' | 'inferred' | 'none';\nexport type ResolvePeriodsEndAtReason = 'crowd_decay' | 'evidence_timeout';\n\n/**\n * Parameters for resolvePeriods().\n *\n * These inputs provide:\n * - The canonical periods to resolve\n * - The evaluation timestamp (`asOf`)\n * - The normalization strategy (`mode`)\n * - Optional contextual signals used for inference (evidence + crowd)\n *\n * None of these inputs modify canonical storage. They are used only to\n * derive a view suitable for UI or analytics.\n */\nexport type ResolvePeriodsParams = {\n /**\n * Canonical periods for a single entity (service or facility).\n *\n * Requirements:\n * - startAt and endAt must be ISO 8601 strings with timezone offsets.\n * - endAt may be null when resolution was not explicitly recorded.\n *\n * These are treated as factual inputs. resolvePeriods() does not\n * mutate or rewrite them.\n */\n periods: Period[];\n\n /**\n * The timestamp at which normalization is evaluated.\n *\n * Must be an ISO 8601 string with timezone offset (e.g. +08:00).\n *\n * Examples:\n * - Determines whether a period is currently active.\n * - Prevents inferred end times from extending into the future.\n */\n asOf: string;\n\n /**\n * Controls how open-ended periods are interpreted.\n */\n mode: ResolvePeriodsMode;\n};\n\n/**\n * Normalized periods returned by `resolvePeriods()`.\n *\n * Each item preserves canonical `startAt`/`endAt` values and adds mode-aware\n * resolution metadata for consumers that need either factual timelines or\n * operational \"active now\" behavior.\n */\ntype ResolvePeriodsResult = {\n /**\n * Start timestamp from canonical period data.\n */\n startAt: string;\n /**\n * Canonical end timestamp as stored in source data.\n *\n * This remains null for open-ended canonical periods.\n */\n endAt: string | null;\n /**\n * Effective end timestamp for the selected mode.\n *\n * - \"canonical\": equals `endAt`\n * - \"operational\": may be inferred (end of day when inferred)\n */\n endAtResolved: string | null;\n /**\n * Origin of `endAtResolved`.\n */\n endAtSource: ResolvePeriodsEndAtSource;\n /**\n * Heuristic used when `endAtSource` is \"inferred\".\n */\n endAtReason?: ResolvePeriodsEndAtReason;\n}[];\n\nfunction resolveByMode(args: {\n period: PeriodFixed;\n mode: ResolvePeriodsMode['kind'];\n asOf: DateTime;\n lastEvidenceAt?: string | null;\n crowd?: ResolvePeriodsCrowdSignal | null;\n config: ResolvePeriodsOperationalModeConfig;\n}): ResolvePeriodsResult[number] {\n const { period, mode, asOf, lastEvidenceAt, crowd, config } = args;\n const startAtDt = DateTime.fromISO(period.startAt, { setZone: true });\n assert(startAtDt.isValid, `Invalid ISO datetime: ${period.startAt}`);\n\n if (mode === 'canonical') {\n return {\n ...period,\n endAtResolved: period.endAt,\n endAtSource: period.endAt ? 'fact' : 'none',\n };\n }\n\n if (period.endAt) {\n return {\n ...period,\n endAtResolved: period.endAt,\n endAtSource: 'fact',\n };\n }\n\n const evidenceTimeoutEnd = lastEvidenceAt\n ? (() => {\n const parsedLastEvidenceAt = DateTime.fromISO(lastEvidenceAt, {\n setZone: true,\n });\n assert(\n parsedLastEvidenceAt.isValid,\n `Invalid ISO datetime: ${lastEvidenceAt}`,\n );\n return parsedLastEvidenceAt.plus({\n minutes: config.evidenceStaleAfterMinutes,\n });\n })()\n : null;\n\n let crowdDecayEnd: DateTime | null = null;\n if (crowd) {\n if (crowd.exitedAt) {\n crowdDecayEnd = DateTime.fromISO(crowd.exitedAt, { setZone: true });\n assert(crowdDecayEnd.isValid, `Invalid ISO datetime: ${crowd.exitedAt}`);\n } else if (!crowd.activeNow && crowd.lastActiveAt) {\n const parsedLastActiveAt = DateTime.fromISO(crowd.lastActiveAt, {\n setZone: true,\n });\n assert(\n parsedLastActiveAt.isValid,\n `Invalid ISO datetime: ${crowd.lastActiveAt}`,\n );\n crowdDecayEnd = parsedLastActiveAt.plus({\n minutes: config.crowdExitGraceMinutes,\n });\n }\n }\n\n const inferredCandidate = crowdDecayEnd ?? evidenceTimeoutEnd;\n const inferredReason: ResolvePeriodsResult[number]['endAtReason'] =\n crowdDecayEnd\n ? 'crowd_decay'\n : evidenceTimeoutEnd\n ? 'evidence_timeout'\n : undefined;\n\n if (!inferredCandidate || !inferredReason) {\n return {\n ...period,\n endAtResolved: null,\n endAtSource: 'none',\n };\n }\n\n const maxInferredEnd = startAtDt.plus({\n minutes: config.maxInferredDurationMinutes,\n });\n\n // Inferred end = end of day (00:00 next day, exclusive) in Singapore timezone.\n let inferredEnd = inferredCandidate\n .setZone('Asia/Singapore')\n .startOf('day')\n .plus({ days: 1 });\n\n // Never infer an end before the period starts.\n if (inferredEnd < startAtDt) {\n inferredEnd = startAtDt;\n }\n // Never infer beyond the configured operational maximum window.\n if (inferredEnd > maxInferredEnd) {\n inferredEnd = maxInferredEnd;\n }\n\n // If inferred close time is in the future relative to asOf, keep it open.\n if (inferredEnd > asOf) {\n return {\n ...period,\n endAtResolved: null,\n endAtSource: 'none',\n };\n }\n\n return {\n ...period,\n endAtResolved: inferredEnd.toISO(),\n endAtSource: 'inferred',\n endAtReason: inferredReason,\n };\n}\n\n/**\n * Resolves canonical Period[] into a view suitable for UI or statistics.\n *\n * This function does NOT mutate canonical period data. It derives a view over\n * stored periods depending on the selected normalization mode.\n *\n * The core problem this solves:\n * - In real operations, disruption \"end\" is often not explicitly reported.\n * - Crowd reports are positive-only (people report problems more than resolution).\n * - Canonical logs should not fabricate timestamps, but the product still needs:\n * - a usable \"active now\" experience, and\n * - honest uptime/statistics.\n *\n * ---------------------------------------------------------------------\n * MODES\n * ---------------------------------------------------------------------\n *\n * 1) \"canonical\" (truth / audit)\n *\n * Intended for:\n * - Issue detail timelines and audit views (\"what do we actually know?\")\n * - Debugging and deterministic replay\n * - Data exports and downstream processing\n *\n * Behavior:\n * - Returns periods exactly as stored.\n * - endAtresolved === endAt.\n * - Open-ended periods (endAt = null) remain open.\n * - No inferred end times are introduced.\n *\n * Use this when you want maximum factual integrity and reproducibility.\n *\n *\n * 2) \"operational\" (live UX)\n *\n * Intended for:\n * - Live disruption UI (homepage banners, \"active now\", notifications)\n * - User-facing duration display (\"likely ended around ...\")\n * - Operational dashboards where preventing \"zombie incidents\" is important\n *\n * Behavior:\n * - If a period has a factual endAt, use it.\n * - If endAt is null, attempt to infer an end time using heuristics such as:\n * - crowd signal decay (preferred when available)\n * - evidence staleness timeout (fallback)\n * - Inferred ends are annotated:\n * endAtSource = \"inferred\"\n * endAtReason = \"crowd_decay\" | \"evidence_timeout\"\n * - If inference would produce an end time later than `asOf`,\n * the period remains open (still active).\n *\n * IMPORTANT:\n * - Inferred ends are derived and reversible.\n * - They must NOT be written back into canonical storage.\n *\n * Inferred ends are set to end of day (00:00 next day, exclusive) in Singapore\n * timezone, not duration-based. This avoids artificially shortening disruption.\n *\n * Use this when you want a stable, user-friendly view of \"what's happening now\"\n * even when reporting is incomplete.\n *\n *\n * ---------------------------------------------------------------------\n * DESIGN PRINCIPLE\n * ---------------------------------------------------------------------\n *\n * Canonical data must remain factually correct and append-only.\n * Heuristics (timeouts, crowd decay, assumptions) belong in derived views,\n * not in canonical period storage.\n */\nexport function resolvePeriods(\n params: ResolvePeriodsParams,\n): ResolvePeriodsResult {\n const { periods, asOf, mode } = params;\n const lastEvidenceAt =\n mode.kind === 'operational' ? mode.lastEvidenceAt : undefined;\n const crowd = mode.kind === 'operational' ? (mode.crowd ?? null) : null;\n const config = mode.kind === 'operational' ? mode.config : undefined;\n\n const normalizedPeriods = periods.flatMap((period) => {\n switch (period.kind) {\n case 'fixed':\n return [period];\n case 'recurring':\n return normalizeRecurringPeriod(period);\n default:\n // @ts-expect-error - we only support fixed and recurring periods for now\n throw new Error(`Invalid period kind: ${period.kind}`);\n }\n });\n\n const effectiveConfig = {\n evidenceStaleAfterMinutes:\n config?.evidenceStaleAfterMinutes ?? DEFAULTS.evidenceStaleAfterMinutes,\n crowdExitGraceMinutes:\n config?.crowdExitGraceMinutes ?? DEFAULTS.crowdExitGraceMinutes,\n maxInferredDurationMinutes:\n config?.maxInferredDurationMinutes ?? DEFAULTS.maxInferredDurationMinutes,\n };\n const asOfDt = DateTime.fromISO(asOf, { setZone: true });\n assert(asOfDt.isValid, `Invalid ISO datetime: ${asOf}`);\n const sorted = [...normalizedPeriods].sort((a, b) => {\n const aStart = DateTime.fromISO(a.startAt, { setZone: true });\n const bStart = DateTime.fromISO(b.startAt, { setZone: true });\n assert(aStart.isValid, `Invalid ISO datetime: ${a.startAt}`);\n assert(bStart.isValid, `Invalid ISO datetime: ${b.startAt}`);\n return aStart.toMillis() - bStart.toMillis();\n });\n\n return sorted.map((period) =>\n resolveByMode({\n period: { ...period },\n mode: mode.kind,\n asOf: asOfDt,\n lastEvidenceAt,\n crowd,\n config: effectiveConfig,\n }),\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|