@motionstudies/core 0.1.0-alpha.18 → 0.1.0-alpha.19
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 +1 -1
- package/domain/published-day.d.ts +8 -0
- package/domain/published-day.js +48 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -246,7 +246,7 @@ These transport-neutral modules came from Zugunruhe's radar-field studies and se
|
|
|
246
246
|
|
|
247
247
|
Render support as opacity or reach, not as confidence in the underlying measurement: it says how far the nearest evidence is, nothing about that evidence's accuracy. The kernel scale is a claim about resolution and belongs in the study's provenance.
|
|
248
248
|
|
|
249
|
-
`@motionstudies/core/domain/published-day` reads a published day of the national layer as the recording host compiles it: `openPublishedDay` takes a transport (`fetchTransport` over a base URL, or any pair of text and bytes readers) and returns the manifest and index up front, then slices, members and packs on demand and cached per file; `partFor` resolves an operator and vehicle to the pack part that holds it; `sliceNameAt` and `cellOf` reproduce the compiler's slice and cell rules exactly, so a position lands in the cell the compiler put it in; `positionAt` interpolates along a track only inside a tolerated gap, and `journeyAt` finds the run in force at an instant. Packs are gunzipped with the platform's `DecompressionStream` unless an inflater is supplied.
|
|
249
|
+
`@motionstudies/core/domain/published-day` reads a published day of the national layer as the recording host compiles it: `openPublishedDay` takes a transport (`fetchTransport` over a base URL, or any pair of text and bytes readers) and returns the manifest and index up front, then slices, members and packs on demand and cached per file; `partFor` resolves an operator and vehicle to the pack part that holds it; `sliceNameAt` and `cellOf` reproduce the compiler's slice and cell rules exactly, so a position lands in the cell the compiler put it in; `positionAt` interpolates along a track only inside a tolerated gap, and `journeyAt` finds the run in force at an instant. Before a composition is built on a day, `checkDayManifest` reports any disagreement between the manifest's hours, slice count, file list and totals, and `reconcileSlice` reports any cell whose distinct-vehicle count differs from its members or that appears in only one of the two files; both return an empty list when consistent. A trimmed fixture fails the manifest check by design. Packs are gunzipped with the platform's `DecompressionStream` unless an inflater is supplied.
|
|
250
250
|
|
|
251
251
|
`@motionstudies/core/domain/gridded-series` reads a regular latitude/longitude grid of variables at listed instants. `bracketTime` finds the adjacent frames; `sampleGriddedSeries` interpolates bilinearly in space and linearly in time; `meanGriddedSeries` returns an area-weighted mean, with an optional valid range. A missing sample with any weight makes the reading unavailable instead of bridging the gap. Values keep the source's units; the consumer converts percentages or applies its own scale.
|
|
252
252
|
|
|
@@ -89,6 +89,14 @@ export declare function cellOf(lon: number, lat: number): readonly [number, numb
|
|
|
89
89
|
export declare function cellKey(cellLon: number, cellLat: number): string;
|
|
90
90
|
/** The pack part holding a vehicle: the only part, or the part whose reference list names it. */
|
|
91
91
|
export declare function partFor(index: DayIndex, operatorRef: string, vehicleRef: string): PackPart | null;
|
|
92
|
+
/** Problems with a compiled day's manifest, empty when consistent: all 24 hours of the date accounted for once, one
|
|
93
|
+
* slice and one members file per slice, and the file list agreeing with the stated file and byte totals (the manifest
|
|
94
|
+
* itself is the one unlisted file). A trimmed fixture, whose statistics describe the whole day, fails by design. */
|
|
95
|
+
export declare function checkDayManifest(manifest: PublishedDayManifest): string[];
|
|
96
|
+
/** Problems reconciling a slice with its members, empty when they agree: the sea and the shoal must count the same
|
|
97
|
+
* vehicles. Each cell's distinct-vehicle count equals its distinct members, no member repeats, and neither file names
|
|
98
|
+
* a cell the other lacks. Samples are not checked here; members carry identities, not sample counts. */
|
|
99
|
+
export declare function reconcileSlice(slice: SliceFile, members: MembersFile): string[];
|
|
92
100
|
/** Position along a track at an instant: linear between the samples around it, null outside the track or across a
|
|
93
101
|
* gap longer than the tolerance, so a vehicle that stopped reporting does not glide. */
|
|
94
102
|
export declare function positionAt(track: readonly TrackPoint[], epochSeconds: number, maxGapSeconds?: number): readonly [lon: number, lat: number] | null;
|
package/domain/published-day.js
CHANGED
|
@@ -31,6 +31,54 @@ export function partFor(index, operatorRef, vehicleRef) {
|
|
|
31
31
|
return op.parts[0];
|
|
32
32
|
return op.parts.find(p => p.refs?.includes(vehicleRef)) ?? null;
|
|
33
33
|
}
|
|
34
|
+
/** Problems with a compiled day's manifest, empty when consistent: all 24 hours of the date accounted for once, one
|
|
35
|
+
* slice and one members file per slice, and the file list agreeing with the stated file and byte totals (the manifest
|
|
36
|
+
* itself is the one unlisted file). A trimmed fixture, whose statistics describe the whole day, fails by design. */
|
|
37
|
+
export function checkDayManifest(manifest) {
|
|
38
|
+
const problems = [], hours = [...manifest.hours.present, ...manifest.hours.missing], distinct = new Set(hours).size;
|
|
39
|
+
if (hours.length !== 24 || distinct !== 24)
|
|
40
|
+
problems.push(`Hours present and missing account for ${distinct} distinct of 24, listed ${hours.length} times`);
|
|
41
|
+
if (hours.some(h => !h.startsWith(`${manifest.date}T`)))
|
|
42
|
+
problems.push('An hour lies outside the manifest date');
|
|
43
|
+
const count = (prefix) => manifest.list.filter(f => f.path.startsWith(prefix)).length;
|
|
44
|
+
if (manifest.stats.slices !== manifest.slices)
|
|
45
|
+
problems.push(`Statistics count ${manifest.stats.slices} slices; the manifest ${manifest.slices}`);
|
|
46
|
+
if (count('slices/') !== manifest.slices)
|
|
47
|
+
problems.push(`${count('slices/')} slice files listed for ${manifest.slices} slices`);
|
|
48
|
+
if (count('members/') !== manifest.slices)
|
|
49
|
+
problems.push(`${count('members/')} members files listed for ${manifest.slices} slices`);
|
|
50
|
+
if (manifest.list.length + 1 !== manifest.files)
|
|
51
|
+
problems.push(`${manifest.list.length} listed files and the manifest disagree with ${manifest.files} files`);
|
|
52
|
+
const bytes = manifest.list.reduce((a, f) => a + f.bytes, 0);
|
|
53
|
+
if (bytes !== manifest.bytes)
|
|
54
|
+
problems.push(`Listed files hold ${bytes} bytes; the manifest states ${manifest.bytes}`);
|
|
55
|
+
return problems;
|
|
56
|
+
}
|
|
57
|
+
/** Problems reconciling a slice with its members, empty when they agree: the sea and the shoal must count the same
|
|
58
|
+
* vehicles. Each cell's distinct-vehicle count equals its distinct members, no member repeats, and neither file names
|
|
59
|
+
* a cell the other lacks. Samples are not checked here; members carry identities, not sample counts. */
|
|
60
|
+
export function reconcileSlice(slice, members) {
|
|
61
|
+
if (slice.slice !== members.slice)
|
|
62
|
+
return [`Slice ${slice.slice} paired with members of ${members.slice}`];
|
|
63
|
+
const problems = [], seen = new Set();
|
|
64
|
+
for (const [lon, lat, , vehicles] of slice.cells) {
|
|
65
|
+
const key = cellKey(lon, lat), refs = members.cells[key];
|
|
66
|
+
seen.add(key);
|
|
67
|
+
if (!refs) {
|
|
68
|
+
problems.push(`Cell ${key} counts ${vehicles} vehicles and has no members`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const unique = new Set(refs.map(member => JSON.stringify(member))).size;
|
|
72
|
+
if (unique !== refs.length)
|
|
73
|
+
problems.push(`Cell ${key} repeats a member`);
|
|
74
|
+
if (unique !== vehicles)
|
|
75
|
+
problems.push(`Cell ${key} counts ${vehicles} vehicles but lists ${unique} members`);
|
|
76
|
+
}
|
|
77
|
+
for (const key of Object.keys(members.cells))
|
|
78
|
+
if (!seen.has(key))
|
|
79
|
+
problems.push(`Members name cell ${key}, absent from the slice`);
|
|
80
|
+
return problems;
|
|
81
|
+
}
|
|
34
82
|
/** Position along a track at an instant: linear between the samples around it, null outside the track or across a
|
|
35
83
|
* gap longer than the tolerance, so a vehicle that stopped reporting does not glide. */
|
|
36
84
|
export function positionAt(track, epochSeconds, maxGapSeconds = 180) {
|