universal-parcel-scraper 0.993.0-main.5345 → 0.993.1-main.5347

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.
@@ -5,3 +5,5 @@ export declare const DHLINK_RECORD_ZONE = "Asia/Shanghai";
5
5
  * gives that instant.
6
6
  */
7
7
  export declare function usClockCopy(wall: string, timestamp: number): boolean;
8
+ /** The US zone, the state's first, on whose clock `wall` strikes at `timestamp`; null when none does. */
9
+ export declare function usClockZone(wall: string, timestamp: number, state?: string | null): string | null;
@@ -10,5 +10,9 @@ const US_ZONES = ['America/New_York', 'America/Chicago', 'America/Denver', 'Amer
10
10
  * gives that instant.
11
11
  */
12
12
  export function usClockCopy(wall, timestamp) {
13
- return US_ZONES.some((zone) => isoTime(wall, zone)?.timestamp === timestamp);
13
+ return usClockZone(wall, timestamp) !== null;
14
+ }
15
+ /** The US zone, the state's first, on whose clock `wall` strikes at `timestamp`; null when none does. */
16
+ export function usClockZone(wall, timestamp, state) {
17
+ return [...(state ? [state] : []), ...US_ZONES].find((zone) => isoTime(wall, zone)?.timestamp === timestamp) ?? null;
14
18
  }
@@ -7,7 +7,7 @@ import { countryCode, firstOccurrence, isoTime, usStateTimeZone } from '../../co
7
7
  import { cleanScalar } from '../../core/transport/text.js';
8
8
  import { isRecord } from '../../core/types.js';
9
9
  import { countryZones } from '../../places/zones.js';
10
- import { DHLINK_RECORD_ZONE as RECORD_ZONE, usClockCopy } from './clock.js';
10
+ import { DHLINK_RECORD_ZONE as RECORD_ZONE, usClockCopy, usClockZone } from './clock.js';
11
11
  import { dhlinkEnglish, dhlinkStage, FLIGHT_BOOKED } from './status.js';
12
12
  export const PROVIDER = 'DHLink';
13
13
  // A long history is a few dozen kilobytes.
@@ -179,6 +179,10 @@ function projectRow(row, readAt) {
179
179
  return { stamp: local, event, rank, china, state, hub: usStateTimeZone(hub) !== null,
180
180
  recorded: Math.min(recordedAt(row) ?? readAt, readAt) };
181
181
  }
182
+ /** A relayed wording without the town and state a US partner can write before it ("Sampletown IL, "). */
183
+ function unplaced(description) {
184
+ return (description ?? '').replace(/^[A-Z][A-Za-z .'-]{0,40} [A-Z]{2}, /, '');
185
+ }
182
186
  function outranks(left, right) {
183
187
  const index = left.findIndex((value, position) => value !== right[position]);
184
188
  return index >= 0 && left[index] > right[index];
@@ -259,6 +263,29 @@ export function parseDhlink(payload, raw, readAt = Date.now()) {
259
263
  const zone = scanZone(scan, destination, last?.leg.beijingClock === true && !(scan.hub && last.leg.hubClockPickup));
260
264
  return { scan, zone, time: zone ? isoTime(scan.stamp, zone) : null };
261
265
  });
266
+ // A US partner's scan can come twice, as UniUni's do: on its hub's wall clock, often after the
267
+ // hub's town, and on UTC. The UTC copy's clock is the other's on a US reading, to the second,
268
+ // before DHLink recorded either: one scan, on that US clock.
269
+ const utcCopies = new Set();
270
+ if (destination === 'US') {
271
+ for (const local of dated) {
272
+ if (utcCopies.has(local))
273
+ continue;
274
+ const wording = unplaced(local.scan.event.description);
275
+ const copy = dated.find((other) => {
276
+ if (other === local || utcCopies.has(other) || unplaced(other.scan.event.description) !== wording)
277
+ return false;
278
+ const instant = isoTime(other.scan.stamp, 'UTC')?.timestamp;
279
+ return instant !== undefined && instant <= Math.min(local.scan.recorded, other.scan.recorded) + CLOCK_SKEW_MS
280
+ && usClockZone(local.scan.stamp, instant, local.scan.state) !== null;
281
+ });
282
+ if (!copy)
283
+ continue;
284
+ const zone = usClockZone(local.scan.stamp, isoTime(copy.scan.stamp, 'UTC').timestamp, local.scan.state);
285
+ Object.assign(local, { zone, time: isoTime(local.scan.stamp, zone) });
286
+ utcCopies.add(copy);
287
+ }
288
+ }
262
289
  // Beside DHLink's own record of a partner's scan on Beijing time, the partner's copy can come
263
290
  // on its US wall clock: the same wording, at the instant of DHLink's row on a US reading.
264
291
  // Without a partner known to be relayed on Beijing time, DHLink's row must be read on it.
@@ -266,7 +293,7 @@ export function parseDhlink(payload, raw, readAt = Date.now()) {
266
293
  && other.scan.event.description === scan.event.description && usClockCopy(scan.stamp, other.time.timestamp))) : [];
267
294
  // A place naming only the destination country is the parcel's, not the scan's: a Canada line's
268
295
  // airline hand-over in China reads "Canada".
269
- const kept = dated.filter((entry) => !copies.includes(entry));
296
+ const kept = dated.filter((entry) => !copies.includes(entry) && !utcCopies.has(entry));
270
297
  const events = kept
271
298
  .map(({ scan, time }) => {
272
299
  const { location, ...event } = scan.event;
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.993.0-main.5345"
5
+ "version": "0.993.1-main.5347"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.993.0-main.5345",
3
+ "version": "0.993.1-main.5347",
4
4
  "description": "Self-hosted parcel tracking: carrier detection, dedicated scrapers and optional universal providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",