@mrtdown/core 2.0.0-alpha.1
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/README.md +107 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -0
- package/dist/helpers/calculateDurationWithinServiceHours.d.ts +2 -0
- package/dist/helpers/calculateDurationWithinServiceHours.js +13 -0
- package/dist/helpers/calculateDurationWithinServiceHours.js.map +1 -0
- package/dist/helpers/calculateDurationWithinServiceHours.test.d.ts +1 -0
- package/dist/helpers/calculateDurationWithinServiceHours.test.js +83 -0
- package/dist/helpers/calculateDurationWithinServiceHours.test.js.map +1 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.d.ts +21 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.js +293 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.js.map +1 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.test.d.ts +1 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.test.js +544 -0
- package/dist/helpers/computeImpactFromEvidenceClaims.test.js.map +1 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.d.ts +2 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.js +15 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.js.map +1 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.test.d.ts +1 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.test.js +126 -0
- package/dist/helpers/computeStartOfDaysWithinInterval.test.js.map +1 -0
- package/dist/helpers/estimateOpenAICost.d.ts +40 -0
- package/dist/helpers/estimateOpenAICost.js +55 -0
- package/dist/helpers/estimateOpenAICost.js.map +1 -0
- package/dist/helpers/keyForAffectedEntity.d.ts +7 -0
- package/dist/helpers/keyForAffectedEntity.js +14 -0
- package/dist/helpers/keyForAffectedEntity.js.map +1 -0
- package/dist/helpers/normalizeRecurringPeriod.d.ts +7 -0
- package/dist/helpers/normalizeRecurringPeriod.js +118 -0
- package/dist/helpers/normalizeRecurringPeriod.js.map +1 -0
- package/dist/helpers/normalizeRecurringPeriod.test.d.ts +1 -0
- package/dist/helpers/normalizeRecurringPeriod.test.js +93 -0
- package/dist/helpers/normalizeRecurringPeriod.test.js.map +1 -0
- package/dist/helpers/resolvePeriods.d.ts +224 -0
- package/dist/helpers/resolvePeriods.js +207 -0
- package/dist/helpers/resolvePeriods.js.map +1 -0
- package/dist/helpers/resolvePeriods.test.d.ts +1 -0
- package/dist/helpers/resolvePeriods.test.js +239 -0
- package/dist/helpers/resolvePeriods.test.js.map +1 -0
- package/dist/helpers/splitIntervalByServiceHours.d.ts +2 -0
- package/dist/helpers/splitIntervalByServiceHours.js +30 -0
- package/dist/helpers/splitIntervalByServiceHours.js.map +1 -0
- package/dist/helpers/splitIntervalByServiceHours.test.d.ts +1 -0
- package/dist/helpers/splitIntervalByServiceHours.test.js +152 -0
- package/dist/helpers/splitIntervalByServiceHours.test.js.map +1 -0
- package/dist/helpers/sumIntervalDuration.d.ts +2 -0
- package/dist/helpers/sumIntervalDuration.js +9 -0
- package/dist/helpers/sumIntervalDuration.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/repo/MRTDownRepository.d.ts +23 -0
- package/dist/repo/MRTDownRepository.js +28 -0
- package/dist/repo/MRTDownRepository.js.map +1 -0
- package/dist/repo/common/FileStore.d.ts +12 -0
- package/dist/repo/common/FileStore.js +27 -0
- package/dist/repo/common/FileStore.js.map +1 -0
- package/dist/repo/common/StandardRepository.d.ts +32 -0
- package/dist/repo/common/StandardRepository.js +58 -0
- package/dist/repo/common/StandardRepository.js.map +1 -0
- package/dist/repo/common/store.d.ts +29 -0
- package/dist/repo/common/store.js +2 -0
- package/dist/repo/common/store.js.map +1 -0
- package/dist/repo/issue/IssueRepository.d.ts +36 -0
- package/dist/repo/issue/IssueRepository.js +177 -0
- package/dist/repo/issue/IssueRepository.js.map +1 -0
- package/dist/repo/issue/helpers/deriveCurrentState.d.ts +51 -0
- package/dist/repo/issue/helpers/deriveCurrentState.js +113 -0
- package/dist/repo/issue/helpers/deriveCurrentState.js.map +1 -0
- package/dist/repo/issue/helpers/deriveCurrentState.test.d.ts +1 -0
- package/dist/repo/issue/helpers/deriveCurrentState.test.js +477 -0
- package/dist/repo/issue/helpers/deriveCurrentState.test.js.map +1 -0
- package/dist/repo/landmark/LandmarkRepository.d.ts +7 -0
- package/dist/repo/landmark/LandmarkRepository.js +12 -0
- package/dist/repo/landmark/LandmarkRepository.js.map +1 -0
- package/dist/repo/line/LineRepository.d.ts +13 -0
- package/dist/repo/line/LineRepository.js +32 -0
- package/dist/repo/line/LineRepository.js.map +1 -0
- package/dist/repo/operator/OperatorRepository.d.ts +7 -0
- package/dist/repo/operator/OperatorRepository.js +12 -0
- package/dist/repo/operator/OperatorRepository.js.map +1 -0
- package/dist/repo/service/ServiceRepository.d.ts +19 -0
- package/dist/repo/service/ServiceRepository.js +39 -0
- package/dist/repo/service/ServiceRepository.js.map +1 -0
- package/dist/repo/station/StationRepository.d.ts +13 -0
- package/dist/repo/station/StationRepository.js +30 -0
- package/dist/repo/station/StationRepository.js.map +1 -0
- package/dist/repo/town/TownRepository.d.ts +7 -0
- package/dist/repo/town/TownRepository.js +12 -0
- package/dist/repo/town/TownRepository.js.map +1 -0
- package/dist/schema/Landmark.d.ts +11 -0
- package/dist/schema/Landmark.js +7 -0
- package/dist/schema/Landmark.js.map +1 -0
- package/dist/schema/Line.d.ts +58 -0
- package/dist/schema/Line.js +35 -0
- package/dist/schema/Line.js.map +1 -0
- package/dist/schema/Operator.d.ts +16 -0
- package/dist/schema/Operator.js +12 -0
- package/dist/schema/Operator.js.map +1 -0
- package/dist/schema/Service.d.ts +55 -0
- package/dist/schema/Service.js +24 -0
- package/dist/schema/Service.js.map +1 -0
- package/dist/schema/Station.d.ts +29 -0
- package/dist/schema/Station.js +25 -0
- package/dist/schema/Station.js.map +1 -0
- package/dist/schema/Town.d.ts +11 -0
- package/dist/schema/Town.js +7 -0
- package/dist/schema/Town.js.map +1 -0
- package/dist/schema/common.d.ts +24 -0
- package/dist/schema/common.js +23 -0
- package/dist/schema/common.js.map +1 -0
- package/dist/schema/issue/bundle.d.ts +239 -0
- package/dist/schema/issue/bundle.js +11 -0
- package/dist/schema/issue/bundle.js.map +1 -0
- package/dist/schema/issue/cause.d.ts +51 -0
- package/dist/schema/issue/cause.js +30 -0
- package/dist/schema/issue/cause.js.map +1 -0
- package/dist/schema/issue/claim.d.ts +149 -0
- package/dist/schema/issue/claim.js +36 -0
- package/dist/schema/issue/claim.js.map +1 -0
- package/dist/schema/issue/entity.d.ts +176 -0
- package/dist/schema/issue/entity.js +35 -0
- package/dist/schema/issue/entity.js.map +1 -0
- package/dist/schema/issue/evidence.d.ts +124 -0
- package/dist/schema/issue/evidence.js +30 -0
- package/dist/schema/issue/evidence.js.map +1 -0
- package/dist/schema/issue/facilityEffect.d.ts +15 -0
- package/dist/schema/issue/facilityEffect.js +12 -0
- package/dist/schema/issue/facilityEffect.js.map +1 -0
- package/dist/schema/issue/id.d.ts +3 -0
- package/dist/schema/issue/id.js +6 -0
- package/dist/schema/issue/id.js.map +1 -0
- package/dist/schema/issue/impactEvent.d.ts +373 -0
- package/dist/schema/issue/impactEvent.js +43 -0
- package/dist/schema/issue/impactEvent.js.map +1 -0
- package/dist/schema/issue/issue.d.ts +19 -0
- package/dist/schema/issue/issue.js +13 -0
- package/dist/schema/issue/issue.js.map +1 -0
- package/dist/schema/issue/issueType.d.ts +7 -0
- package/dist/schema/issue/issueType.js +3 -0
- package/dist/schema/issue/issueType.js.map +1 -0
- package/dist/schema/issue/period.d.ts +72 -0
- package/dist/schema/issue/period.js +32 -0
- package/dist/schema/issue/period.js.map +1 -0
- package/dist/schema/issue/serviceEffect.d.ts +29 -0
- package/dist/schema/issue/serviceEffect.js +21 -0
- package/dist/schema/issue/serviceEffect.js.map +1 -0
- package/dist/schema/issue/serviceScope.d.ts +38 -0
- package/dist/schema/issue/serviceScope.js +30 -0
- package/dist/schema/issue/serviceScope.js.map +1 -0
- package/dist/util/assert.d.ts +1 -0
- package/dist/util/assert.js +6 -0
- package/dist/util/assert.js.map +1 -0
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.d.ts +7 -0
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js +24 -0
- package/dist/util/ingestContent/helpers/getSlugDateTimeFromClaims.js.map +1 -0
- package/dist/util/ingestContent/index.d.ts +12 -0
- package/dist/util/ingestContent/index.js +171 -0
- package/dist/util/ingestContent/index.js.map +1 -0
- package/dist/util/ingestContent/types.d.ts +32 -0
- package/dist/util/ingestContent/types.js +2 -0
- package/dist/util/ingestContent/types.js.map +1 -0
- package/dist/write/MRTDownWriter.d.ts +27 -0
- package/dist/write/MRTDownWriter.js +27 -0
- package/dist/write/MRTDownWriter.js.map +1 -0
- package/dist/write/common/FileWriteStore.d.ts +13 -0
- package/dist/write/common/FileWriteStore.js +31 -0
- package/dist/write/common/FileWriteStore.js.map +1 -0
- package/dist/write/common/StandardWriter.d.ts +14 -0
- package/dist/write/common/StandardWriter.js +17 -0
- package/dist/write/common/StandardWriter.js.map +1 -0
- package/dist/write/common/store.d.ts +32 -0
- package/dist/write/common/store.js +2 -0
- package/dist/write/common/store.js.map +1 -0
- package/dist/write/id/IdGenerator.d.ts +18 -0
- package/dist/write/id/IdGenerator.js +23 -0
- package/dist/write/id/IdGenerator.js.map +1 -0
- package/dist/write/issue/IssueWriter.d.ts +12 -0
- package/dist/write/issue/IssueWriter.js +33 -0
- package/dist/write/issue/IssueWriter.js.map +1 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# mrtdown-data
|
|
2
|
+
|
|
3
|
+
A comprehensive data repository and API system that tracks Singapore's MRT (Mass Rapid Transit) service disruptions, maintenance, and infrastructure issues. Functions as a status monitoring system for Singapore's public transportation network.
|
|
4
|
+
|
|
5
|
+
## Tech Stack
|
|
6
|
+
|
|
7
|
+
- **Backend**: Node.js + TypeScript, Hono API framework
|
|
8
|
+
- **Database**: DuckDB with normalized relational schema
|
|
9
|
+
- **Validation**: Zod schemas with OpenAPI integration
|
|
10
|
+
- **Testing**: Vitest
|
|
11
|
+
- **Linting**: Biome
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install dependencies
|
|
17
|
+
npm install
|
|
18
|
+
|
|
19
|
+
# Build and generate database
|
|
20
|
+
npm run build
|
|
21
|
+
|
|
22
|
+
# Start development API server
|
|
23
|
+
npm run api:dev # Runs on port 4000
|
|
24
|
+
|
|
25
|
+
# Run tests
|
|
26
|
+
npm test
|
|
27
|
+
|
|
28
|
+
# Lint and format
|
|
29
|
+
npx biome check
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Development Commands
|
|
33
|
+
|
|
34
|
+
### Database Operations
|
|
35
|
+
```bash
|
|
36
|
+
npm run build # Compile TypeScript (auto-runs db:generate)
|
|
37
|
+
npm run db:generate # Generate DuckDB database from source data
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### API Development
|
|
41
|
+
```bash
|
|
42
|
+
npm run api:dev # Start dev server on port 4000
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Data Processing
|
|
46
|
+
```bash
|
|
47
|
+
npm run ingest:webhook # Process incoming webhook data
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Testing & Quality
|
|
51
|
+
```bash
|
|
52
|
+
npm test # Run Vitest tests
|
|
53
|
+
npx biome check # Lint and format code
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Database Queries
|
|
57
|
+
```bash
|
|
58
|
+
# Query the database (use -readonly when API server is running)
|
|
59
|
+
duckdb -readonly -c "SELECT * FROM issues LIMIT 10" mrtdown.duckdb
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Architecture Overview
|
|
63
|
+
|
|
64
|
+
### Core Data Models
|
|
65
|
+
- **Lines**: MRT/LRT lines (NSL, EWL, CCL, etc.) with service schedules
|
|
66
|
+
- **Issues**: Disruptions, maintenance, infrastructure problems with time intervals
|
|
67
|
+
- **Stations**: Station information with multi-language support
|
|
68
|
+
- **Time-aware**: All operations handle Singapore timezone (`Asia/Singapore`)
|
|
69
|
+
|
|
70
|
+
### API Structure
|
|
71
|
+
Located in `/src/api/routes/`:
|
|
72
|
+
- **Overview**: System-wide status and line summaries
|
|
73
|
+
- **Lines**: Individual line profiles with detailed uptime metrics
|
|
74
|
+
- **Issues**: Issue details and historical data
|
|
75
|
+
- **Stations**: Station-specific information
|
|
76
|
+
- **Analytics**: Statistical analysis endpoints
|
|
77
|
+
|
|
78
|
+
All endpoints require Bearer token authentication except `/docs`.
|
|
79
|
+
|
|
80
|
+
### Data Flow
|
|
81
|
+
1. **Source data** (JSON files in `/data/source/`)
|
|
82
|
+
2. **Database generation** (DuckDB with complex analytics)
|
|
83
|
+
3. **API endpoints** (Real-time queries with multi-language support)
|
|
84
|
+
|
|
85
|
+
## Key Features
|
|
86
|
+
|
|
87
|
+
- **Real-time Status Monitoring**: Track MRT line disruptions and maintenance
|
|
88
|
+
- **Historical Analytics**: Complex uptime calculations and service metrics
|
|
89
|
+
- **Multi-language Support**: Content available in 4 languages
|
|
90
|
+
- **Time-zone Aware**: All operations in Singapore timezone
|
|
91
|
+
- **Service Hours Logic**: Different schedules for weekdays/weekends/holidays
|
|
92
|
+
- **Webhook Integration**: Real-time data ingestion capabilities
|
|
93
|
+
|
|
94
|
+
## Issue Data Structure
|
|
95
|
+
|
|
96
|
+
- **File naming**: `YYYY-MM-DD-descriptive-slug.json`
|
|
97
|
+
- **Types**: `disruption`, `maintenance`, `infra`
|
|
98
|
+
- **Time intervals**: Start/end timestamps with timezone awareness
|
|
99
|
+
- **Multi-language**: All titles have 4-language translations
|
|
100
|
+
|
|
101
|
+
## Development Notes
|
|
102
|
+
|
|
103
|
+
- Database regeneration required when source data changes
|
|
104
|
+
- API responses include related entities for client efficiency
|
|
105
|
+
- Performance optimized for read-heavy analytical workloads
|
|
106
|
+
- Extensive use of CTEs for complex uptime calculations
|
|
107
|
+
- Proper handling of ongoing issues (end_at = NULL)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DIR_ISSUE = "issue";
|
|
2
|
+
export declare const DIR_LINE = "line";
|
|
3
|
+
export declare const DIR_LANDMARK = "landmark";
|
|
4
|
+
export declare const DIR_OPERATOR = "operator";
|
|
5
|
+
export declare const DIR_SERVICE = "service";
|
|
6
|
+
export declare const DIR_STATION = "station";
|
|
7
|
+
export declare const DIR_TOWN = "town";
|
|
8
|
+
export declare const FILE_ISSUE = "issue.json";
|
|
9
|
+
export declare const FILE_ISSUE_EVIDENCE = "evidence.ndjson";
|
|
10
|
+
export declare const FILE_ISSUE_IMPACT = "impact.ndjson";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DIR_ISSUE = 'issue';
|
|
2
|
+
export const DIR_LINE = 'line';
|
|
3
|
+
export const DIR_LANDMARK = 'landmark';
|
|
4
|
+
export const DIR_OPERATOR = 'operator';
|
|
5
|
+
export const DIR_SERVICE = 'service';
|
|
6
|
+
export const DIR_STATION = 'station';
|
|
7
|
+
export const DIR_TOWN = 'town';
|
|
8
|
+
export const FILE_ISSUE = 'issue.json';
|
|
9
|
+
export const FILE_ISSUE_EVIDENCE = 'evidence.ndjson';
|
|
10
|
+
export const FILE_ISSUE_IMPACT = 'impact.ndjson';
|
|
11
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"/","sources":["constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC;AACjC,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AACvC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AACrC,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AACrC,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE/B,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AACvC,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AACrD,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC","sourcesContent":["export const DIR_ISSUE = 'issue';\nexport const DIR_LINE = 'line';\nexport const DIR_LANDMARK = 'landmark';\nexport const DIR_OPERATOR = 'operator';\nexport const DIR_SERVICE = 'service';\nexport const DIR_STATION = 'station';\nexport const DIR_TOWN = 'town';\n\nexport const FILE_ISSUE = 'issue.json';\nexport const FILE_ISSUE_EVIDENCE = 'evidence.ndjson';\nexport const FILE_ISSUE_IMPACT = 'impact.ndjson';\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Duration, Interval } from 'luxon';
|
|
2
|
+
import { assert } from '../util/assert.js';
|
|
3
|
+
import { splitIntervalByServiceHours } from './splitIntervalByServiceHours.js';
|
|
4
|
+
export function calculateDurationWithinServiceHours(start, end) {
|
|
5
|
+
const interval = Interval.fromDateTimes(start, end);
|
|
6
|
+
assert(interval.isValid);
|
|
7
|
+
let result = Duration.fromObject({ seconds: 0 });
|
|
8
|
+
for (const segment of splitIntervalByServiceHours(interval)) {
|
|
9
|
+
result = result.plus(segment.toDuration());
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=calculateDurationWithinServiceHours.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculateDurationWithinServiceHours.js","sourceRoot":"/","sources":["helpers/calculateDurationWithinServiceHours.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,QAAQ,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAE/E,MAAM,UAAU,mCAAmC,CACjD,KAAe,EACf,GAAa;IAEb,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEzB,IAAI,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjD,KAAK,MAAM,OAAO,IAAI,2BAA2B,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5D,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { type DateTime, Duration, Interval } from 'luxon';\nimport { assert } from '../util/assert.js';\nimport { splitIntervalByServiceHours } from './splitIntervalByServiceHours.js';\n\nexport function calculateDurationWithinServiceHours(\n start: DateTime,\n end: DateTime,\n): Duration {\n const interval = Interval.fromDateTimes(start, end);\n assert(interval.isValid);\n\n let result = Duration.fromObject({ seconds: 0 });\n\n for (const segment of splitIntervalByServiceHours(interval)) {\n result = result.plus(segment.toDuration());\n }\n\n return result;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { calculateDurationWithinServiceHours } from './calculateDurationWithinServiceHours.js';
|
|
3
|
+
import { DateTime } from 'luxon';
|
|
4
|
+
describe('calculateDurationWithinServiceHours', () => {
|
|
5
|
+
test('single day, within service hours', () => {
|
|
6
|
+
expect(calculateDurationWithinServiceHours(DateTime.fromObject({
|
|
7
|
+
day: 1,
|
|
8
|
+
month: 4,
|
|
9
|
+
year: 2025,
|
|
10
|
+
hour: 8,
|
|
11
|
+
minute: 0,
|
|
12
|
+
}), DateTime.fromObject({
|
|
13
|
+
day: 1,
|
|
14
|
+
month: 4,
|
|
15
|
+
year: 2025,
|
|
16
|
+
hour: 12,
|
|
17
|
+
minute: 30,
|
|
18
|
+
})).as('hours')).toEqual(4.5);
|
|
19
|
+
});
|
|
20
|
+
describe('transcends service hour boundaries', () => {
|
|
21
|
+
test('single day, starts before service hours', () => {
|
|
22
|
+
expect(calculateDurationWithinServiceHours(DateTime.fromObject({
|
|
23
|
+
day: 1,
|
|
24
|
+
month: 4,
|
|
25
|
+
year: 2025,
|
|
26
|
+
hour: 5,
|
|
27
|
+
minute: 0,
|
|
28
|
+
}), DateTime.fromObject({
|
|
29
|
+
day: 1,
|
|
30
|
+
month: 4,
|
|
31
|
+
year: 2025,
|
|
32
|
+
hour: 6,
|
|
33
|
+
minute: 0,
|
|
34
|
+
})).as('hours')).toEqual(0.5);
|
|
35
|
+
});
|
|
36
|
+
test('single day, ends after service hours', () => {
|
|
37
|
+
expect(calculateDurationWithinServiceHours(DateTime.fromObject({
|
|
38
|
+
day: 1,
|
|
39
|
+
month: 4,
|
|
40
|
+
year: 2025,
|
|
41
|
+
hour: 18,
|
|
42
|
+
minute: 0,
|
|
43
|
+
}), DateTime.fromObject({
|
|
44
|
+
day: 2,
|
|
45
|
+
month: 4,
|
|
46
|
+
year: 2025,
|
|
47
|
+
hour: 1,
|
|
48
|
+
minute: 0,
|
|
49
|
+
})).as('hours')).toEqual(6);
|
|
50
|
+
});
|
|
51
|
+
test('multiple days, transcends multiple service hours/non services hour ranges', () => {
|
|
52
|
+
expect(calculateDurationWithinServiceHours(DateTime.fromObject({
|
|
53
|
+
day: 1,
|
|
54
|
+
month: 4,
|
|
55
|
+
year: 2025,
|
|
56
|
+
hour: 18,
|
|
57
|
+
minute: 0,
|
|
58
|
+
}), DateTime.fromObject({
|
|
59
|
+
day: 2,
|
|
60
|
+
month: 4,
|
|
61
|
+
year: 2025,
|
|
62
|
+
hour: 18,
|
|
63
|
+
minute: 0,
|
|
64
|
+
})).as('hours')).toEqual(18.5);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
test('outside service hours', () => {
|
|
68
|
+
expect(calculateDurationWithinServiceHours(DateTime.fromObject({
|
|
69
|
+
day: 1,
|
|
70
|
+
month: 4,
|
|
71
|
+
year: 2025,
|
|
72
|
+
hour: 3,
|
|
73
|
+
minute: 0,
|
|
74
|
+
}), DateTime.fromObject({
|
|
75
|
+
day: 1,
|
|
76
|
+
month: 4,
|
|
77
|
+
year: 2025,
|
|
78
|
+
hour: 4,
|
|
79
|
+
minute: 30,
|
|
80
|
+
})).as('hours')).toEqual(0);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=calculateDurationWithinServiceHours.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculateDurationWithinServiceHours.test.js","sourceRoot":"/","sources":["helpers/calculateDurationWithinServiceHours.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, test } from 'vitest';\nimport { calculateDurationWithinServiceHours } from './calculateDurationWithinServiceHours.js';\nimport { DateTime } from 'luxon';\n\ndescribe('calculateDurationWithinServiceHours', () => {\n test('single day, within service hours', () => {\n expect(\n calculateDurationWithinServiceHours(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 8,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 12,\n minute: 30,\n }),\n ).as('hours'),\n ).toEqual(4.5);\n });\n describe('transcends service hour boundaries', () => {\n test('single day, starts before service hours', () => {\n expect(\n calculateDurationWithinServiceHours(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 5,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 6,\n minute: 0,\n }),\n ).as('hours'),\n ).toEqual(0.5);\n });\n test('single day, ends after service hours', () => {\n expect(\n calculateDurationWithinServiceHours(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 1,\n minute: 0,\n }),\n ).as('hours'),\n ).toEqual(6);\n });\n test('multiple days, transcends multiple service hours/non services hour ranges', () => {\n expect(\n calculateDurationWithinServiceHours(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 2,\n month: 4,\n year: 2025,\n hour: 18,\n minute: 0,\n }),\n ).as('hours'),\n ).toEqual(18.5);\n });\n });\n test('outside service hours', () => {\n expect(\n calculateDurationWithinServiceHours(\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 3,\n minute: 0,\n }),\n DateTime.fromObject({\n day: 1,\n month: 4,\n year: 2025,\n hour: 4,\n minute: 30,\n }),\n ).as('hours'),\n ).toEqual(0);\n });\n});\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type IssueBundleState } from '../repo/issue/helpers/deriveCurrentState.js';
|
|
2
|
+
import type { IssueBundle } from '../schema/issue/bundle.js';
|
|
3
|
+
import type { Claim } from '../schema/issue/claim.js';
|
|
4
|
+
import type { ImpactEvent } from '../schema/issue/impactEvent.js';
|
|
5
|
+
type Params = {
|
|
6
|
+
issueBundle: IssueBundle;
|
|
7
|
+
evidenceId: string;
|
|
8
|
+
evidenceTs: string;
|
|
9
|
+
claims: Claim[];
|
|
10
|
+
};
|
|
11
|
+
type Result = {
|
|
12
|
+
newState: IssueBundleState;
|
|
13
|
+
newImpactEvents: ImpactEvent[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Compute the impact from the evidence claims.
|
|
17
|
+
* @param params - The parameters.
|
|
18
|
+
* @returns The result.
|
|
19
|
+
*/
|
|
20
|
+
export declare function computeImpactFromEvidenceClaims(params: Params): Result;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { deepStrictEqual } from 'node:assert';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import { deriveCurrentState, } from '../repo/issue/helpers/deriveCurrentState.js';
|
|
4
|
+
import { assert } from '../util/assert.js';
|
|
5
|
+
import { IdGenerator } from '../write/id/IdGenerator.js';
|
|
6
|
+
import { keyForAffectedEntity } from './keyForAffectedEntity.js';
|
|
7
|
+
/**
|
|
8
|
+
* Compute the impact from the evidence claims.
|
|
9
|
+
* @param params - The parameters.
|
|
10
|
+
* @returns The result.
|
|
11
|
+
*/
|
|
12
|
+
export function computeImpactFromEvidenceClaims(params) {
|
|
13
|
+
const result = {
|
|
14
|
+
newState: {
|
|
15
|
+
services: {},
|
|
16
|
+
servicesProvenance: {},
|
|
17
|
+
facilities: {},
|
|
18
|
+
facilitiesProvenance: {},
|
|
19
|
+
impactEventIds: [],
|
|
20
|
+
},
|
|
21
|
+
newImpactEvents: [],
|
|
22
|
+
};
|
|
23
|
+
const eventTs = params.evidenceTs;
|
|
24
|
+
const eventDateTime = DateTime.fromISO(eventTs);
|
|
25
|
+
assert(eventDateTime.isValid, `Invalid date: ${eventTs}`);
|
|
26
|
+
const currentState = deriveCurrentState(params.issueBundle);
|
|
27
|
+
const claimsKeyedByAffectedEntity = Object.fromEntries(params.claims.map((claim) => [keyForAffectedEntity(claim.entity), claim]));
|
|
28
|
+
for (const [key, claim] of Object.entries(claimsKeyedByAffectedEntity)) {
|
|
29
|
+
switch (claim.entity.type) {
|
|
30
|
+
case 'service': {
|
|
31
|
+
const currentServiceState = currentState.services[key] ?? {
|
|
32
|
+
effect: null,
|
|
33
|
+
scopes: [],
|
|
34
|
+
periods: [],
|
|
35
|
+
causes: [],
|
|
36
|
+
};
|
|
37
|
+
const currentServiceProvenance = currentState.servicesProvenance[key] ?? {};
|
|
38
|
+
if (claim.effect?.service != null &&
|
|
39
|
+
!isEqual(currentServiceState.effect, claim.effect.service)) {
|
|
40
|
+
currentServiceState.effect = claim.effect.service;
|
|
41
|
+
currentServiceProvenance.effect = {
|
|
42
|
+
evidenceId: params.evidenceId,
|
|
43
|
+
};
|
|
44
|
+
result.newImpactEvents.push({
|
|
45
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
46
|
+
type: 'service_effects.set',
|
|
47
|
+
ts: eventTs,
|
|
48
|
+
basis: { evidenceId: params.evidenceId },
|
|
49
|
+
entity: claim.entity,
|
|
50
|
+
effect: claim.effect.service,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
switch (params.issueBundle.issue.type) {
|
|
54
|
+
case 'disruption': {
|
|
55
|
+
const isCleared = currentServiceState.periods.every((period) => period.kind === 'fixed' && period.endAt != null);
|
|
56
|
+
let currentStatus = 'open';
|
|
57
|
+
if (isCleared) {
|
|
58
|
+
currentStatus = 'cleared';
|
|
59
|
+
}
|
|
60
|
+
if (currentStatus !== claim.statusSignal) {
|
|
61
|
+
switch (claim.statusSignal) {
|
|
62
|
+
case 'open': {
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'cleared': {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (claim.timeHints != null) {
|
|
74
|
+
const { newPeriods, hasChanged } = reconcilePeriodsWithTimeHints(currentServiceState.periods, claim.timeHints);
|
|
75
|
+
if (hasChanged) {
|
|
76
|
+
currentServiceState.periods = newPeriods;
|
|
77
|
+
currentServiceProvenance.periods = {
|
|
78
|
+
evidenceId: params.evidenceId,
|
|
79
|
+
};
|
|
80
|
+
result.newImpactEvents.push({
|
|
81
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
82
|
+
type: 'periods.set',
|
|
83
|
+
ts: eventTs,
|
|
84
|
+
basis: { evidenceId: params.evidenceId },
|
|
85
|
+
entity: claim.entity,
|
|
86
|
+
periods: currentServiceState.periods,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (claim.scopes?.service != null &&
|
|
91
|
+
!isEqual(currentServiceState.scopes, claim.scopes.service)) {
|
|
92
|
+
currentServiceState.scopes = claim.scopes.service;
|
|
93
|
+
currentServiceProvenance.scopes = {
|
|
94
|
+
evidenceId: params.evidenceId,
|
|
95
|
+
};
|
|
96
|
+
result.newImpactEvents.push({
|
|
97
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
98
|
+
type: 'service_scopes.set',
|
|
99
|
+
ts: eventTs,
|
|
100
|
+
basis: { evidenceId: params.evidenceId },
|
|
101
|
+
entity: claim.entity,
|
|
102
|
+
serviceScopes: claim.scopes.service,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (claim.causes != null &&
|
|
106
|
+
!isEqual(currentServiceState.causes, claim.causes)) {
|
|
107
|
+
currentServiceState.causes = claim.causes;
|
|
108
|
+
currentServiceProvenance.causes = {
|
|
109
|
+
evidenceId: params.evidenceId,
|
|
110
|
+
};
|
|
111
|
+
result.newImpactEvents.push({
|
|
112
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
113
|
+
type: 'causes.set',
|
|
114
|
+
ts: eventTs,
|
|
115
|
+
basis: { evidenceId: params.evidenceId },
|
|
116
|
+
entity: claim.entity,
|
|
117
|
+
causes: claim.causes,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
result.newState.services[key] = currentServiceState;
|
|
121
|
+
result.newState.servicesProvenance[key] = currentServiceProvenance;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'facility': {
|
|
125
|
+
const currentFacilityState = currentState.facilities[key] ?? {
|
|
126
|
+
effect: null,
|
|
127
|
+
periods: [],
|
|
128
|
+
};
|
|
129
|
+
const currentFacilityProvenance = currentState.facilitiesProvenance[key] ?? {};
|
|
130
|
+
if (claim.effect?.facility != null &&
|
|
131
|
+
!isEqual(currentFacilityState.effect, claim.effect.facility)) {
|
|
132
|
+
currentFacilityState.effect = claim.effect.facility;
|
|
133
|
+
currentFacilityProvenance.effect = {
|
|
134
|
+
evidenceId: params.evidenceId,
|
|
135
|
+
};
|
|
136
|
+
result.newImpactEvents.push({
|
|
137
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
138
|
+
type: 'facility_effects.set',
|
|
139
|
+
ts: eventTs,
|
|
140
|
+
basis: { evidenceId: params.evidenceId },
|
|
141
|
+
entity: claim.entity,
|
|
142
|
+
effect: claim.effect.facility,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (claim.timeHints != null) {
|
|
146
|
+
const { newPeriods, hasChanged } = reconcilePeriodsWithTimeHints(currentFacilityState.periods, claim.timeHints);
|
|
147
|
+
if (hasChanged) {
|
|
148
|
+
currentFacilityState.periods = newPeriods;
|
|
149
|
+
currentFacilityProvenance.periods = {
|
|
150
|
+
evidenceId: params.evidenceId,
|
|
151
|
+
};
|
|
152
|
+
result.newImpactEvents.push({
|
|
153
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
154
|
+
type: 'periods.set',
|
|
155
|
+
ts: eventTs,
|
|
156
|
+
basis: { evidenceId: params.evidenceId },
|
|
157
|
+
entity: claim.entity,
|
|
158
|
+
periods: currentFacilityState.periods,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (claim.causes != null &&
|
|
163
|
+
!isEqual(currentFacilityState.causes, claim.causes)) {
|
|
164
|
+
currentFacilityState.causes = claim.causes;
|
|
165
|
+
currentFacilityProvenance.causes = {
|
|
166
|
+
evidenceId: params.evidenceId,
|
|
167
|
+
};
|
|
168
|
+
result.newImpactEvents.push({
|
|
169
|
+
id: IdGenerator.impactEventId(eventDateTime),
|
|
170
|
+
type: 'causes.set',
|
|
171
|
+
ts: eventTs,
|
|
172
|
+
basis: { evidenceId: params.evidenceId },
|
|
173
|
+
entity: claim.entity,
|
|
174
|
+
causes: claim.causes,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
result.newState.facilities[key] = currentFacilityState;
|
|
178
|
+
result.newState.facilitiesProvenance[key] = currentFacilityProvenance;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Reconcile the periods with the time hints.
|
|
187
|
+
* @param currentPeriods - The current periods.
|
|
188
|
+
* @param timeHints - The time hints.
|
|
189
|
+
* @returns The new periods and whether the periods have changed.
|
|
190
|
+
*/
|
|
191
|
+
function reconcilePeriodsWithTimeHints(currentPeriods, timeHints) {
|
|
192
|
+
switch (timeHints.kind) {
|
|
193
|
+
case 'fixed': {
|
|
194
|
+
return {
|
|
195
|
+
newPeriods: [timeHints],
|
|
196
|
+
hasChanged: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case 'recurring': {
|
|
200
|
+
return {
|
|
201
|
+
newPeriods: [timeHints],
|
|
202
|
+
hasChanged: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
case 'start-only': {
|
|
206
|
+
let hasChanged = false;
|
|
207
|
+
const newPeriods = [];
|
|
208
|
+
if (currentPeriods.length === 0) {
|
|
209
|
+
newPeriods.push({
|
|
210
|
+
kind: 'fixed',
|
|
211
|
+
startAt: timeHints.startAt,
|
|
212
|
+
endAt: null,
|
|
213
|
+
});
|
|
214
|
+
hasChanged = true;
|
|
215
|
+
return { newPeriods, hasChanged };
|
|
216
|
+
}
|
|
217
|
+
for (const period of currentPeriods) {
|
|
218
|
+
switch (period.kind) {
|
|
219
|
+
case 'fixed': {
|
|
220
|
+
newPeriods.push({
|
|
221
|
+
...period,
|
|
222
|
+
startAt: timeHints.startAt,
|
|
223
|
+
});
|
|
224
|
+
hasChanged = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'recurring': {
|
|
228
|
+
const newPeriod = {
|
|
229
|
+
...period,
|
|
230
|
+
startAt: timeHints.startAt,
|
|
231
|
+
};
|
|
232
|
+
if (newPeriod.timeWindow != null) {
|
|
233
|
+
const startAt = DateTime.fromISO(timeHints.startAt);
|
|
234
|
+
assert(startAt.isValid);
|
|
235
|
+
newPeriod.timeWindow.startAt = startAt.toFormat('HH:mm:ss');
|
|
236
|
+
}
|
|
237
|
+
newPeriods.push(newPeriod);
|
|
238
|
+
hasChanged = true;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
newPeriods,
|
|
245
|
+
hasChanged,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
case 'end-only': {
|
|
249
|
+
let hasChanged = false;
|
|
250
|
+
const newPeriods = [];
|
|
251
|
+
for (const period of currentPeriods) {
|
|
252
|
+
switch (period.kind) {
|
|
253
|
+
case 'fixed': {
|
|
254
|
+
newPeriods.push({
|
|
255
|
+
...period,
|
|
256
|
+
endAt: timeHints.endAt,
|
|
257
|
+
});
|
|
258
|
+
hasChanged = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'recurring': {
|
|
262
|
+
const newPeriod = {
|
|
263
|
+
...period,
|
|
264
|
+
endAt: timeHints.endAt,
|
|
265
|
+
};
|
|
266
|
+
if (newPeriod.timeWindow != null) {
|
|
267
|
+
const endAt = DateTime.fromISO(timeHints.endAt);
|
|
268
|
+
assert(endAt.isValid);
|
|
269
|
+
newPeriod.timeWindow.endAt = endAt.toFormat('HH:mm:ss');
|
|
270
|
+
}
|
|
271
|
+
newPeriods.push(newPeriod);
|
|
272
|
+
hasChanged = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
newPeriods,
|
|
279
|
+
hasChanged,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function isEqual(a, b) {
|
|
285
|
+
try {
|
|
286
|
+
deepStrictEqual(a, b);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=computeImpactFromEvidenceClaims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computeImpactFromEvidenceClaims.js","sourceRoot":"/","sources":["helpers/computeImpactFromEvidenceClaims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EACL,kBAAkB,GAEnB,MAAM,6CAA6C,CAAC;AASrD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAcjE;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAAC,MAAc;IAC5D,MAAM,MAAM,GAAW;QACrB,QAAQ,EAAE;YACR,QAAQ,EAAE,EAAE;YACZ,kBAAkB,EAAE,EAAE;YACtB,UAAU,EAAE,EAAE;YACd,oBAAoB,EAAE,EAAE;YACxB,cAAc,EAAE,EAAE;SACnB;QACD,eAAe,EAAE,EAAE;KACpB,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC;IAClC,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,iBAAiB,OAAO,EAAE,CAAC,CAAC;IAE1D,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAE5D,MAAM,2BAA2B,GAA0B,MAAM,CAAC,WAAW,CAC3E,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAC1E,CAAC;IAEF,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,2BAA2B,CAAC,EAAE,CAAC;QACvE,QAAQ,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC1B,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,MAAM,mBAAmB,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI;oBACxD,MAAM,EAAE,IAAI;oBACZ,MAAM,EAAE,EAAE;oBACV,OAAO,EAAE,EAAE;oBACX,MAAM,EAAE,EAAE;iBACX,CAAC;gBACF,MAAM,wBAAwB,GAC5B,YAAY,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBAE7C,IACE,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI;oBAC7B,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAC1D,CAAC;oBACD,mBAAmB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;oBAClD,wBAAwB,CAAC,MAAM,GAAG;wBAChC,UAAU,EAAE,MAAM,CAAC,UAAU;qBAC9B,CAAC;oBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;wBAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;wBAC5C,IAAI,EAAE,qBAAqB;wBAC3B,EAAE,EAAE,OAAO;wBACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;wBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;qBAC7B,CAAC,CAAC;gBACL,CAAC;gBAED,QAAQ,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;oBACtC,KAAK,YAAY,CAAC,CAAC,CAAC;wBAClB,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,KAAK,CACjD,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAC5D,CAAC;wBAEF,IAAI,aAAa,GAAsB,MAAM,CAAC;wBAE9C,IAAI,SAAS,EAAE,CAAC;4BACd,aAAa,GAAG,SAAS,CAAC;wBAC5B,CAAC;wBAED,IAAI,aAAa,KAAK,KAAK,CAAC,YAAY,EAAE,CAAC;4BACzC,QAAQ,KAAK,CAAC,YAAY,EAAE,CAAC;gCAC3B,KAAK,MAAM,CAAC,CAAC,CAAC;oCACZ,MAAM;gCACR,CAAC;gCACD,KAAK,SAAS,CAAC,CAAC,CAAC;oCACf,MAAM;gCACR,CAAC;4BACH,CAAC;wBACH,CAAC;wBACD,MAAM;oBACR,CAAC;gBACH,CAAC;gBAED,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;oBAC5B,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,6BAA6B,CAC9D,mBAAmB,CAAC,OAAO,EAC3B,KAAK,CAAC,SAAS,CAChB,CAAC;oBACF,IAAI,UAAU,EAAE,CAAC;wBACf,mBAAmB,CAAC,OAAO,GAAG,UAAU,CAAC;wBACzC,wBAAwB,CAAC,OAAO,GAAG;4BACjC,UAAU,EAAE,MAAM,CAAC,UAAU;yBAC9B,CAAC;wBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;4BAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;4BAC5C,IAAI,EAAE,aAAa;4BACnB,EAAE,EAAE,OAAO;4BACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;4BACxC,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,OAAO,EAAE,mBAAmB,CAAC,OAAO;yBACrC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;gBAED,IACE,KAAK,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI;oBAC7B,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAC1D,CAAC;oBACD,mBAAmB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;oBAClD,wBAAwB,CAAC,MAAM,GAAG;wBAChC,UAAU,EAAE,MAAM,CAAC,UAAU;qBAC9B,CAAC;oBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;wBAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;wBAC5C,IAAI,EAAE,oBAAoB;wBAC1B,EAAE,EAAE,OAAO;wBACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;wBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;qBACpC,CAAC,CAAC;gBACL,CAAC;gBAED,IACE,KAAK,CAAC,MAAM,IAAI,IAAI;oBACpB,CAAC,OAAO,CAAC,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAClD,CAAC;oBACD,mBAAmB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;oBAC1C,wBAAwB,CAAC,MAAM,GAAG;wBAChC,UAAU,EAAE,MAAM,CAAC,UAAU;qBAC9B,CAAC;oBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;wBAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;wBAC5C,IAAI,EAAE,YAAY;wBAClB,EAAE,EAAE,OAAO;wBACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;wBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,MAAM,EAAE,KAAK,CAAC,MAAM;qBACrB,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC;gBACpD,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,wBAAwB,CAAC;gBAEnE,MAAM;YACR,CAAC;YACD,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,MAAM,oBAAoB,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI;oBAC3D,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,EAAE;iBACZ,CAAC;gBACF,MAAM,yBAAyB,GAC7B,YAAY,CAAC,oBAAoB,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBAE/C,IACE,KAAK,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI;oBAC9B,CAAC,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAC5D,CAAC;oBACD,oBAAoB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;oBACpD,yBAAyB,CAAC,MAAM,GAAG;wBACjC,UAAU,EAAE,MAAM,CAAC,UAAU;qBAC9B,CAAC;oBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;wBAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;wBAC5C,IAAI,EAAE,sBAAsB;wBAC5B,EAAE,EAAE,OAAO;wBACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;wBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ;qBAC9B,CAAC,CAAC;gBACL,CAAC;gBAED,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;oBAC5B,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,6BAA6B,CAC9D,oBAAoB,CAAC,OAAO,EAC5B,KAAK,CAAC,SAAS,CAChB,CAAC;oBACF,IAAI,UAAU,EAAE,CAAC;wBACf,oBAAoB,CAAC,OAAO,GAAG,UAAU,CAAC;wBAC1C,yBAAyB,CAAC,OAAO,GAAG;4BAClC,UAAU,EAAE,MAAM,CAAC,UAAU;yBAC9B,CAAC;wBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;4BAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;4BAC5C,IAAI,EAAE,aAAa;4BACnB,EAAE,EAAE,OAAO;4BACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;4BACxC,MAAM,EAAE,KAAK,CAAC,MAAM;4BACpB,OAAO,EAAE,oBAAoB,CAAC,OAAO;yBACtC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;gBAED,IACE,KAAK,CAAC,MAAM,IAAI,IAAI;oBACpB,CAAC,OAAO,CAAC,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EACnD,CAAC;oBACD,oBAAoB,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;oBAC3C,yBAAyB,CAAC,MAAM,GAAG;wBACjC,UAAU,EAAE,MAAM,CAAC,UAAU;qBAC9B,CAAC;oBACF,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC;wBAC1B,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,aAAa,CAAC;wBAC5C,IAAI,EAAE,YAAY;wBAClB,EAAE,EAAE,OAAO;wBACX,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;wBACxC,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,MAAM,EAAE,KAAK,CAAC,MAAM;qBACrB,CAAC,CAAC;gBACL,CAAC;gBAED,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,oBAAoB,CAAC;gBACvD,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,GAAG,CAAC,GAAG,yBAAyB,CAAC;gBAEtE,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAOD;;;;;GAKG;AACH,SAAS,6BAA6B,CACpC,cAAwB,EACxB,SAAyB;IAEzB,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACvB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO;gBACL,UAAU,EAAE,CAAC,SAAS,CAAC;gBACvB,UAAU,EAAE,IAAI;aACjB,CAAC;QACJ,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO;gBACL,UAAU,EAAE,CAAC,SAAS,CAAC;gBACvB,UAAU,EAAE,IAAI;aACjB,CAAC;QACJ,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,MAAM,UAAU,GAAa,EAAE,CAAC;YAChC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChC,UAAU,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,SAAS,CAAC,OAAO;oBAC1B,KAAK,EAAE,IAAI;iBACZ,CAAC,CAAC;gBACH,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;YACpC,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;gBACpC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;oBACpB,KAAK,OAAO,CAAC,CAAC,CAAC;wBACb,UAAU,CAAC,IAAI,CAAC;4BACd,GAAG,MAAM;4BACT,OAAO,EAAE,SAAS,CAAC,OAAO;yBAC3B,CAAC,CAAC;wBACH,UAAU,GAAG,IAAI,CAAC;wBAClB,MAAM;oBACR,CAAC;oBACD,KAAK,WAAW,CAAC,CAAC,CAAC;wBACjB,MAAM,SAAS,GAAW;4BACxB,GAAG,MAAM;4BACT,OAAO,EAAE,SAAS,CAAC,OAAO;yBAC3B,CAAC;wBACF,IAAI,SAAS,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;4BACjC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;4BACpD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;4BACxB,SAAS,CAAC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;wBAC9D,CAAC;wBACD,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC3B,UAAU,GAAG,IAAI,CAAC;wBAClB,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO;gBACL,UAAU;gBACV,UAAU;aACX,CAAC;QACJ,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,MAAM,UAAU,GAAa,EAAE,CAAC;YAChC,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;gBACpC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;oBACpB,KAAK,OAAO,CAAC,CAAC,CAAC;wBACb,UAAU,CAAC,IAAI,CAAC;4BACd,GAAG,MAAM;4BACT,KAAK,EAAE,SAAS,CAAC,KAAK;yBACvB,CAAC,CAAC;wBACH,UAAU,GAAG,IAAI,CAAC;wBAClB,MAAM;oBACR,CAAC;oBACD,KAAK,WAAW,CAAC,CAAC,CAAC;wBACjB,MAAM,SAAS,GAAW;4BACxB,GAAG,MAAM;4BACT,KAAK,EAAE,SAAS,CAAC,KAAK;yBACvB,CAAC;wBACF,IAAI,SAAS,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;4BACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;4BAChD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;4BACtB,SAAS,CAAC,UAAU,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;wBAC1D,CAAC;wBACD,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC3B,UAAU,GAAG,IAAI,CAAC;wBAClB,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO;gBACL,UAAU;gBACV,UAAU;aACX,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,CAAU,EAAE,CAAU;IACrC,IAAI,CAAC;QACH,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { deepStrictEqual } from 'node:assert';\nimport { DateTime } from 'luxon';\nimport {\n deriveCurrentState,\n type IssueBundleState,\n} from '../repo/issue/helpers/deriveCurrentState.js';\nimport type { IssueBundle } from '../schema/issue/bundle.js';\nimport type {\n Claim,\n ClaimStatusSignal,\n ClaimTimeHints,\n} from '../schema/issue/claim.js';\nimport type { ImpactEvent } from '../schema/issue/impactEvent.js';\nimport type { Period } from '../schema/issue/period.js';\nimport { assert } from '../util/assert.js';\nimport { IdGenerator } from '../write/id/IdGenerator.js';\nimport { keyForAffectedEntity } from './keyForAffectedEntity.js';\n\ntype Params = {\n issueBundle: IssueBundle;\n evidenceId: string;\n evidenceTs: string;\n claims: Claim[];\n};\n\ntype Result = {\n newState: IssueBundleState;\n newImpactEvents: ImpactEvent[];\n};\n\n/**\n * Compute the impact from the evidence claims.\n * @param params - The parameters.\n * @returns The result.\n */\nexport function computeImpactFromEvidenceClaims(params: Params): Result {\n const result: Result = {\n newState: {\n services: {},\n servicesProvenance: {},\n facilities: {},\n facilitiesProvenance: {},\n impactEventIds: [],\n },\n newImpactEvents: [],\n };\n\n const eventTs = params.evidenceTs;\n const eventDateTime = DateTime.fromISO(eventTs);\n assert(eventDateTime.isValid, `Invalid date: ${eventTs}`);\n\n const currentState = deriveCurrentState(params.issueBundle);\n\n const claimsKeyedByAffectedEntity: Record<string, Claim> = Object.fromEntries(\n params.claims.map((claim) => [keyForAffectedEntity(claim.entity), claim]),\n );\n\n for (const [key, claim] of Object.entries(claimsKeyedByAffectedEntity)) {\n switch (claim.entity.type) {\n case 'service': {\n const currentServiceState = currentState.services[key] ?? {\n effect: null,\n scopes: [],\n periods: [],\n causes: [],\n };\n const currentServiceProvenance =\n currentState.servicesProvenance[key] ?? {};\n\n if (\n claim.effect?.service != null &&\n !isEqual(currentServiceState.effect, claim.effect.service)\n ) {\n currentServiceState.effect = claim.effect.service;\n currentServiceProvenance.effect = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'service_effects.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n effect: claim.effect.service,\n });\n }\n\n switch (params.issueBundle.issue.type) {\n case 'disruption': {\n const isCleared = currentServiceState.periods.every(\n (period) => period.kind === 'fixed' && period.endAt != null,\n );\n\n let currentStatus: ClaimStatusSignal = 'open';\n\n if (isCleared) {\n currentStatus = 'cleared';\n }\n\n if (currentStatus !== claim.statusSignal) {\n switch (claim.statusSignal) {\n case 'open': {\n break;\n }\n case 'cleared': {\n break;\n }\n }\n }\n break;\n }\n }\n\n if (claim.timeHints != null) {\n const { newPeriods, hasChanged } = reconcilePeriodsWithTimeHints(\n currentServiceState.periods,\n claim.timeHints,\n );\n if (hasChanged) {\n currentServiceState.periods = newPeriods;\n currentServiceProvenance.periods = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'periods.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n periods: currentServiceState.periods,\n });\n }\n }\n\n if (\n claim.scopes?.service != null &&\n !isEqual(currentServiceState.scopes, claim.scopes.service)\n ) {\n currentServiceState.scopes = claim.scopes.service;\n currentServiceProvenance.scopes = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'service_scopes.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n serviceScopes: claim.scopes.service,\n });\n }\n\n if (\n claim.causes != null &&\n !isEqual(currentServiceState.causes, claim.causes)\n ) {\n currentServiceState.causes = claim.causes;\n currentServiceProvenance.causes = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'causes.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n causes: claim.causes,\n });\n }\n\n result.newState.services[key] = currentServiceState;\n result.newState.servicesProvenance[key] = currentServiceProvenance;\n\n break;\n }\n case 'facility': {\n const currentFacilityState = currentState.facilities[key] ?? {\n effect: null,\n periods: [],\n };\n const currentFacilityProvenance =\n currentState.facilitiesProvenance[key] ?? {};\n\n if (\n claim.effect?.facility != null &&\n !isEqual(currentFacilityState.effect, claim.effect.facility)\n ) {\n currentFacilityState.effect = claim.effect.facility;\n currentFacilityProvenance.effect = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'facility_effects.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n effect: claim.effect.facility,\n });\n }\n\n if (claim.timeHints != null) {\n const { newPeriods, hasChanged } = reconcilePeriodsWithTimeHints(\n currentFacilityState.periods,\n claim.timeHints,\n );\n if (hasChanged) {\n currentFacilityState.periods = newPeriods;\n currentFacilityProvenance.periods = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'periods.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n periods: currentFacilityState.periods,\n });\n }\n }\n\n if (\n claim.causes != null &&\n !isEqual(currentFacilityState.causes, claim.causes)\n ) {\n currentFacilityState.causes = claim.causes;\n currentFacilityProvenance.causes = {\n evidenceId: params.evidenceId,\n };\n result.newImpactEvents.push({\n id: IdGenerator.impactEventId(eventDateTime),\n type: 'causes.set',\n ts: eventTs,\n basis: { evidenceId: params.evidenceId },\n entity: claim.entity,\n causes: claim.causes,\n });\n }\n\n result.newState.facilities[key] = currentFacilityState;\n result.newState.facilitiesProvenance[key] = currentFacilityProvenance;\n\n break;\n }\n }\n }\n\n return result;\n}\n\ntype ReconcilePeriodsWithTimeHintsResults = {\n newPeriods: Period[];\n hasChanged: boolean;\n};\n\n/**\n * Reconcile the periods with the time hints.\n * @param currentPeriods - The current periods.\n * @param timeHints - The time hints.\n * @returns The new periods and whether the periods have changed.\n */\nfunction reconcilePeriodsWithTimeHints(\n currentPeriods: Period[],\n timeHints: ClaimTimeHints,\n): ReconcilePeriodsWithTimeHintsResults {\n switch (timeHints.kind) {\n case 'fixed': {\n return {\n newPeriods: [timeHints],\n hasChanged: true,\n };\n }\n case 'recurring': {\n return {\n newPeriods: [timeHints],\n hasChanged: true,\n };\n }\n case 'start-only': {\n let hasChanged = false;\n const newPeriods: Period[] = [];\n if (currentPeriods.length === 0) {\n newPeriods.push({\n kind: 'fixed',\n startAt: timeHints.startAt,\n endAt: null,\n });\n hasChanged = true;\n return { newPeriods, hasChanged };\n }\n for (const period of currentPeriods) {\n switch (period.kind) {\n case 'fixed': {\n newPeriods.push({\n ...period,\n startAt: timeHints.startAt,\n });\n hasChanged = true;\n break;\n }\n case 'recurring': {\n const newPeriod: Period = {\n ...period,\n startAt: timeHints.startAt,\n };\n if (newPeriod.timeWindow != null) {\n const startAt = DateTime.fromISO(timeHints.startAt);\n assert(startAt.isValid);\n newPeriod.timeWindow.startAt = startAt.toFormat('HH:mm:ss');\n }\n newPeriods.push(newPeriod);\n hasChanged = true;\n break;\n }\n }\n }\n return {\n newPeriods,\n hasChanged,\n };\n }\n case 'end-only': {\n let hasChanged = false;\n const newPeriods: Period[] = [];\n for (const period of currentPeriods) {\n switch (period.kind) {\n case 'fixed': {\n newPeriods.push({\n ...period,\n endAt: timeHints.endAt,\n });\n hasChanged = true;\n break;\n }\n case 'recurring': {\n const newPeriod: Period = {\n ...period,\n endAt: timeHints.endAt,\n };\n if (newPeriod.timeWindow != null) {\n const endAt = DateTime.fromISO(timeHints.endAt);\n assert(endAt.isValid);\n newPeriod.timeWindow.endAt = endAt.toFormat('HH:mm:ss');\n }\n newPeriods.push(newPeriod);\n hasChanged = true;\n break;\n }\n }\n }\n return {\n newPeriods,\n hasChanged,\n };\n }\n }\n}\n\nfunction isEqual(a: unknown, b: unknown): boolean {\n try {\n deepStrictEqual(a, b);\n return true;\n } catch (error) {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|