universal-parcel-scraper 0.637.1 → 0.640.0-main.3020

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
@@ -155,7 +155,7 @@ createTracker({ providers: ['ParcelsApp', 'Ship24', '17TRACK', 'Postal Ninja', '
155
155
  <!-- GENERATED:stages -->
156
156
  <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">
157
157
 
158
- The carrier folders record 16,380 such statuses, each filed under one stage.
158
+ The carrier folders record 16,387 such statuses, each filed under one stage.
159
159
  <!-- /GENERATED:stages -->
160
160
 
161
161
  Wording nobody recorded yet goes through a classifier that reads seven European languages.
package/data/catalog.json CHANGED
@@ -2524,6 +2524,28 @@
2524
2524
  "smartposti.lv"
2525
2525
  ],
2526
2526
  "path": "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$"
2527
+ },
2528
+ {
2529
+ "domains": [
2530
+ "smartposti.ee",
2531
+ "smartposti.lt",
2532
+ "smartposti.lv"
2533
+ ],
2534
+ "pathPattern": "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$",
2535
+ "params": [
2536
+ "trackingCode"
2537
+ ]
2538
+ },
2539
+ {
2540
+ "domains": [
2541
+ "itella.ee",
2542
+ "itella.lt",
2543
+ "itella.lv"
2544
+ ],
2545
+ "pathPattern": "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$",
2546
+ "params": [
2547
+ "trackingCode"
2548
+ ]
2527
2549
  }
2528
2550
  ],
2529
2551
  "detectionRules": [
@@ -9665,7 +9687,14 @@
9665
9687
  ]
9666
9688
  }
9667
9689
  ],
9668
- "detectionRules": []
9690
+ "detectionRules": [
9691
+ {
9692
+ "pattern": "^[A-Z]{2}\\d{9}MZ$",
9693
+ "confidence": "low",
9694
+ "checksum": "s10",
9695
+ "preferred": true
9696
+ }
9697
+ ]
9669
9698
  },
9670
9699
  "correo-argentino": {
9671
9700
  "displayName": "Correo Argentino",
@@ -12808,7 +12837,14 @@
12808
12837
  ]
12809
12838
  }
12810
12839
  ],
12811
- "detectionRules": []
12840
+ "detectionRules": [
12841
+ {
12842
+ "pattern": "^E[A-Z]\\d{9}VN$",
12843
+ "confidence": "low",
12844
+ "checksum": "s10",
12845
+ "preferred": true
12846
+ }
12847
+ ]
12812
12848
  },
12813
12849
  "energia-tk": {
12814
12850
  "displayName": "TK Energia",
@@ -69758,6 +69758,7 @@
69758
69758
  "intl-post"
69759
69759
  ],
69760
69760
  "asked": [
69761
+ "ems-vietnam",
69761
69762
  "chronopost"
69762
69763
  ]
69763
69764
  },
@@ -69838,6 +69839,7 @@
69838
69839
  "intl-post"
69839
69840
  ],
69840
69841
  "asked": [
69842
+ "ems-vietnam",
69841
69843
  "chronopost"
69842
69844
  ]
69843
69845
  },
@@ -70156,6 +70158,7 @@
70156
70158
  "intl-post"
70157
70159
  ],
70158
70160
  "asked": [
70161
+ "ems-vietnam",
70159
70162
  "chronopost"
70160
70163
  ]
70161
70164
  },
@@ -70188,6 +70191,7 @@
70188
70191
  "intl-post"
70189
70192
  ],
70190
70193
  "asked": [
70194
+ "corre",
70191
70195
  "chronopost"
70192
70196
  ]
70193
70197
  },
@@ -70670,6 +70674,7 @@
70670
70674
  "intl-post"
70671
70675
  ],
70672
70676
  "asked": [
70677
+ "ems-vietnam",
70673
70678
  "chronopost"
70674
70679
  ]
70675
70680
  },
@@ -70766,6 +70771,7 @@
70766
70771
  "intl-post"
70767
70772
  ],
70768
70773
  "asked": [
70774
+ "ems-vietnam",
70769
70775
  "chronopost"
70770
70776
  ]
70771
70777
  },
@@ -70801,6 +70807,7 @@
70801
70807
  "intl-post"
70802
70808
  ],
70803
70809
  "asked": [
70810
+ "ems-vietnam",
70804
70811
  "chronopost"
70805
70812
  ]
70806
70813
  },
@@ -70812,6 +70819,7 @@
70812
70819
  "intl-post"
70813
70820
  ],
70814
70821
  "asked": [
70822
+ "ems-vietnam",
70815
70823
  "chronopost"
70816
70824
  ]
70817
70825
  },
@@ -70851,6 +70859,7 @@
70851
70859
  "intl-post"
70852
70860
  ],
70853
70861
  "asked": [
70862
+ "ems-vietnam",
70854
70863
  "chronopost"
70855
70864
  ]
70856
70865
  },
@@ -71263,6 +71272,7 @@
71263
71272
  "intl-post"
71264
71273
  ],
71265
71274
  "asked": [
71275
+ "ems-vietnam",
71266
71276
  "chronopost"
71267
71277
  ]
71268
71278
  },
@@ -71698,6 +71708,7 @@
71698
71708
  "intl-post"
71699
71709
  ],
71700
71710
  "asked": [
71711
+ "ems-vietnam",
71701
71712
  "chronopost"
71702
71713
  ]
71703
71714
  },
@@ -71935,6 +71946,7 @@
71935
71946
  "intl-post"
71936
71947
  ],
71937
71948
  "asked": [
71949
+ "ems-vietnam",
71938
71950
  "chronopost"
71939
71951
  ]
71940
71952
  },
@@ -92902,6 +92914,7 @@
92902
92914
  "intl-post"
92903
92915
  ],
92904
92916
  "asked": [
92917
+ "ems-vietnam",
92905
92918
  "chronopost"
92906
92919
  ]
92907
92920
  },
@@ -2309,6 +2309,24 @@ var Ye = {
2309
2309
  "smartposti.lv"
2310
2310
  ],
2311
2311
  path: "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$"
2312
+ },
2313
+ {
2314
+ domains: [
2315
+ "smartposti.ee",
2316
+ "smartposti.lt",
2317
+ "smartposti.lv"
2318
+ ],
2319
+ pathPattern: "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$",
2320
+ params: ["trackingCode"]
2321
+ },
2322
+ {
2323
+ domains: [
2324
+ "itella.ee",
2325
+ "itella.lt",
2326
+ "itella.lv"
2327
+ ],
2328
+ pathPattern: "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$",
2329
+ params: ["trackingCode"]
2312
2330
  }
2313
2331
  ],
2314
2332
  detectionRules: [
@@ -7648,7 +7666,12 @@ var Ye = {
7648
7666
  pathPattern: "^/cgi-bin/(?:tracktrace|trace)\\.cgi$",
7649
7667
  params: ["OBJ"]
7650
7668
  }],
7651
- detectionRules: []
7669
+ detectionRules: [{
7670
+ pattern: "^[A-Z]{2}\\d{9}MZ$",
7671
+ confidence: "low",
7672
+ checksum: "s10",
7673
+ preferred: !0
7674
+ }]
7652
7675
  },
7653
7676
  "correo-argentino": {
7654
7677
  displayName: "Correo Argentino",
@@ -9985,7 +10008,12 @@ var Ye = {
9985
10008
  pathPattern: "^/tra-cuu/tra-cuu-buu-gui/?$",
9986
10009
  params: ["code"]
9987
10010
  }],
9988
- detectionRules: []
10011
+ detectionRules: [{
10012
+ pattern: "^E[A-Z]\\d{9}VN$",
10013
+ confidence: "low",
10014
+ checksum: "s10",
10015
+ preferred: !0
10016
+ }]
9989
10017
  },
9990
10018
  "energia-tk": {
9991
10019
  displayName: "TK Energia",
@@ -24581,7 +24609,7 @@ var Ye = {
24581
24609
  "connect-couriers": [],
24582
24610
  coordinadora: [],
24583
24611
  "copa-courier": ["copa-courier-1"],
24584
- corre: [],
24612
+ corre: ["corre-s10"],
24585
24613
  "correo-argentino": ["correo-argentino-s10"],
24586
24614
  "correo-uruguayo": ["correo-uruguayo-s10"],
24587
24615
  "correos-bolivia": [],
@@ -24687,7 +24715,7 @@ var Ye = {
24687
24715
  elta: ["elta-s10"],
24688
24716
  emile: ["emile-1", "emile-2"],
24689
24717
  "emirates-post": [],
24690
- "ems-vietnam": [],
24718
+ "ems-vietnam": ["ems-vietnam-s10"],
24691
24719
  "energia-tk": [],
24692
24720
  energo: ["energo-1"],
24693
24721
  "envia-colombia": [],
@@ -29850,7 +29878,7 @@ function Hc(e) {
29850
29878
  if (sc(t) || /\b(?:en|em) (?:proceso de |processo de )?devolu(?:cion|cao)\b|^devolu(?:cion|cao)$|\bdevolu(?:cion|cao) (?:en curso|em curso|iniciada|solicitada|en transito|em transito)\b|\bdevolu(?:cion|cao) (?:al|ao|a la|a) (?:remitente|remetente|origen|origem)\b(?! (?:completad|finalizad|concluid|entregad|entregue|realizad|efectuad|efetuad))|\ben camino (?:de vuelta |de regreso )?(?:al|hacia el) remitente\b|\b(?:a caminho (?:de volta )?(?:do|ao)|em transito para o) remetente\b|\b(?:siendo|sendo|a ser) (?:(?:devuelt|devolvid)[oa]s?|entregad[oa]s? al remitente|entregues? ao remetente)\b|\b(?:no|nao) (?:(?:ha|han|se|fue|foi|foram|sido|ser|sera|puede|pode|pudo|podido|todavia|aun|ainda|ya|ja) )*(?:(?:devuelt|devolvid)[oa]s?|entregad[oa]s? al remitente|entregues? ao remetente)\b|\b(?:sera|seran|serao|va a ser|vai ser|puede ser|pode ser|debe ser|deve ser|devera ser) (?:(?:pronto|proximamente|en breve|em breve|brevemente) )?(?:(?:devuelt|devolvid)[oa]s?|entregad[oa]s? al remitente|entregues? ao remetente)\b|\bse (?:le )?devolvera\b/.test(t)) return "exception";
29851
29879
  if (/returned to (?:the )?sender|retour(?:ne)? a l'expediteur|zuruck an (?:den )?absender|an (?:den )?absender zuruck|retour a l'expediteur|reso al mittente|restituit[oa] al mittente|delivered (?:back )?to (?:the )?(?:sender|shipper)/.test(t) || /^(?:(?:envio|paquete|pedido|objeto|encomenda|pacote|remessa) )?(?:devuelt|devolvid)[oa]$|\b(?:devuelt|devolvid|retornad)[oa]s? (?:al|ao|a la|a) (?:remitente|remetente|origen|origem)\b|\bentregad[oa]s? al remitente\b|\bentregues? ao remetente\b|\bdevolu(?:cion|cao) (?:(?:al|ao|a la|a) (?:remitente|remetente|origen|origem) )?(?:completad|finalizad|concluid|entregad|entregue|realizad|efectuad|efetuad)/.test(t)) return "returned";
29852
29880
  if (/^(?:a )?(?:new|next|another|further) (?:delivery )?attempt\b/.test(t) && !/\b(?:not|unable|fail(?:ed|ure)?|unsuccessful|missed|absent|closed|refused|nobody|no one)\b|n't\b/.test(t)) return "in_transit";
29853
- if (/not (?:yet )?delivered|\bun ?delivered\b|(?:could not|couldn't).*deliver|\bcan(?: ?not|'t) be delivered\b|unable to deliver|delivery (?:attempt|failed)|non livre|n'(?:a|avons) (?:pas )?pu.*(?:remis|remettre)|n'a pas pu etre (?:distribue|livre)|\bne (?:peut|pourra|pourrait|pouvait) (?:pas )?etre (?:distribue|livre)|livraison (?:impossible|echouee)|\bechec (?:de (?:la )?)?livraison|tentative de livraison|nicht zugestellt|nicht zugestellt werden|zustellung.*(?:fehlgeschlagen|nicht moglich)|zustellversuch|non consegnat[oa]|\bmancata consegna\b|non e stato possibile consegnare|consegna (?:fallita|non riuscita)|tentativo di consegna/.test(t) || /\bno (?:(?:ha|han|hemos|se|le|lo|la|fue|sido|ser|es|era|pudo|puede|podido|posible|todavia|aun) )*(?:entregad[oa]s?|entregar(?:lo|la|le|se)?|repartid[oa]s?|realizar la entrega|efectuar la entrega)\b|\bsin entregar\b|\b(?:entrega|reparto) (?:fallid[oa]|imposible|no (?:(?:ha|han|se|fue|sido|ser|pudo|puede|podido) )*(?:realizad[oa]|efectuad[oa]|completad[oa]|posible|conseguid[oa]))\b|\bimposible (?:entregar|realizar la entrega|efectuar la entrega)\b|\bfallo (?:en la|de) entrega\b|\bintento fallido\b|\bintento de (?:entrega|reparto)\b(?! (?:programad|previst|para (?:hoy|manana|el)|manana|hoy))|\bnao (?:(?:foi|foram|ser|sera|pode|podemos|conseguimos|conseguiu|se|possivel|e|ainda|tem sido|esta|estava) )*(?:entregues?|entregar|efetuar a entrega|efectuar a entrega|realizar a entrega)\b|\bentrega (?:falhada|sem sucesso|impossivel|nao (?:(?:foi|pode|ser|sera|tem sido) )*(?:efetuada|efectuada|realizada|conseguida|concretizada|possivel))\b|\bfalha (?:na|de) entrega\b|\btentativa (?:de entrega|sem sucesso)\b(?! (?:agendada|prevista|programada|para (?:hoje|amanha)|amanha|hoje))|\bcarteiro nao atendido\b/.test(t) || !/\bpick ?up (?:attempt|was attempted|unsuccessful|failed)|attempted (?:to )?pick ?up/.test(t) && /(?:attempted|tried) to deliver|\bcard(?:ed| left)\b|\bwe(?: have|'ve)? missed (?:you|each other)\b(?![^.;!?]*\bready for (?:pick ?up|collection)\b)|\battempted delivery\b(?! (?:is )?(?:scheduled|planned|expected|tomorrow|will)\b)|unsuccessful delivery|delivery (?:was )?unsuccessful|unable to complete (?:the |your |this )?delivery|(?<!\bif (?:the |your )?)\b(?:recipient|addressee|consignee|customer|receiver|user)(?: was| is)? (?:not (?:at home|home|available|present)|unavailable|absent)\b|absence of (?:the )?(?:recipient|addressee|consignee)|\bbusiness (?:was |is )?closed\b|\bpremises (?:(?:was|were) )?closed\b|destinataire absent|(?<!\ben cas d')\babsence du destinataire|(?:entreprise|societe) (?:etait |est )?fermee?\b|nicht angetroffen|(?:firma|betrieb|geschaft) (?:war |ist )?geschlossen|destinatario assente|(?:attivita|azienda|ditta) (?:era |e )?chiusa/.test(t) || !/\bintento de recogida\b|\brecogida (?:fallida|no realizada|no efectuada)\b|\btentativa de (?:recolha|coleta)\b|\b(?:recolha|coleta) (?:falhada|sem sucesso|nao (?:realizada|efetuada|efectuada))\b/.test(t) && !/\b(?:si|caso)\b[^.;:]*\b(?:ausen|nadie|ninguem|cerrad|fechad|no (?:esta|se encuentra)|nao (?:esta|se encontra))|\bse (?:o |a )?(?:destinatari[oa] |cliente )?(?:estiver|for|nao estiver)\b/.test(t) && /\bausen(?:te|tes|cia)\b|\b(?:destinatari[oa]|cliente|consignatari[oa]) (?:no (?:esta|estaba|se encuentra|se encontraba|disponible|presente)|nao (?:esta|estava|se encontra|se encontrava|disponivel|presente))\b|\bnadie en (?:casa|el domicilio)\b|\bninguem em casa\b|\b(?:establecimiento|negocio|empresa|comercio|tienda) (?:esta |estaba |se encontraba )?cerrad[oa]\b|\b(?:estabelecimento|empresa|loja|comercio|negocio) (?:esta |estava |se encontrava )?fechad[oa]\b/.test(t)) return "failed_attempt";
29881
+ if (/not (?:yet )?delivered|\bun ?delivered\b|(?:could not|couldn't).*deliver|\bcan(?: ?not|'t) be delivered\b|unable to deliver|delivery (?:attempt|failed)|non livre|n'(?:a|avons) (?:pas )?pu.*(?:remis|remettre)|n'a pas pu etre (?:distribue|livre)|\bne (?:peut|pourra|pourrait|pouvait) (?:pas )?etre (?:distribue|livre)|livraison (?:impossible|echouee)|\bechec (?:de (?:la )?)?livraison|tentative de livraison|nicht zugestellt|nicht zugestellt werden|zustellung.*(?:fehlgeschlagen|nicht moglich)|zustellversuch|non consegnat[oa]|\bmancata consegna\b|non e stato possibile consegnare|consegna (?:fallita|non riuscita)|tentativo di consegna/.test(t) || /\bno (?:(?:ha|han|hemos|se|le|lo|la|fue|sido|ser|es|era|pudo|puede|podido|posible|todavia|aun) )*(?:entregad[oa]s?|entregar(?:lo|la|le|se)?|repartid[oa]s?|realizar la entrega|efectuar la entrega)\b|\bsin entregar\b|\b(?:entrega|reparto) (?:fallid[oa]|imposible|no (?:(?:ha|han|se|fue|sido|ser|pudo|puede|podido) )*(?:realizad[oa]|efectuad[oa]|completad[oa]|posible|conseguid[oa]))\b|\bimposible (?:entregar|realizar la entrega|efectuar la entrega)\b|\bfallo (?:en la|de) entrega\b|\bintento fallido\b|\bintento de (?:entrega|reparto)\b(?! (?:programad|previst|para (?:hoy|manana|el)|manana|hoy))|\bnao (?:(?:foi|foram|ser|sera|pode|podemos|conseguimos|conseguiu|se|possivel|e|ainda|tem sido|esta|estava) )*(?:entregues?|entregar|efetuar a entrega|efectuar a entrega|realizar a entrega)\b|\bentrega (?:falhada|sem sucesso|impossivel|nao (?:(?:foi|pode|ser|sera|tem sido) )*(?:efetuada|efectuada|realizada|conseguida|concretizada|possivel))\b|\bfalha (?:na|de) entrega\b|\btentativa (?:de entrega|sem sucesso)\b(?! (?:agendada|prevista|programada|para (?:hoje|amanha)|amanha|hoje))|\bcarteiro nao atendido\b/.test(t) || !/\bpick ?up (?:attempt|was attempted|unsuccessful|failed)|attempted (?:to )?pick ?up/.test(t) && /(?:attempted|tried) to deliver|\bcard(?:ed| left)\b|\bwe(?: have|'ve)? missed (?:you|each other)\b(?![^.;!?]*\bready for (?:pick ?up|collection)\b)|\battempted delivery\b(?! (?:is )?(?:scheduled|planned|expected|tomorrow|will)\b)|unsuccessful delivery|delivery (?:was )?(?:un|not )successful|unable to complete (?:the |your |this )?delivery|(?<!\bif (?:the |your )?)\b(?:recipient|addressee|consignee|customer|receiver|user)(?: was| is)? (?:not (?:at home|home|available|present)|unavailable|absent)\b|absence of (?:the )?(?:recipient|addressee|consignee)|\bbusiness (?:was |is )?closed\b|\bpremises (?:(?:was|were) )?closed\b|destinataire absent|(?<!\ben cas d')\babsence du destinataire|(?:entreprise|societe) (?:etait |est )?fermee?\b|nicht angetroffen|(?:firma|betrieb|geschaft) (?:war |ist )?geschlossen|destinatario assente|(?:attivita|azienda|ditta) (?:era |e )?chiusa/.test(t) || !/\bintento de recogida\b|\brecogida (?:fallida|no realizada|no efectuada)\b|\btentativa de (?:recolha|coleta)\b|\b(?:recolha|coleta) (?:falhada|sem sucesso|nao (?:realizada|efetuada|efectuada))\b/.test(t) && !/\b(?:si|caso)\b[^.;:]*\b(?:ausen|nadie|ninguem|cerrad|fechad|no (?:esta|se encuentra)|nao (?:esta|se encontra))|\bse (?:o |a )?(?:destinatari[oa] |cliente )?(?:estiver|for|nao estiver)\b/.test(t) && /\bausen(?:te|tes|cia)\b|\b(?:destinatari[oa]|cliente|consignatari[oa]) (?:no (?:esta|estaba|se encuentra|se encontraba|disponible|presente)|nao (?:esta|estava|se encontra|se encontrava|disponivel|presente))\b|\bnadie en (?:casa|el domicilio)\b|\bninguem em casa\b|\b(?:establecimiento|negocio|empresa|comercio|tienda) (?:esta |estaba |se encontraba )?cerrad[oa]\b|\b(?:estabelecimento|empresa|loja|comercio|negocio) (?:esta |estava |se encontrava )?fechad[oa]\b/.test(t)) return "failed_attempt";
29854
29882
  if (jc.test(t)) return Mc.test(t) ? "ready_for_pickup" : "in_transit";
29855
29883
  if (/\ba request was made to change (?:this|the|your) delivery date\b/.test(t) || /^(?:the )?delivery (?:details|information) (?:(?:has|have) been |were |was )?updated$/.test(t) || /^(?:package|parcel) shipment$/.test(t)) return "in_transit";
29856
29884
  if (/\b(?:damaged|broken in transit)\b|endommag|avarie|deterior|beschadigt|danneggiat|danad[oa]|danificad|uszkodzon/.test(t) || /\blost (?:in transit|package|parcel|shipment)?\b|colis perdu|envoi perdu|egare|verloren|verlust der sendung|smarrit|(?:paquete|envio) perdid|extraviad|zagubion|zaginion/.test(t) || /refused by (?:the )?(?:recipient|consignee)|\brefused\b|rejected by (?:the )?recipient|refus(?:e|ee)? par le destinataire|refus du destinataire|(?:colis|envoi|pli) refuse|annahme verweigert|verweigert|rifiutat|rechazad|recusad|odmowa przyjecia|odmowiono przyjecia/.test(t) || /address (?:incomplete|incorrect|invalid|insufficient|unknown)|(?:incorrect|incomplete|insufficient|wrong|invalid) address|(?:updated|correct(?:ed)?|complete) (?:delivery )?address (?:is )?(?:required|needed)|address(?:ee)? (?:unknown|cannot be located)|recipient unknown|adresse (?:incorrecte|incomplete|erronee|invalide|inconnue)|destinataire inconnu|(?:adresse|anschrift) (?:unvollstandig|falsch|unbekannt)|empfanger unbekannt|indirizzo (?:errato|incompleto|insufficiente|sconosciuto)|destinatario sconosciuto|direccion (?:incorrecta|incompleta|erronea|desconocida)|destinatario desconocido|endereco (?:incorreto|incompleto|errado|desconhecido)|destinatario desconhecido|adres (?:niepelny|nieprawidlowy|bledny)|nieznany adresat/.test(t)) return "exception";
@@ -76,5 +76,13 @@
76
76
  ]
77
77
  }
78
78
  ],
79
- "detection": []
79
+ "detection": [
80
+ {
81
+ "id": "corre-s10",
82
+ "pattern": "^[A-Z]{2}\\d{9}MZ$",
83
+ "confidence": "low",
84
+ "preferred": true,
85
+ "checksum": "s10"
86
+ }
87
+ ]
80
88
  }
@@ -102,5 +102,13 @@
102
102
  ]
103
103
  }
104
104
  ],
105
- "detection": []
105
+ "detection": [
106
+ {
107
+ "id": "ems-vietnam-s10",
108
+ "pattern": "^E[A-Z]\\d{9}VN$",
109
+ "confidence": "low",
110
+ "preferred": true,
111
+ "checksum": "s10"
112
+ }
113
+ ]
106
114
  }
@@ -1,2 +1,5 @@
1
1
  import type { ClassifiedStatus } from '../../core/status/index.js';
2
+ import type { CarrierStatusMap } from '../../core/status/statusMap.js';
2
3
  export declare function evriStatus(wording: string): ClassifiedStatus | undefined;
4
+ /** What the map says about one scan: Evri's labels carry no code. */
5
+ export declare const statusMap: CarrierStatusMap;
@@ -10,6 +10,7 @@ const STATUSES = {
10
10
  'in transit': { status: 'in_transit', stage: 'in_transit' },
11
11
  'in customs': { status: 'in_transit', stage: 'customs' },
12
12
  'customs cleared': { status: 'in_transit', stage: 'in_transit' },
13
+ 'departed origin country': { status: 'in_transit', stage: 'in_transit' },
13
14
  'arrived in country': { status: 'in_transit', stage: 'in_transit' },
14
15
  'handed to partner carrier': { status: 'in_transit', stage: 'in_transit' },
15
16
  'received by carrier': { status: 'in_transit', stage: 'in_transit' },
@@ -28,3 +29,5 @@ export function evriStatus(wording) {
28
29
  const key = wording.trim().toLowerCase();
29
30
  return Object.hasOwn(STATUSES, key) ? STATUSES[key] : undefined;
30
31
  }
32
+ /** What the map says about one scan: Evri's labels carry no code. */
33
+ export const statusMap = { stage: (_code, wording) => evriStatus(wording)?.stage, gaps: [] };
@@ -129,6 +129,14 @@
129
129
  "firstSeen": "2026-09-26",
130
130
  "note": "Synthetic semantic boundary; not observed in the live reference."
131
131
  },
132
+ {
133
+ "wording": "Departed Origin Country",
134
+ "language": "en",
135
+ "stage": "in_transit",
136
+ "confirmedBy": "live",
137
+ "firstSeen": "2026-10-22",
138
+ "note": "After export customs and before the arrival in the destination country."
139
+ },
132
140
  {
133
141
  "wording": "Arrived In Country",
134
142
  "language": "en",
@@ -93,6 +93,28 @@
93
93
  "smartposti.lv"
94
94
  ],
95
95
  "path": "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$"
96
+ },
97
+ {
98
+ "domains": [
99
+ "smartposti.ee",
100
+ "smartposti.lt",
101
+ "smartposti.lv"
102
+ ],
103
+ "pathPattern": "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$",
104
+ "params": [
105
+ "trackingCode"
106
+ ]
107
+ },
108
+ {
109
+ "domains": [
110
+ "itella.ee",
111
+ "itella.lt",
112
+ "itella.lv"
113
+ ],
114
+ "pathPattern": "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$",
115
+ "params": [
116
+ "trackingCode"
117
+ ]
96
118
  }
97
119
  ],
98
120
  "detection": [
@@ -2,7 +2,7 @@ import { IndeterminateError, InvalidInputError, SchemaError } from '../../core/e
2
2
  import { calendarDay, explicitOffsetTime } from '../../core/time/index.js';
3
3
  import { clean } from '../../core/transport/index.js';
4
4
  import { isRecord } from '../../core/types.js';
5
- import { classifySeurStatus } from './status.js';
5
+ import { classifySeurStatus, isSeurDelay } from './status.js';
6
6
  export function normalizeSeurNumber(raw) {
7
7
  const number = raw.replace(/\s/g, '');
8
8
  // Twenty-two digits is a sender's reference, which the lookup also finds, as
@@ -84,14 +84,25 @@ export function parseSeur(value, raw) {
84
84
  return { description: mapped?.status === 'delivered' ? 'Delivered' : label, provider_status: label,
85
85
  provider_group: group, provider_code: code, ...(mapped ? { stage: mapped.stage } : {}), ...scanTime(row.fecha) };
86
86
  });
87
+ const delay = (event) => isSeurDelay(event.provider_code, String(event.provider_group), String(event.provider_status));
88
+ // Oldest first: a delay notice keeps the stage of the scan before it.
89
+ let previous;
90
+ for (const event of [...events].reverse()) {
91
+ if (!delay(event))
92
+ previous = event.stage ?? previous;
93
+ else if (previous)
94
+ Object.assign(event, { stage: previous, stage_source: 'none' });
95
+ }
87
96
  // The official widget consumes situaciones[0]; unresolved clocks retain that position.
97
+ // Its status is the newest scan's other than a delay notice.
88
98
  const latest = events[0];
89
- const mapped = classifySeurStatus(latest.provider_code, String(latest.provider_group), String(latest.provider_status));
99
+ const current = events.find((event) => !delay(event));
100
+ const mapped = current && classifySeurStatus(current.provider_code, String(current.provider_group), String(current.provider_status));
90
101
  const weight = value.peso;
91
102
  return { status: mapped?.status ?? 'unknown', ...(mapped ? { current_stage: mapped.stage } : {}),
92
103
  last_status_text: latest.description, last_update: latest.time ?? null,
93
104
  ...(latest.local_time ? { last_update_local: latest.local_time } : {}),
94
- ...(mapped?.status === 'delivered' && latest.time ? { delivered_at: latest.time } : {}),
105
+ ...(mapped?.status === 'delivered' && current?.time ? { delivered_at: current.time } : {}),
95
106
  ...(typeof weight === 'number' && Number.isFinite(weight) && weight > 0 && weight <= 100_000 ? { weight_kg: weight } : {}),
96
107
  events: events.slice(0, 100) };
97
108
  }
@@ -1,4 +1,9 @@
1
1
  import type { ClassifiedStatus } from '../../core/status/index.js';
2
+ import type { CarrierStatusMap } from '../../core/status/statusMap.js';
2
3
  export declare function classifySeurStatus(code: string, group: string, wording: string): ClassifiedStatus | undefined;
4
+ /** Whether the scan is one of SEUR's delay notices, which keep the stage the parcel had. */
5
+ export declare function isSeurDelay(code: string, group: string, wording: string): boolean;
3
6
  /** The status group SEUR sent with an observed code. */
4
7
  export declare function seurStatusGroup(code: string): string | undefined;
8
+ /** What the map says about one scan, read with the group SEUR sends with its code. */
9
+ export declare const statusMap: CarrierStatusMap;
@@ -1,18 +1,21 @@
1
1
  const delivered = { status: 'delivered', stage: 'delivered' };
2
2
  const inTransit = { status: 'in_transit', stage: 'in_transit' };
3
3
  const exception = { status: 'exception', stage: 'exception' };
4
+ // A null reading is a delay notice, not a movement: the scan keeps the stage of the one before it.
4
5
  const observed = {
5
6
  LL003: { group: 'ENTREGADO', wording: 'EL ENVÍO ESTÁ ENTREGADO.', mapped: delivered },
6
7
  LL010: { group: 'ENTREGADO', wording: 'EL DESTINATARIO HA RETIRADO EL ENVÍO DE LA TIENDA SEUR PICKUP SELECCIONADA.', mapped: delivered },
7
8
  LL020: { group: 'ENTREGADO', wording: 'EL ENVÍO HA SIDO ENTREGADO A UN VECINO.', mapped: delivered },
8
9
  LC003: { group: 'EN REPARTO', wording: 'EL ENVÍO ESTÁ EN REPARTO.', mapped: { status: 'out_for_delivery', stage: 'out_for_delivery' } },
9
- LI300: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO Y ES POSIBLE QUE SE DEMORE LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: exception },
10
- LI582: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO PROVOCANDO UNA POSIBLE DEMORA EN LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: exception },
10
+ LI300: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO Y ES POSIBLE QUE SE DEMORE LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: null },
11
+ LI582: { group: 'EN DEMORA', wording: 'EL ENVÍO HA SUFRIDO UN RETRASO PROVOCANDO UNA POSIBLE DEMORA EN LA ENTREGA. DISCULPA LAS MOLESTIAS.', mapped: null },
11
12
  // Recorded after the day's delivery round, which starts again the next day.
12
13
  LI521: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO PORQUE LA DIRECCIÓN DE ENTREGA ESTÁ INCOMPLETA O ERRÓNEA.', mapped: { status: 'exception', stage: 'failed_attempt' } },
13
14
  LI523: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO POR AUSENCIA O CIERRE. SE PUEDE REPROGRAMAR UNA NUEVA ENTREGA.', mapped: { status: 'exception', stage: 'failed_attempt' } },
14
15
  LI524: { group: 'EN INCIDENCIA', wording: 'EL ENVÍO NO SE HA ENTREGADO PORQUE NO ES LA DIRECCIÓN ACTUAL DEL DESTINATARIO.', mapped: exception },
15
16
  LI530: { group: 'ENTREGA EN TIENDA', wording: 'EL ENVÍO ESTÁ DISPONIBLE PARA RECOGER EN EL PUNTO SEUR PICKUP.', mapped: { status: 'in_transit', stage: 'ready_for_pickup' } },
17
+ // Pending arrival at the destination's facility: in the delay group, but it says where the parcel is.
18
+ LI511: { group: 'EN DEMORA', wording: 'EL ENVÍO ESTÁ PENDIENTE DE LLEGAR A DESTINO. EN CUANTO SE RECIBA, ACTUALIZAREMOS LA SITUACIÓN.', mapped: inTransit },
16
19
  LI567: { group: 'EN TRÁNSITO', wording: 'EL ENVÍO ESTÁ EN TRÁNSITO. PRÓXIMAMENTE LLEGARÁ A LAS INSTALACIONES DE DESTINO.', mapped: inTransit },
17
20
  LO001: { group: 'EN TRÁNSITO', wording: 'EL ENVÍO ESTÁ EN TRÁNSITO. PRÓXIMAMENTE LLEGARÁ A LAS INSTALACIONES DE DESTINO.', mapped: inTransit },
18
21
  SW189: { group: 'EN TRÁNSITO', wording: 'EL ENVÍO ESTÁ EN TRÁNSITO. PRÓXIMAMENTE LLEGARÁ A LAS INSTALACIONES DE DESTINO.', mapped: inTransit },
@@ -25,13 +28,25 @@ const observed = {
25
28
  SX001: { group: 'EN TRÁNSITO', wording: 'EL ENVÍO HA SIDO RECEPCIONADO EN LAS INSTALACIONES DE ORIGEN DE SEUR. COMIENZA EL TRÁNSITO A DESTINO', mapped: { status: 'in_transit', stage: 'accepted' } },
26
29
  SX010: { group: 'NOTIFICADO', wording: 'EL ENVÍO HA SIDO REGISTRADO.', mapped: { status: 'pending', stage: 'registered' } },
27
30
  };
28
- export function classifySeurStatus(code, group, wording) {
31
+ function entry(code, group, wording) {
29
32
  if (!Object.hasOwn(observed, code))
30
33
  return undefined;
31
- const entry = observed[code];
32
- return group.toUpperCase() === entry.group && wording.toUpperCase() === entry.wording ? entry.mapped : undefined;
34
+ const found = observed[code];
35
+ return group.toUpperCase() === found.group && wording.toUpperCase() === found.wording ? found : undefined;
36
+ }
37
+ export function classifySeurStatus(code, group, wording) {
38
+ return entry(code, group, wording)?.mapped ?? undefined;
39
+ }
40
+ /** Whether the scan is one of SEUR's delay notices, which keep the stage the parcel had. */
41
+ export function isSeurDelay(code, group, wording) {
42
+ return entry(code, group, wording)?.mapped === null;
33
43
  }
34
44
  /** The status group SEUR sent with an observed code. */
35
45
  export function seurStatusGroup(code) {
36
46
  return Object.hasOwn(observed, code) ? observed[code].group : undefined;
37
47
  }
48
+ /** What the map says about one scan, read with the group SEUR sends with its code. */
49
+ export const statusMap = {
50
+ stage: (code, wording) => (code ? classifySeurStatus(code, seurStatusGroup(code) ?? '', wording)?.stage : undefined),
51
+ gaps: ['LI300', 'LI582'].map((code) => ({ code, note: 'A delay notice, not a movement: the parcel keeps the stage it had.' })),
52
+ };
@@ -71,16 +71,26 @@
71
71
  "code": "LI300",
72
72
  "wording": "EL ENVÍO HA SUFRIDO UN RETRASO Y ES POSIBLE QUE SE DEMORE LA ENTREGA. DISCULPA LAS MOLESTIAS.",
73
73
  "language": "es",
74
- "stage": "exception",
75
- "confirmedBy": "live"
74
+ "confirmedBy": "live",
75
+ "unmapped": true,
76
+ "note": "A delay notice in the EN DEMORA group, not a movement: the row keeps the stage of the one before it."
76
77
  },
77
78
  {
78
79
  "code": "LI582",
79
80
  "wording": "EL ENVÍO HA SUFRIDO UN RETRASO PROVOCANDO UNA POSIBLE DEMORA EN LA ENTREGA. DISCULPA LAS MOLESTIAS.",
80
81
  "language": "es",
81
- "stage": "exception",
82
82
  "confirmedBy": "live",
83
- "note": "A delay notice in the EN DEMORA group, read like LI300."
83
+ "unmapped": true,
84
+ "note": "A delay notice in the EN DEMORA group, read like LI300. The parcel seen with it moved on to an agreed delivery day."
85
+ },
86
+ {
87
+ "code": "LI511",
88
+ "wording": "EL ENVÍO ESTÁ PENDIENTE DE LLEGAR A DESTINO. EN CUANTO SE RECIBA, ACTUALIZAREMOS LA SITUACIÓN.",
89
+ "language": "es",
90
+ "stage": "in_transit",
91
+ "confirmedBy": "live",
92
+ "firstSeen": "2026-10-22",
93
+ "note": "In the EN DEMORA group, but it says where the parcel is: between the origin's receipt and the transit to the destination's facility."
84
94
  },
85
95
  {
86
96
  "code": "LC003",
@@ -17,7 +17,7 @@ const NOT_FOUND = 'Order not found.';
17
17
  // Its answer, for any number, to a query sent without the page's own verification.
18
18
  const VERIFICATION = 'Verification required.';
19
19
  // The batch's own code for a query whose verification token was missing or refused.
20
- const VERIFICATION_CODE = 'HUMAN_VERIFICATION_REQUIRED';
20
+ const VERIFICATION_CODES = new Set(['HUMAN_VERIFICATION_REQUIRED', 'SLIDER_VERIFICATION_FAILED']);
21
21
  // "2026-01-13 09:04:05", the scan site's wall clock.
22
22
  const STAMP = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/;
23
23
  // "GMT-07:00" or "GMT-7:00", the scan site's offset at that time.
@@ -97,7 +97,7 @@ export function parseSwiftx(payload, raw, readAt = Date.now()) {
97
97
  if (!isRecord(batch) || typeof batch.success !== 'boolean')
98
98
  throw new SchemaError(PROVIDER);
99
99
  if (!batch.success) {
100
- if (batch.errorCode === VERIFICATION_CODE)
100
+ if (VERIFICATION_CODES.has(String(batch.errorCode)))
101
101
  throw new ChallengeError(PROVIDER, 'SwiftX asked for its tracking page\'s verification');
102
102
  throw new IndeterminateError(PROVIDER, 'SwiftX did not answer for the shipment');
103
103
  }
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import type { CarrierStatus } from '../../core/result/index.js';
15
15
  import type { Stage } from '../../core/status/index.js';
16
+ import type { CarrierStatusMap } from '../../core/status/statusMap.js';
16
17
  /**
17
18
  * Classify USPS status prose. Returns null when nothing matches, so callers
18
19
  * leave an event unstaged rather than invent a stage. `hasEvents` reports an
@@ -23,3 +24,5 @@ export declare function uspsStage(text: string): Stage | null;
23
24
  export declare function statusForStage(stage: Stage): CarrierStatus;
24
25
  /** The result-level status for banner and history wording. */
25
26
  export declare function uspsStatus(text: string, hasEvents?: boolean): CarrierStatus;
27
+ /** What the map says about one scan: USPS's scans carry no code, and their wording alone stages them. */
28
+ export declare const statusMap: CarrierStatusMap;
@@ -74,3 +74,5 @@ export function uspsStatus(text, hasEvents = false) {
74
74
  // Unrecognized wording only means "moving" once the shipment has scans.
75
75
  return hasEvents ? 'in_transit' : 'unknown';
76
76
  }
77
+ /** What the map says about one scan: USPS's scans carry no code, and their wording alone stages them. */
78
+ export const statusMap = { stage: (_code, wording) => uspsStage(wording) ?? undefined, gaps: [] };
@@ -12,8 +12,10 @@ export const ZTO_INTERNATIONAL_MAX_BYTES = 512 * 1024;
12
12
  // 771 and eleven digits: ZTO's own pattern for its international waybills.
13
13
  const NUMBER = /^771\d{11}$/;
14
14
  const WALL_CLOCK = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/;
15
- // ZTO works on Shanghai's clock; a scan's clock is only checked in it.
15
+ // ZTO works on Shanghai's clock; a scan's clock is checked in it.
16
16
  const HOME_ZONE = 'Asia/Shanghai';
17
+ // A waybill's departure from its origin: by air, by sea, by road.
18
+ const DEPARTURES = new Set(['220', '221', '222']);
17
19
  // The image code the page asks for when the tracker refuses a lookup.
18
20
  const CHALLENGE_CODES = new Set(['19001', '19002', '50003', '60005']);
19
21
  // The English line of a parcel taken in for the recipient says "Delivered".
@@ -74,11 +76,12 @@ function placeOf(chinese) {
74
76
  const place = clean(signed ?? /[[【]([^\]】]{1,60})[\]】]/.exec(chinese)?.[1], 60);
75
77
  return place && !NOT_PLACE.test(place) ? place : '';
76
78
  }
79
+ const homeClock = (local) => firstOccurrence(DateTime.fromISO(local, { zone: HOME_ZONE }));
77
80
  /** A scan's wall clock, checked by reading it back. */
78
81
  function localTimeOf(row) {
79
82
  const wall = WALL_CLOCK.exec(text(row, 'time', 64));
80
83
  const local = wall ? `${wall[1]}T${wall[2]}` : '';
81
- const home = local ? firstOccurrence(DateTime.fromISO(local, { zone: HOME_ZONE })) : null;
84
+ const home = local ? homeClock(local) : null;
82
85
  // Luxon rolls 24:00:00 over to the next day; only a clock that reads back unchanged is kept.
83
86
  if (!home?.isValid || home.toFormat("yyyy-MM-dd'T'HH:mm:ss") !== local)
84
87
  throw new SchemaError(PROVIDER, 'ZTO International returned an invalid scan time');
@@ -106,6 +109,22 @@ function projectRow(row, number) {
106
109
  ...(staged ? { stage: staged.stage, stage_source: staged.source } : {}),
107
110
  };
108
111
  }
112
+ /**
113
+ * ZTO names the warehouse or country of an origin leg abroad ("[美国]", "[日本]") and no place on
114
+ * its own leg in China. Unplaced, that leg's scans would borrow a zone from the destination's,
115
+ * across the flight. So when nothing up to the first departure names a place, those scans are
116
+ * dated on Shanghai's clock and keep their wall clock. `events` are newest first.
117
+ */
118
+ function dateHomeLeg(events) {
119
+ let departure = -1;
120
+ events.forEach((event, index) => { if (DEPARTURES.has(event.provider_code ?? ''))
121
+ departure = index; });
122
+ if (departure < 0 || events.slice(departure).some((event) => event.location))
123
+ return events;
124
+ return events.map((event, index) => index < departure ? event : {
125
+ ...event, time: homeClock(event.local_time).toISO({ suppressMilliseconds: true }), time_zone: HOME_ZONE, time_zone_basis: 'carrier',
126
+ });
127
+ }
109
128
  /** The tracker's refusal: an image code is a challenge, anything else is inconclusive. */
110
129
  function failure(error) {
111
130
  const code = isRecord(error) ? cleanScalar(error.code, 32) : '';
@@ -156,17 +175,18 @@ export function parseZtoInternational(payload, raw) {
156
175
  }
157
176
  if (!events.length)
158
177
  throw new NoHistoryError(PROVIDER, 'ZTO International has no scans for the waybill yet');
159
- const newest = events[0];
160
- const current = events.find((event) => event.stage);
178
+ const dated = dateHomeLeg(events);
179
+ const newest = dated[0];
180
+ const current = dated.find((event) => event.stage);
161
181
  const stage = current?.stage;
162
182
  return {
163
183
  status: stage ? languageStageStatus(stage) : 'unknown',
164
184
  ...(stage ? { current_stage: stage, current_stage_source: current.stage_source } : {}),
165
185
  last_status_text: newest.description,
166
- last_update: null,
186
+ last_update: newest.time ?? null,
167
187
  last_update_local: newest.local_time,
168
188
  expected_delivery: null,
169
- events: events.slice(0, MAX_EVENTS),
170
- ...(events.length > MAX_EVENTS ? { history_truncated: true } : {}),
189
+ events: dated.slice(0, MAX_EVENTS),
190
+ ...(dated.length > MAX_EVENTS ? { history_truncated: true } : {}),
171
191
  };
172
192
  }
@@ -1,8 +1,9 @@
1
1
  import { classifyWording } from '../../core/status/index.js';
2
2
  /**
3
- * The stage of each scan code ZTO International's tracker sends. Codes below
4
- * 100 are ZTO's own network, at home and abroad; codes from 150 are ZTO
5
- * International's warehouses, line hauls and customs.
3
+ * The stage of each scan code ZTO International's tracker sends. Codes up to
4
+ * 16 are ZTO's own network, at home and abroad; codes from 150 are ZTO
5
+ * International's warehouses, line hauls and customs. On an export to Europe,
6
+ * codes 100, 375 and 415 copy the last-mile carrier's scans.
6
7
  */
7
8
  const CODES = new Map([
8
9
  ['1', 'accepted'],
@@ -15,6 +16,10 @@ const CODES = new Map([
15
16
  // Taken in by a parcel station or a neighbour for the recipient; the page
16
17
  // calls it delivered, but the collection follows as code 15.
17
18
  ['16', 'ready_for_pickup'],
19
+ // The order checked before the overseas warehouse receives the parcel.
20
+ ['30', 'registered'],
21
+ // Copied from the last-mile carrier abroad, as an arrival.
22
+ ['100', 'in_transit'],
18
23
  // The overseas warehouse received the parcel: the first scan of an import.
19
24
  ['150', 'accepted'],
20
25
  ['180', 'in_transit'],
@@ -33,6 +38,11 @@ const CODES = new Map([
33
38
  ['370', 'in_transit'],
34
39
  ['371', 'in_transit'],
35
40
  ['373', 'customs'],
41
+ // The destination's import clearance, finished.
42
+ ['375', 'in_transit'],
43
+ // A hand-over in the warehouse or to the last-mile carrier abroad.
44
+ ['415', 'in_transit'],
45
+ ['-581', 'exception'],
36
46
  ]);
37
47
  /** A scan's stage by its code, or by its wording when the code is new. */
38
48
  export function ztoInternationalStage(code, wording) {
@@ -13,6 +13,8 @@
13
13
  {"code": "5", "wording": "Dispatch Signed for", "language": "en", "stage": "delivered", "confirmedBy": "live", "note": "A delivery in ZTO's network in Cambodia, without the station the line names."},
14
14
  {"code": "15", "wording": "Delivered", "language": "en", "stage": "delivered", "confirmedBy": "live", "note": "Signed for with the pickup code a parcel station gave the recipient."},
15
15
  {"code": "16", "wording": "Received on the recipient's behalf", "language": "en", "stage": "ready_for_pickup", "confirmedBy": "live", "note": "The adapter's wording: the English line says Delivered, and the Chinese line names who took the parcel in, a parcel station in the histories seen. The collection follows as code 15."},
16
+ {"code": "30", "wording": "The order has been reviewed", "language": "en", "stage": "registered", "confirmedBy": "live", "firstSeen": "2026-10-22", "note": "The first row of an export to Europe, two days before the warehouse received the parcel."},
17
+ {"code": "100", "wording": "Arrival at inward office of exchange.", "language": "en", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-10-22", "note": "A row copied from the last-mile carrier abroad; the copy seen matched its out-for-delivery scan, read on ZTO's words alone."},
16
18
  {"code": "150", "wording": "Parcel entered the warehouse", "language": "en", "stage": "accepted", "confirmedBy": "live", "note": "The overseas warehouse, in the United States, Japan, Korea or Hong Kong, received the parcel."},
17
19
  {"code": "180", "wording": "Packed into shipping bag", "language": "en", "stage": "in_transit", "confirmedBy": "live"},
18
20
  {"code": "190", "wording": "Parcel left the warehouse", "language": "en", "stage": "in_transit", "confirmedBy": "live"},
@@ -28,6 +30,9 @@
28
30
  {"code": "360", "wording": "Import customs in progress.", "language": "en", "stage": "customs", "confirmedBy": "live"},
29
31
  {"code": "370", "wording": "clearance finish", "language": "en", "stage": "in_transit", "confirmedBy": "live"},
30
32
  {"code": "371", "wording": "clearance finish", "language": "en", "stage": "in_transit", "confirmedBy": "live", "note": "The Chinese line adds that the parcel enters domestic transport."},
31
- {"code": "373", "wording": "Customs inspection", "language": "en", "stage": "customs", "confirmedBy": "live", "note": "Chinese customs picked the parcel for inspection."}
33
+ {"code": "373", "wording": "Customs inspection", "language": "en", "stage": "customs", "confirmedBy": "live", "note": "Chinese customs picked the parcel for inspection."},
34
+ {"code": "375", "wording": "clearance completed, customs", "language": "en", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-10-22", "note": "The destination's import clearance, finished, as the last-mile carrier abroad reports it."},
35
+ {"code": "415", "wording": "Express shipment / transshipment", "language": "en", "stage": "in_transit", "confirmedBy": "live", "firstSeen": "2026-10-22", "note": "After the warehouse's receipt and when the last-mile carrier abroad passes the parcel on."},
36
+ {"code": "-581", "wording": "Parcel with exceptions", "language": "en", "stage": "exception", "confirmedBy": "live", "firstSeen": "2026-10-22", "note": "A problem the row does not name, after the last mile's delivery round."}
32
37
  ]
33
38
  }
@@ -169,6 +169,7 @@ import { statusMap as estesForwarding } from '../../carriers/estes-forwarding/st
169
169
  import { statusMap as eurodis } from '../../carriers/eurodis/status.js';
170
170
  import { statusMap as euroexpressBa } from '../../carriers/euroexpress-ba/status.js';
171
171
  import { statusMap as evdemoLojistik } from '../../carriers/evdemo-lojistik/status.js';
172
+ import { statusMap as evri } from '../../carriers/evri/status.js';
172
173
  import { statusMap as evropat } from '../../carriers/evropat/status.js';
173
174
  import { statusMap as ewe } from '../../carriers/ewe/status.js';
174
175
  import { statusMap as exelot } from '../../carriers/exelot/status.js';
@@ -425,6 +426,7 @@ import { statusMap as sendex } from '../../carriers/sendex/status.js';
425
426
  import { statusMap as sending } from '../../carriers/sending/status.js';
426
427
  import { statusMap as serpost } from '../../carriers/serpost/status.js';
427
428
  import { statusMap as servientrega } from '../../carriers/servientrega/status.js';
429
+ import { statusMap as seur } from '../../carriers/seur/status.js';
428
430
  import { statusMap as sfExpress } from '../../carriers/sf-express/status.js';
429
431
  import { statusMap as sfc } from '../../carriers/sfc/status.js';
430
432
  import { statusMap as shadowfax } from '../../carriers/shadowfax/status.js';
@@ -509,6 +511,7 @@ import { statusMap as upaya } from '../../carriers/upaya/status.js';
509
511
  import { statusMap as ups } from '../../carriers/ups/status.js';
510
512
  import { statusMap as urbano } from '../../carriers/urbano/status.js';
511
513
  import { statusMap as urvaam } from '../../carriers/urvaam/status.js';
514
+ import { statusMap as usps } from '../../carriers/usps/status.js';
512
515
  import { statusMap as uzbekistanPost } from '../../carriers/uzbekistan-post/status.js';
513
516
  import { statusMap as valmo } from '../../carriers/valmo/status.js';
514
517
  import { statusMap as vaspExpresso } from '../../carriers/vasp-expresso/status.js';
@@ -567,7 +570,7 @@ export const STATUS_MAPS = {
567
570
  'early-bird': earlyBird, 'easy-mail': easyMail,
568
571
  econt, ecourier, 'egypt-post': egyptPost, elta, emile, 'ems-vietnam': emsVietnam, 'energia-tk': energiaTk, 'envia-colombia': enviaColombia,
569
572
  envialia, 'eparcel-kr': eparcelKr, 'epost-global': epostGlobal, equick, estes, 'estes-forwarding': estesForwarding, eurodis, 'euroexpress-ba': euroexpressBa,
570
- 'evdemo-lojistik': evdemoLojistik, evropat, ewe, exelot,
573
+ 'evdemo-lojistik': evdemoLojistik, evri, evropat, ewe, exelot,
571
574
  'exline-kz': exlineKz, exmate, 'express-one': expressOne, 'fan-courier': fanCourier, 'fargo-courier': fargoCourier, 'fargo-uz': fargoUz, fastdrop,
572
575
  fastfrate, 'fedex-freight': fedexFreight, 'fedex-pl': fedexPl, fermopoint, 'fg-delivery': fgDelivery, 'first-line': firstLine,
573
576
  'flash-express': flashExpress,
@@ -608,7 +611,7 @@ export const STATUS_MAPS = {
608
611
  'quicksilver-sa': quicksilverSa, quiqup, qwintry, raben, 'ram-hand-to-hand': ramHandToHand, redpack, redur, redx, relaytech,
609
612
  rivigo, 'rl-carriers': rlCarriers, roadrunner, 'royal-express-mm': royalExpressMm, 'royal-mail': royalMail,
610
613
  'rpx-indonesia': rpxIndonesia, sagawa, 'sagawa-global': sagawaGlobal, saia, sailpost, sameday, 'sapx-express': sapxExpress,
611
- 'sat-ukraine': satUkraine, 'saudi-post': saudiPost, seino, seko, sendex, sending, serpost, servientrega, 'sf-express': sfExpress, sfc, shadowfax, shipa, shipblu, shipentegra, shiprocket,
614
+ 'sat-ukraine': satUkraine, 'saudi-post': saudiPost, seino, seko, sendex, sending, serpost, servientrega, seur, 'sf-express': sfExpress, sfc, shadowfax, shipa, shipblu, shipentegra, shiprocket,
612
615
  'shree-anjani': shreeAnjani, 'shree-maruti': shreeMaruti, 'skroutz-last-mile': skroutzLastMile,
613
616
  skyking, 'skynet-sa': skynetSa, 'skynet-worldwide': skynetWorldwide, skypostal, slgtrax,
614
617
  'slovak-parcel-service': slovakParcelService,
@@ -620,7 +623,7 @@ export const STATUS_MAPS = {
620
623
  'thailand-post': thailandPost, thhy, tiki, tipsa, 'tirupati-courier': tirupatiCourier, 'tmm-express': tmmExpress, tnt, 'tnt-it': tntIt, 'today-kr': todayKr, toptrans, torrestir, 'total-express': totalExpress,
621
624
  trackon, 'tracx-logis': tracxLogis, transaher, transcorp, transmission, tsjy, 'tst-cf-express': tstCfExpress, tufesa, 'tws-express': twsExpress,
622
625
  'ubi-smart-parcel': ubiSmartParcel,
623
- 'united-delivery-service': unitedDeliveryService, upaya, ups, urbano, urvaam,
626
+ 'united-delivery-service': unitedDeliveryService, upaya, ups, urbano, urvaam, usps,
624
627
  'uzbekistan-post': uzbekistanPost, valmo, 'vasp-expresso': vaspExpresso, veloces, venipak, 'via-cargo': viaCargo, 'viettel-post': viettelPost,
625
628
  vimenpaq, 'vinted-go': vintedGo, vozovoz, 'vrl-logistics': vrlLogistics, wanbexpress, whistl, winit, 'xde-logistics': xdeLogistics, xdp, xlcourier, 'xpo-ltl': xpoLtl,
626
629
  yanwen, 'ycs-logistics': ycsLogistics, 'yilong-logistics': yilongLogistics, yto, yunexpress, zto, 'zto-international': ztoInternational,
@@ -230,7 +230,7 @@ export function trackingLanguageStage(description) {
230
230
  // absent recipient or a closed business. A scheduled attempt is not a missed
231
231
  // one, nor is a sender pickup.
232
232
  if (!/\bpick ?up (?:attempt|was attempted|unsuccessful|failed)|attempted (?:to )?pick ?up/.test(text)
233
- && /(?:attempted|tried) to deliver|\bcard(?:ed| left)\b|\bwe(?: have|'ve)? missed (?:you|each other)\b(?![^.;!?]*\bready for (?:pick ?up|collection)\b)|\battempted delivery\b(?! (?:is )?(?:scheduled|planned|expected|tomorrow|will)\b)|unsuccessful delivery|delivery (?:was )?unsuccessful|unable to complete (?:the |your |this )?delivery|(?<!\bif (?:the |your )?)\b(?:recipient|addressee|consignee|customer|receiver|user)(?: was| is)? (?:not (?:at home|home|available|present)|unavailable|absent)\b|absence of (?:the )?(?:recipient|addressee|consignee)|\bbusiness (?:was |is )?closed\b|\bpremises (?:(?:was|were) )?closed\b|destinataire absent|(?<!\ben cas d')\babsence du destinataire|(?:entreprise|societe) (?:etait |est )?fermee?\b|nicht angetroffen|(?:firma|betrieb|geschaft) (?:war |ist )?geschlossen|destinatario assente|(?:attivita|azienda|ditta) (?:era |e )?chiusa/.test(text))
233
+ && /(?:attempted|tried) to deliver|\bcard(?:ed| left)\b|\bwe(?: have|'ve)? missed (?:you|each other)\b(?![^.;!?]*\bready for (?:pick ?up|collection)\b)|\battempted delivery\b(?! (?:is )?(?:scheduled|planned|expected|tomorrow|will)\b)|unsuccessful delivery|delivery (?:was )?(?:un|not )successful|unable to complete (?:the |your |this )?delivery|(?<!\bif (?:the |your )?)\b(?:recipient|addressee|consignee|customer|receiver|user)(?: was| is)? (?:not (?:at home|home|available|present)|unavailable|absent)\b|absence of (?:the )?(?:recipient|addressee|consignee)|\bbusiness (?:was |is )?closed\b|\bpremises (?:(?:was|were) )?closed\b|destinataire absent|(?<!\ben cas d')\babsence du destinataire|(?:entreprise|societe) (?:etait |est )?fermee?\b|nicht angetroffen|(?:firma|betrieb|geschaft) (?:war |ist )?geschlossen|destinatario assente|(?:attivita|azienda|ditta) (?:era |e )?chiusa/.test(text))
234
234
  return 'failed_attempt';
235
235
  // The same in Spanish and Portuguese ("Ausente"), where "recogida" and
236
236
  // "recolha" name the sender pickup and "si"/"caso" make absence a condition.
@@ -2524,6 +2524,28 @@
2524
2524
  "smartposti.lv"
2525
2525
  ],
2526
2526
  "path": "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$"
2527
+ },
2528
+ {
2529
+ "domains": [
2530
+ "smartposti.ee",
2531
+ "smartposti.lt",
2532
+ "smartposti.lv"
2533
+ ],
2534
+ "pathPattern": "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$",
2535
+ "params": [
2536
+ "trackingCode"
2537
+ ]
2538
+ },
2539
+ {
2540
+ "domains": [
2541
+ "itella.ee",
2542
+ "itella.lt",
2543
+ "itella.lv"
2544
+ ],
2545
+ "pathPattern": "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$",
2546
+ "params": [
2547
+ "trackingCode"
2548
+ ]
2527
2549
  }
2528
2550
  ],
2529
2551
  "detectionRules": [
@@ -9665,7 +9687,14 @@
9665
9687
  ]
9666
9688
  }
9667
9689
  ],
9668
- "detectionRules": []
9690
+ "detectionRules": [
9691
+ {
9692
+ "pattern": "^[A-Z]{2}\\d{9}MZ$",
9693
+ "confidence": "low",
9694
+ "checksum": "s10",
9695
+ "preferred": true
9696
+ }
9697
+ ]
9669
9698
  },
9670
9699
  "correo-argentino": {
9671
9700
  "displayName": "Correo Argentino",
@@ -12808,7 +12837,14 @@
12808
12837
  ]
12809
12838
  }
12810
12839
  ],
12811
- "detectionRules": []
12840
+ "detectionRules": [
12841
+ {
12842
+ "pattern": "^E[A-Z]\\d{9}VN$",
12843
+ "confidence": "low",
12844
+ "checksum": "s10",
12845
+ "preferred": true
12846
+ }
12847
+ ]
12812
12848
  },
12813
12849
  "energia-tk": {
12814
12850
  "displayName": "TK Energia",
@@ -1673,6 +1673,14 @@ export declare const CARRIER_CATALOG: {
1673
1673
  }, {
1674
1674
  readonly domains: readonly ["smartposti.ee", "smartposti.lt", "smartposti.lv"];
1675
1675
  readonly path: "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$";
1676
+ }, {
1677
+ readonly domains: readonly ["smartposti.ee", "smartposti.lt", "smartposti.lv"];
1678
+ readonly pathPattern: "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$";
1679
+ readonly params: readonly ["trackingCode"];
1680
+ }, {
1681
+ readonly domains: readonly ["itella.ee", "itella.lt", "itella.lv"];
1682
+ readonly pathPattern: "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$";
1683
+ readonly params: readonly ["trackingCode"];
1676
1684
  }];
1677
1685
  readonly detectionRules: readonly [{
1678
1686
  readonly pattern: "^[A-Z]{2}\\d{9}FI$";
@@ -6300,7 +6308,12 @@ export declare const CARRIER_CATALOG: {
6300
6308
  readonly pathPattern: "^/cgi-bin/(?:tracktrace|trace)\\.cgi$";
6301
6309
  readonly params: readonly ["OBJ"];
6302
6310
  }];
6303
- readonly detectionRules: readonly [];
6311
+ readonly detectionRules: readonly [{
6312
+ readonly pattern: "^[A-Z]{2}\\d{9}MZ$";
6313
+ readonly confidence: "low";
6314
+ readonly checksum: "s10";
6315
+ readonly preferred: true;
6316
+ }];
6304
6317
  };
6305
6318
  readonly "correo-argentino": {
6306
6319
  readonly displayName: "Correo Argentino";
@@ -8311,7 +8324,12 @@ export declare const CARRIER_CATALOG: {
8311
8324
  readonly pathPattern: "^/tra-cuu/tra-cuu-buu-gui/?$";
8312
8325
  readonly params: readonly ["code"];
8313
8326
  }];
8314
- readonly detectionRules: readonly [];
8327
+ readonly detectionRules: readonly [{
8328
+ readonly pattern: "^E[A-Z]\\d{9}VN$";
8329
+ readonly confidence: "low";
8330
+ readonly checksum: "s10";
8331
+ readonly preferred: true;
8332
+ }];
8315
8333
  };
8316
8334
  readonly "energia-tk": {
8317
8335
  readonly displayName: "TK Energia";
@@ -2525,6 +2525,28 @@ export const CARRIER_CATALOG = {
2525
2525
  "smartposti.lv"
2526
2526
  ],
2527
2527
  "path": "^/(?:(?:[a-z]{2}/)?tracking|saadetise-jalgimine|siuntos-sekimas|pakas-meklesana)/([^/?#]+)(?:/[0-9]+)?/?$"
2528
+ },
2529
+ {
2530
+ "domains": [
2531
+ "smartposti.ee",
2532
+ "smartposti.lt",
2533
+ "smartposti.lv"
2534
+ ],
2535
+ "pathPattern": "^/(?:[a-z]{2}/)?receiving/parcel-tracking/?$",
2536
+ "params": [
2537
+ "trackingCode"
2538
+ ]
2539
+ },
2540
+ {
2541
+ "domains": [
2542
+ "itella.ee",
2543
+ "itella.lt",
2544
+ "itella.lv"
2545
+ ],
2546
+ "pathPattern": "^/(?:[a-z]{2}/)?(?:private|business)-customer/track-shipment/?$",
2547
+ "params": [
2548
+ "trackingCode"
2549
+ ]
2528
2550
  }
2529
2551
  ],
2530
2552
  "detectionRules": [
@@ -9666,7 +9688,14 @@ export const CARRIER_CATALOG = {
9666
9688
  ]
9667
9689
  }
9668
9690
  ],
9669
- "detectionRules": []
9691
+ "detectionRules": [
9692
+ {
9693
+ "pattern": "^[A-Z]{2}\\d{9}MZ$",
9694
+ "confidence": "low",
9695
+ "checksum": "s10",
9696
+ "preferred": true
9697
+ }
9698
+ ]
9670
9699
  },
9671
9700
  "correo-argentino": {
9672
9701
  "displayName": "Correo Argentino",
@@ -12809,7 +12838,14 @@ export const CARRIER_CATALOG = {
12809
12838
  ]
12810
12839
  }
12811
12840
  ],
12812
- "detectionRules": []
12841
+ "detectionRules": [
12842
+ {
12843
+ "pattern": "^E[A-Z]\\d{9}VN$",
12844
+ "confidence": "low",
12845
+ "checksum": "s10",
12846
+ "preferred": true
12847
+ }
12848
+ ]
12813
12849
  },
12814
12850
  "energia-tk": {
12815
12851
  "displayName": "TK Energia",
@@ -32929,7 +32965,7 @@ export const DETECTION_RULE_IDS = {
32929
32965
  "connect-couriers": [],
32930
32966
  "coordinadora": [],
32931
32967
  "copa-courier": ["copa-courier-1"],
32932
- "corre": [],
32968
+ "corre": ["corre-s10"],
32933
32969
  "correo-argentino": ["correo-argentino-s10"],
32934
32970
  "correo-uruguayo": ["correo-uruguayo-s10"],
32935
32971
  "correos-bolivia": [],
@@ -33007,7 +33043,7 @@ export const DETECTION_RULE_IDS = {
33007
33043
  "elta": ["elta-s10"],
33008
33044
  "emile": ["emile-1", "emile-2"],
33009
33045
  "emirates-post": [],
33010
- "ems-vietnam": [],
33046
+ "ems-vietnam": ["ems-vietnam-s10"],
33011
33047
  "energia-tk": [],
33012
33048
  "energo": ["energo-1"],
33013
33049
  "envia-colombia": [],
@@ -236,7 +236,9 @@ const CAINIAO_LAST_MILE = [
236
236
  * logged with the label, were dated up to six hours after ParcelsApp first
237
237
  * returned them on a parcel to Colombia, and come before that moment only on
238
238
  * China's clock, the parcel's origin (2026-10-16). Its scans in the
239
- * destination matched Cainiao's import customs to the minute on Colombia's.
239
+ * destination matched Cainiao's import customs to the minute on Colombia's,
240
+ * and its "Container reception" AliExpress's own receipt by the local
241
+ * delivery company, which states its offset.
240
242
  *
241
243
  * EMS's scans at the destination's office of exchange, from its arrival to its
242
244
  * departure, are on the destination's clock, as its origin leg is on the
@@ -280,7 +282,7 @@ const KNOWN_CLOCKS = new Map([
280
282
  ['mailamericas', [
281
283
  { wording: /^(?:order created|tax id|origin warehouse)\.|^transit to destination country\. (?:on the way to the airport|origin customs|flight)$/i,
282
284
  zone: ORIGIN },
283
- { wording: /^(?:arrived at destination country|destination customs|wi\s*[–-]\s*intermediate warehouse|receiving destination mail|received in destination office|delivery (?:branch|office)|finished)\.|^delivered\b/i,
285
+ { wording: /^(?:arrived at destination country|destination customs|wi\s*[–-]\s*intermediate warehouse|receiving destination mail|received in destination office|delivery (?:branch|office)|finished)\.|^delivered\b|^container reception$/i,
284
286
  zone: DESTINATION },
285
287
  ]],
286
288
  ['4pxexpress', [
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.637.1"
5
+ "version": "0.640.0"
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.637.1",
3
+ "version": "0.640.0-main.3020",
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",