universal-parcel-scraper 0.856.1-main.4572 → 0.856.2-main.4574

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 CHANGED
@@ -163,7 +163,7 @@ the command line's `track --source ParcelsApp` does the same.
163
163
  <!-- GENERATED:stages -->
164
164
  <img src="docs/assets/stages.svg" alt="DHL: Die Sendung wurde in das Zustellfahrzeug geladen.; Mondial Relay: En cours de livraison; Correios Brazil: Objeto saiu para entrega ao destinatário; Correos Express: EN REPARTO; Yamato Transport: 配達中; La Poste / Colissimo: DISTOU. All are filed under out_for_delivery." width="760">
165
165
 
166
- The carrier folders record 17,848 such statuses, each filed under one stage.
166
+ The carrier folders record 17,863 such statuses, each filed under one stage.
167
167
  <!-- /GENERATED:stages -->
168
168
 
169
169
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
@@ -182,15 +182,15 @@ export function chinaPostEnglish(wording) {
182
182
  }
183
183
  // China Post's domestic scans as a forwarder or a provider relays them in Chinese: an arrival at an
184
184
  // office or a departure from one, whether its office is still in the wording or has become the scan's
185
- // place, a sorting, a flight between two cities, the closing of an export dispatch, its hand-over
186
- // to the carrier that flies it and the airline's receipt and departure, and export customs'
187
- // presentation and release.
185
+ // place, a sorting or a dispatch to the delivering office, a flight between two cities, the closing of
186
+ // an export dispatch, its hand-over to the carrier that flies it and the airline's receipt and
187
+ // departure, and export customs' presentation and release.
188
188
  const RELAYED_STAGES = [
189
189
  [/^邮件(?:到达|离开)(?:$|[【,])/u, 'in_transit'],
190
190
  [/^邮件已在【[^【】]+】完成分拣,准备发出$/u, 'in_transit'],
191
191
  [/^邮件已乘机由【[^【】]+】飞往【[^【】]+】$/u, 'in_transit'],
192
192
  [/^【[^【】]+】已出口直封$/u, 'in_transit'],
193
- [/^已交承运商运输$/u, 'in_transit'],
193
+ [/^(?:已交承运商运输|揽投配发)$/u, 'in_transit'],
194
194
  [/^航空公司(?:接收|启运)$/u, 'in_transit'],
195
195
  [/^送交出口海关$/u, 'customs'],
196
196
  [/^出口海关\/放行$/u, 'in_transit'],
@@ -1,8 +1,11 @@
1
1
  import { normalizeHualeiNumber, parseHualeiPage, parseHualeiTrack } from '../gzyl/hualei.js';
2
- import { jrtEnglish, jrtStage } from './status.js';
2
+ import { isJrtUpsRow, jrtEnglish, jrtStage } from './status.js';
3
3
  export const PROVIDER = 'JRT';
4
4
  export const JRT_ORIGIN = 'http://106.53.64.22:8082';
5
- export const JRT = { carrier: 'jrt', provider: PROVIDER, origin: JRT_ORIGIN, stage: jrtStage, english: jrtEnglish };
5
+ export const JRT = {
6
+ carrier: 'jrt', provider: PROVIDER, origin: JRT_ORIGIN, stage: jrtStage, english: jrtEnglish,
7
+ clock: ({ content, location }) => (isJrtUpsRow(content, location) ? 'wall' : 'platform'),
8
+ };
6
9
  export const normalizeJrtNumber = (raw) => normalizeHualeiNumber(raw, PROVIDER);
7
10
  /** The track interface's reply, or null when it holds no row for the number. */
8
11
  export const parseJrtTrack = (payload, number, readAt) => parseHualeiTrack(payload, number, JRT, readAt);
@@ -1,5 +1,5 @@
1
1
  import type { Stage } from '../../core/status/index.js';
2
- import type { CarrierStatusMap } from '../../core/status/statusMap.js';
2
+ import { type CarrierStatusMap } from '../../core/status/statusMap.js';
3
3
  /** The key a row's `normalizeStatusWording` wording is mapped under: a booked flight's day becomes an ellipsis. */
4
4
  export declare const jrtScanKey: (wording: string) => string;
5
5
  /**
@@ -14,4 +14,6 @@ export declare function jrtStage(_code: string | null, wording: string): Stage |
14
14
  * flight it books, which is still to come.
15
15
  */
16
16
  export declare const jrtEnglish: (_code: string | null, wording: string) => string | undefined;
17
+ /** Whether a row is one UPS wrote, by its wording or its place in the United States: it is on UPS's wall clock. */
18
+ export declare const isJrtUpsRow: (wording: string, place: string) => boolean;
17
19
  export declare const statusMap: CarrierStatusMap;
@@ -1,4 +1,5 @@
1
1
  import { statusWordings } from '../../core/status/english.js';
2
+ import { normalizeStatusWording } from '../../core/status/statusMap.js';
2
3
  import statuses from './statuses.json' with { type: 'json' };
3
4
  const WORDINGS = statusWordings(statuses.entries);
4
5
  // The airport row names the day of the booked flight in both halves.
@@ -10,10 +11,12 @@ const TAKE_OFF_DAY = /^(flight is expected to take off on )[a-z]+ \d{1,2}$/;
10
11
  // The newer English rows name the flight's days as month.day: "expected to depart on 10.27 and land on 10.27".
11
12
  const FLIGHT_DATES = /^(the flight is expected to depart on )\d{1,2}\.\d{1,2}( and land on )\d{1,2}\.\d{1,2}$/;
12
13
  const LANDING_DATE = /^(the flight has departed and is expected to )\d{1,2}\.\d{1,2}( land)$/;
14
+ // On the line into the United States JRT books the flight by its month and day alone: "10/22号航班".
15
+ const BOOKED_FLIGHT = /^\d{1,2}\/\d{1,2}(号航班)$/;
13
16
  /** The key a row's `normalizeStatusWording` wording is mapped under: a booked flight's day becomes an ellipsis. */
14
17
  export const jrtScanKey = (wording) => wording.replace(FLIGHT_DAY, '$1…$2…')
15
18
  .replace(CLEARED_FLIGHT_DAY, '$1…$2…').replace(TAKE_OFF_DAY, '$1…')
16
- .replace(FLIGHT_DATES, '$1…$2…').replace(LANDING_DATE, '$1…$2');
19
+ .replace(FLIGHT_DATES, '$1…$2…').replace(LANDING_DATE, '$1…$2').replace(BOOKED_FLIGHT, '…$1');
17
20
  /**
18
21
  * The stage JRT's map gives a row, by its whole `normalizeStatusWording` wording:
19
22
  * JRT's own, or the English the adapter shows for it.
@@ -28,4 +31,9 @@ export function jrtStage(_code, wording) {
28
31
  * flight it books, which is still to come.
29
32
  */
30
33
  export const jrtEnglish = (_code, wording) => WORDINGS.english(jrtScanKey(wording));
34
+ // UPS's rows, which the tracker copies in Chinese on UPS's wall clock on the line into the United States.
35
+ const UPS_ROWS = new Set(['发件人已创建标签,但是 UPS 尚未收到包裹。', '抵达设施', '离开设施', '已提取', 'UPS 设施处理', '已装上递送车辆',
36
+ '准备今天递送', '已递送'].map(normalizeStatusWording));
37
+ /** Whether a row is one UPS wrote, by its wording or its place in the United States: it is on UPS's wall clock. */
38
+ export const isJrtUpsRow = (wording, place) => UPS_ROWS.has(normalizeStatusWording(wording)) || /(?:^|,)\s*US$/.test(place.trim());
31
39
  export const statusMap = { stage: jrtStage, gaps: [] };
@@ -360,6 +360,129 @@
360
360
  "stage": "in_transit",
361
361
  "confirmedBy": "live",
362
362
  "note": "The airline's notice of arrival, after the landing and before the pickup and customs clearance."
363
+ },
364
+ {
365
+ "wording": "…号航班",
366
+ "language": "zh",
367
+ "english": "Booked on a flight",
368
+ "stage": "in_transit",
369
+ "confirmedBy": "live",
370
+ "firstSeen": "2026-10-30",
371
+ "note": "JRT's own row on its line into the United States, naming the flight's month and day, read as an ellipsis."
372
+ },
373
+ {
374
+ "wording": "航班已飞",
375
+ "language": "zh",
376
+ "english": "Flight has flown",
377
+ "stage": "in_transit",
378
+ "confirmedBy": "live",
379
+ "firstSeen": "2026-10-30"
380
+ },
381
+ {
382
+ "wording": "航班抵达",
383
+ "language": "zh",
384
+ "english": "Flight has arrived",
385
+ "stage": "in_transit",
386
+ "confirmedBy": "live",
387
+ "firstSeen": "2026-10-30"
388
+ },
389
+ {
390
+ "wording": "提货中",
391
+ "language": "zh",
392
+ "english": "Picking up the goods",
393
+ "stage": "in_transit",
394
+ "confirmedBy": "live",
395
+ "firstSeen": "2026-10-30",
396
+ "note": "At the destination airport after the landing, before the clearance."
397
+ },
398
+ {
399
+ "wording": "清关完成,已提货,待交仓",
400
+ "language": "zh",
401
+ "english": "Customs clearance completed, goods picked up, awaiting hand-over to the warehouse",
402
+ "stage": "in_transit",
403
+ "confirmedBy": "live",
404
+ "firstSeen": "2026-10-30"
405
+ },
406
+ {
407
+ "wording": "已交仓",
408
+ "language": "zh",
409
+ "english": "Handed over to the warehouse",
410
+ "stage": "in_transit",
411
+ "confirmedBy": "live",
412
+ "firstSeen": "2026-10-30",
413
+ "note": "The hand-over to the delivering carrier's warehouse, before UPS's first facility scan."
414
+ },
415
+ {
416
+ "wording": "发件人已创建标签,但是 UPS 尚未收到包裹。",
417
+ "language": "zh",
418
+ "english": "Shipper created a label, UPS has not received the package yet.",
419
+ "stage": "registered",
420
+ "confirmedBy": "live",
421
+ "firstSeen": "2026-10-30",
422
+ "note": "UPS's label row. On the line into the United States the tracker copies UPS's rows in Chinese, on UPS's wall clock at the scan's place; the English is UPS's."
423
+ },
424
+ {
425
+ "wording": "抵达设施",
426
+ "language": "zh",
427
+ "english": "Arrived at Facility",
428
+ "stage": "in_transit",
429
+ "confirmedBy": "live",
430
+ "firstSeen": "2026-10-30",
431
+ "note": "Copied from UPS."
432
+ },
433
+ {
434
+ "wording": "离开设施",
435
+ "language": "zh",
436
+ "english": "Departed from Facility",
437
+ "stage": "in_transit",
438
+ "confirmedBy": "live",
439
+ "firstSeen": "2026-10-30",
440
+ "note": "Copied from UPS."
441
+ },
442
+ {
443
+ "wording": "已提取",
444
+ "language": "zh",
445
+ "english": "Picked Up",
446
+ "stage": "in_transit",
447
+ "confirmedBy": "live",
448
+ "firstSeen": "2026-10-30",
449
+ "note": "Copied from UPS without a place, between its facility scans; the parcel is already on its way."
450
+ },
451
+ {
452
+ "wording": "UPS 设施处理",
453
+ "language": "zh",
454
+ "english": "Processing at UPS Facility",
455
+ "stage": "in_transit",
456
+ "confirmedBy": "live",
457
+ "firstSeen": "2026-10-30",
458
+ "note": "Copied from UPS."
459
+ },
460
+ {
461
+ "wording": "已装上递送车辆",
462
+ "language": "zh",
463
+ "english": "Loaded on Delivery Vehicle",
464
+ "stage": "out_for_delivery",
465
+ "confirmedBy": "live",
466
+ "firstSeen": "2026-10-30",
467
+ "note": "Copied from UPS, whose own map gives its code OF out for delivery."
468
+ },
469
+ {
470
+ "wording": "准备今天递送",
471
+ "language": "zh",
472
+ "english": "Out For Delivery Today",
473
+ "stage": "out_for_delivery",
474
+ "confirmedBy": "live",
475
+ "firstSeen": "2026-10-30",
476
+ "note": "Copied from UPS, whose own map gives its code OT out for delivery."
477
+ },
478
+ {
479
+ "wording": "已递送",
480
+ "language": "zh",
481
+ "english": "Delivered",
482
+ "stage": "delivered",
483
+ "confirmedBy": "live",
484
+ "firstSeen": "2026-10-30",
485
+ "note": "Copied from UPS."
363
486
  }
364
487
  ]
365
488
  }
@@ -10,6 +10,7 @@
10
10
  {"wording": "航班抵达【Flight arrival】", "language": "zh", "stage": "in_transit", "confirmedBy": "live", "note": "The flight has landed."},
11
11
  {"wording": "航班已启运/Package in transit to destination country", "language": "zh", "stage": "in_transit", "confirmedBy": "live", "note": "The booked flight has left."},
12
12
  {"wording": "航班已落地,拆板中/Package arrived at destination country", "language": "zh", "stage": "in_transit", "confirmedBy": "live", "note": "The flight has landed and its pallets are being broken down."},
13
+ {"wording": "已完成国内报关手续【Domestic customs clearance procedures have been completed.】", "language": "zh", "stage": "in_transit", "confirmedBy": "live", "note": "Cleared after the landing, before the delivery."},
13
14
  {"wording": "货物已清关完成/Import customs clearance complete", "language": "zh", "stage": "in_transit", "confirmedBy": "live", "note": "Released by the destination's customs, the day after the landing."},
14
15
  {"wording": "货物电子信息已经收到", "language": "zh", "stage": "registered", "confirmedBy": "prior-art", "note": "The platform writes this row itself for an order without scans, on its creation time."}
15
16
  ]
@@ -562,6 +562,12 @@ function providerWordingStage(description) {
562
562
  // hub taking the parcel in, the parcel sent on, and a flight held up, for which results carry no delay flag.
563
563
  if (/^customer information manifested[.!]?$/i.test(value))
564
564
  return 'registered';
565
+ // A booking before any carrier has the parcel: a collection requested or booked, or a carrier still waiting for it.
566
+ if (/^(?:pickup requested|booked|waiting for (?:the )?parcel)[.!]?$/i.test(value))
567
+ return 'registered';
568
+ // A forwarder's warehouse scanning the parcel in or out, as aggregators relay SPT Express's rows.
569
+ if (/^item (?:in|out)bound scanning[.!]?$/i.test(value))
570
+ return 'in_transit';
565
571
  if (/^checked in at origin hub[.!]?$|^parcel received and processing[.!]?$/i.test(value))
566
572
  return 'accepted';
567
573
  if (/^parcel shipped[.!]?$|^(?:airline|flight) delay(?:ed)?[.!]?$/i.test(value))
@@ -44,6 +44,7 @@ const NO_STEP = [
44
44
  // released it, whether a driver collects or delivers, or which forwarder's warehouse scanned it.
45
45
  ['item received', 'A bare receipt that names no receiver: ParcelsApp reads it by its place, the wording alone cannot.'],
46
46
  ['received.', 'A bare receipt that names no receiver: a store, a centre or a carrier.'],
47
+ ['package received', 'A bare receipt that names no receiver: a store, a centre or a carrier.'],
47
48
  ['shipped', "A dispatch that does not say whether the seller or a carrier sent the parcel on."],
48
49
  ['released', 'A release that does not say from customs or from a hold.'],
49
50
  ['driver assigned', 'A driver assigned, to a collection or to a delivery round: the wording does not say which.'],
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.856.1-main.4572"
5
+ "version": "0.856.2-main.4574"
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.856.1-main.4572",
3
+ "version": "0.856.2-main.4574",
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",