From 05545fc84df89a31bb09a82c4f34faddbf340f00 Mon Sep 17 00:00:00 2001 From: Christian Billen Date: Sat, 21 Feb 2026 18:31:20 +0100 Subject: [PATCH 1/3] feat: add Canpar, Purolator, and Spee-Dee courier definitions Adds three new Canadian/regional courier definitions, each verified against real tracking numbers: - Canpar (22): letter [CDKLSUXZ] + 21 digits; no mathematical checksum (the 22nd character is a piece sequence number, not a check digit); regex-only validation. - Purolator (12): 12-digit PIN with verified Luhn checksum (17/17 real numbers confirmed); first digit constrained to [0-5] to avoid false positives against FedEx Express 12-digit numbers. - Purolator (alpha + 9): 3 uppercase letters + 9 digits; regex-only. - Spee-Dee Delivery (20): SP prefix + exactly 18 digits; no mathematical checksum (confirmed against 10 real numbers); regex-only validation. Co-authored-by: Cursor --- couriers/canpar.json | 26 +++++++++++++++++++++ couriers/purolator.json | 50 +++++++++++++++++++++++++++++++++++++++++ couriers/speedee.json | 26 +++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 couriers/canpar.json create mode 100644 couriers/purolator.json create mode 100644 couriers/speedee.json diff --git a/couriers/canpar.json b/couriers/canpar.json new file mode 100644 index 0000000..191310a --- /dev/null +++ b/couriers/canpar.json @@ -0,0 +1,26 @@ +{ + "name": "Canpar", + "courier_code": "canpar", + "tracking_numbers": [ + { + "name": "Canpar (22)", + "id": "canpar_22", + "regex": "\\s*(?[CDKLSUXZ]\\s*([0-9]\\s*){21})\\s*", + "validation": {}, + "tracking_url": "https://www.canpar.com/en/track/tracking.jsp?reference=%s&locale=en", + "test_numbers": { + "valid": [ + "D576002440000002510003", + "D576002440000002510001", + "D576002440000002508002", + "D576002440000002507003" + ], + "invalid": [ + "1576002440000002510003", + "D57600244000000251000", + "M576002440000002510003" + ] + } + } + ] +} diff --git a/couriers/purolator.json b/couriers/purolator.json new file mode 100644 index 0000000..89109b7 --- /dev/null +++ b/couriers/purolator.json @@ -0,0 +1,50 @@ +{ + "name": "Purolator", + "courier_code": "purolator", + "tracking_numbers": [ + { + "name": "Purolator (12)", + "id": "purolator_numeric", + "regex": "\\s*(?[0-5]\\s*([0-9]\\s*){10})(?[0-9]\\s*)", + "validation": { + "checksum": { + "name": "luhn" + } + }, + "tracking_url": "https://www.purolator.com/en/shipping/tracker?searchValue=%s", + "test_numbers": { + "valid": [ + "320595463938", + "320606146993", + "287809468872", + "320610804900", + "331426749957", + "331434972567" + ], + "invalid": [ + "320595463939", + "287809468875" + ] + } + }, + { + "name": "Purolator (alpha + 9)", + "id": "purolator_alpha", + "regex": "\\s*(?([A-Z]\\s*){3}([0-9]\\s*){9})\\s*", + "validation": {}, + "tracking_url": "https://www.purolator.com/en/shipping/tracker?searchValue=%s", + "test_numbers": { + "valid": [ + "KYV009956937", + "CGK002986959", + "JFV247545960", + "TLR000083964" + ], + "invalid": [ + "KY009956937", + "331426749957" + ] + } + } + ] +} diff --git a/couriers/speedee.json b/couriers/speedee.json new file mode 100644 index 0000000..cc59a29 --- /dev/null +++ b/couriers/speedee.json @@ -0,0 +1,26 @@ +{ + "name": "Spee-Dee Delivery", + "courier_code": "speedee", + "tracking_numbers": [ + { + "name": "Spee-Dee (20)", + "id": "speedee", + "regex": "\\s*S\\s*P\\s*(?([0-9]\\s*){18})\\s*", + "validation": {}, + "tracking_url": "https://www.speedeedelivery.com/track/?tracking=%s", + "test_numbers": { + "valid": [ + "SP029692510000920746", + "SP029692510000901479", + "SP029692510000688332", + "SP029692510000279301", + "SP029692450001607639" + ], + "invalid": [ + "029692510000920746", + "SX029692510000920746" + ] + } + } + ] +} From 51d7c1f5086e4c5135f0abdd0868bd4c81f56be8 Mon Sep 17 00:00:00 2001 From: Christian Billen Date: Sat, 21 Feb 2026 18:45:33 +0100 Subject: [PATCH 2/3] fix: address PR review feedback - canpar: add K-prefix valid test number to exercise the full [CDKLSUXZ] character class, not just D - purolator (12): move trailing \s* outside the CheckDigit group so the captured value contains only the digit, consistent with every other pattern in the repo Co-authored-by: Cursor --- couriers/canpar.json | 3 ++- couriers/purolator.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/couriers/canpar.json b/couriers/canpar.json index 191310a..90be4ab 100644 --- a/couriers/canpar.json +++ b/couriers/canpar.json @@ -13,7 +13,8 @@ "D576002440000002510003", "D576002440000002510001", "D576002440000002508002", - "D576002440000002507003" + "D576002440000002507003", + "K576002440000002507001" ], "invalid": [ "1576002440000002510003", diff --git a/couriers/purolator.json b/couriers/purolator.json index 89109b7..35a6278 100644 --- a/couriers/purolator.json +++ b/couriers/purolator.json @@ -5,7 +5,7 @@ { "name": "Purolator (12)", "id": "purolator_numeric", - "regex": "\\s*(?[0-5]\\s*([0-9]\\s*){10})(?[0-9]\\s*)", + "regex": "\\s*(?[0-5]\\s*([0-9]\\s*){10})(?[0-9])\\s*", "validation": { "checksum": { "name": "luhn" From 3902aeafb91e3606a9c73e809fcf498802c3955f Mon Sep 17 00:00:00 2001 From: Christian Billen Date: Sat, 21 Feb 2026 18:52:27 +0100 Subject: [PATCH 3/3] test(canpar): replace synthetic K-prefix with verified L and S examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the synthetically-constructed K576002... test number added during review with real L and S service-letter tracking numbers, confirming three distinct prefixes from actual shipment data. Also includes a piece-10 example (D576002440000001718010) and a piece-4 example (D576002440000001428004) which demonstrate that the 3-digit trailing field is a piece sequence number (001–NNN), not a check digit — a non-sequential multi-piece shipment (pieces 002 and 004 with no 003) was observed in the same dataset. Co-authored-by: Cursor --- couriers/canpar.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/couriers/canpar.json b/couriers/canpar.json index 90be4ab..7796668 100644 --- a/couriers/canpar.json +++ b/couriers/canpar.json @@ -10,11 +10,13 @@ "tracking_url": "https://www.canpar.com/en/track/tracking.jsp?reference=%s&locale=en", "test_numbers": { "valid": [ - "D576002440000002510003", - "D576002440000002510001", - "D576002440000002508002", - "D576002440000002507003", - "K576002440000002507001" + "D576002440000001718010", + "D576002440000001428004", + "D576002440000002496001", + "L576002440000000001004", + "L576002440000000004001", + "S576002440000000004001", + "S576002440000000025001" ], "invalid": [ "1576002440000002510003",