Skip to content

Commit

Permalink
Merge pull request #84 from jkeen/feature/partners
Browse files Browse the repository at this point in the history
Model partnerships between shipping couriers
  • Loading branch information
jkeen authored May 15, 2023
2 parents dac72c3 + 4ff4ea9 commit 1439635
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 10 deletions.
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ This repository contains json files that programatically describe how to detect,
}
}
```
- `tracking_url` - A url that we can use to find the tracking
history for a particular tracking number. It assumes the
tracking number can be entered using python style
string formatting "www.courier.com?trackingnumber=%s".
- `tracking_url` - A url that we can use to find the tracking history for a particular tracking number. It assumes the tracking number can be entered using python style string formatting "www.courier.com?trackingnumber=%s".

- `test_numbers`:
- `valid`: an array of valid tracking numbers for testing
Expand Down Expand Up @@ -108,6 +105,35 @@ This repository contains json files that programatically describe how to detect,
Each hash in the `lookup` array should contain a key called `matches` or `matces_regex`, specifying how the value of `regex_group_name` should be compared.


- `partners` - Each entry of the partners array describes a possible partnership between carriers. A partnership is only valid if both ends of the partnership pass the checks. If the tracking number passes both sets of validation, this indicates that the shipment was handled by both parties, usually one acting as the _shipper_, and the other as the last mile _carrier_. Each item in the partners array should have:
- `partner_id`: (required) reference indicating the related definition
- `partner_type`: (required) indicating the type of relationship. Currently the two supported relationship types are `shipper` and `carrier`.
- `description`: (optional) mainly for humans reading this
- `validation`: (optional) a validation block that determins if this partnership applies
- `matches_all` or `matches_any`: array of match conditions. Each match condition must have a `regex_group_name` indicating the name of the regex group to match against, and then either a `matches` key or a `matches_regex` key with a string or a regex to match against

```json
//usps.json

"partners": [{
"partner_id": "fedex_smartpost",
"partner_type": "origin",
"description": "FedEx SmartPost uses USPS for last mile delivery, but not all USPS91 numbers are SmartPosts",
"validation": {
"matches_all": [
{
"regex_group_name": "ServiceType",
"matches": "29"
},
{
"regex_group_name": "SCNC",
"matches": "62"
}
]
}
}],
```


### Making a contribution
- Modify or add definitions in the couriers/*.json files. Take a look at the existing ones, and follow the guidance above.
Expand Down
27 changes: 22 additions & 5 deletions couriers/fedex.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,38 @@
},
{
"name": "FedEx SmartPost",
"description": "IMpb CO3 standard",
"id": "fedex_smartpost",
"description": "Shipped by FedEx, Delivered by USPS",
"regex": [
"\\s*(?:",
"(?:(?<RoutingApplicationId>4\\s*2\\s*0\\s*)(?<DestinationZip>([0-9]\\s*){5}))?",
"(?<ApplicationIdentifier>9\\s*2\\s*)",
")?",
"(?<SerialNumber>",
"(?<ServiceType>([0-9]\\s*){3})",
"(?<ShipperId>([0-9]\\s*){9})",
"(?<PackageId>([0-9]\\s*){7})",
"(?<SCNC>([0-9]\\s*){2})",
"(?<ServiceType>([0-9]\\s*){2})",
"(?<ShipperId>([0-9]\\s*){8})",
"(?<PackageId>([0-9]\\s*){11}|([0-9]\\s*){7})",
")",
"(?<CheckDigit>([0-9]\\s*))"
],
"additional": [
{
"name": "Service Type",
"regex_group_name": "ServiceType",
"lookup": [
{
"matches_regex": ".",
"name": "Delivered by USPS"
}
]
}
],
"partners": [{
"partner_id": "usps_91",
"partner_type": "carrier",
"description": "FedEx SmartPost is a shipping service that utilizes FedEx for the initial transport and the United States Postal Service for final delivery."
}],
"validation": {
"checksum": {
"name": "mod10",
Expand All @@ -121,7 +139,6 @@
"test_numbers": {
"valid": [
"61299998820821171811",
" 6 1 2 9 9 9 9 8 8 2 0 8 2 1 1 7 1 8 1 1 ",
"9261292700768711948021",
"420 11213 92 6129098349792366623 8",
"92 6129098349792366623 8",
Expand Down
35 changes: 34 additions & 1 deletion couriers/usps.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,23 @@
}
}
},
"partners": [{
"description": "FedEx SmartPost uses USPS for last mile delivery, but not all USPS91 numbers are SmartPosts",
"partner_type": "shipper",
"partner_id": "fedex_smartpost",
"validation": {
"matches_all": [
{
"regex_group_name": "ServiceType",
"matches": "29"
},
{
"matches": "61",
"regex_group_name": "SCNC"
}
]
}
}],
"tracking_url": "https://tools.usps.com/go/TrackConfirmAction?tLabels=%s",
"test_numbers": {
"valid": [
Expand All @@ -136,7 +153,23 @@
"420000000000000000000000000000",
"420000009200000000000000000000"
]
}
},
"additional": [
{
"name": "Service Type",
"regex_group_name": "ServiceType",
"lookup": [
{
"matches": "11",
"name": "First Class (R)"
},
{
"matches": "29",
"name": "Fedex Smart Post"
}
]
}
]
}
]
}
30 changes: 30 additions & 0 deletions spec/format_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ def parsed_json_file(file)
JSON.parse(File.read(file)).deep_symbolize_keys!
end

def tracking_data
@tracking_data ||= begin
data = {}
courier_files.each do |file|
file_name = file.split('/').last
json = parsed_json_file(file)

json[:tracking_numbers].each do |info|
data[info[:id]] = info
end
end

data
end
end

courier_files.each do |file|
file_name = file.split('/').last
data = parsed_json_file(file)
Expand Down Expand Up @@ -146,6 +162,20 @@ def parsed_json_file(file)
end
end
end

it 'should have a two way reference if the partner block is included' do
if info.keys.include?(:partners)
info[:partners].each do |partner|
expect(partner[:partner_id]).to(be_truthy)
expect(partner[:partner_type]).to(be_truthy)

partner_data = tracking_data[partner[:partner_id]]

expect(partner_data&.keys&.include?(:partners)).to(eq(true))
expect(partner_data[:partners]&.map { |p| p[:partner_id] }).to(include(info[:id]))
end
end
end
end
end
end
Expand Down

0 comments on commit 1439635

Please sign in to comment.