Open jpotvin opened 4 years ago
This rule returns seperate tables for grocery orders that are no_delivery
, charged_delivery
, and free_delivery
.
grocery_delivery_policy.rule
META
VERSION "0.0.1"
RUNTIME "0.4.0"
CRITICALITY "experimental"
MANAGER "Joseph Potvin <jpotvin@xalgorithms.org>"
AUTHOR "Joseph Potvin <jpotvin@xalgorithms.org>"
MAINTAINER "Ryan Fleck <ryan.fleck@protonmail.com>";
EFFECTIVE
IN "CA-ON"
FROM "2018-04-01T00:00"
TO "9999-12-30T23:59"
TIMEZONE "America/Toronto"
WHEN envelope:type == "delivery_inquiry";
WHEN envelope:organization == "abc_grocers";
WHEN items:delivery_is_available == "X";
WHEN items:delivered == "N";
REQUIRE org.xalgorithms.examples.grocery_delivery_records:0.0.1 AS table_origin;
ASSEMBLE delivery_data
COLUMNS ( "shipment_id", "delivery_is_available", "box_type_is_standard", "box_half_full", "box_contents_value", "delivered" ) FROM table_origin; // Everything but the contents of the order.
REFINE delivery_data AS no_delivery
FILTER "box_half_full" == "Y";
REFINE delivery_data AS charged_delivery_standard
FILTER "box_half_full" != "Y"
FILTER "box_type_is_standard" != "Y"
FILTER "box_contents_value" > 100.00
REFINE delivery_data AS charged_delivery_non_standard
FILTER "box_half_full" != "Y"
FILTER "box_type_is_standard" != "N"
ASSEMBLE charged_delivery
COLUMNS FROM charged_delivery_standard
COLUMNS FROM charged_delivery_non_standard
REFINE delivery_data AS free_delivery
FILTER "box_half_full" != "Y"
FILTER "box_type_is_standard" == "N"
FILTER "box_contents_value" < 100.00
grocery_delivery_records.table
META
VERSION "0.0.1"
RUNTIME "0.4.0"
CRITICALITY "experimental"
MANAGER "Joseph Potvin <jpotvin@xalgorithms.org>"
AUTHOR "Joseph Potvin <jpotvin@xalgorithms.org>"
MAINTAINER "Ryan Fleck <ryan.fleck@protonmail.com>";
DATA grocery_delivery_records.json
grocery_delivery_records.json
[
{
"shipment_id":"ZQ430928",
"delivery_is_available":"",
"box_half_full":"N",
"box_type_is_standard":"-",
"box_contents_value":0.00,
"delivered":"N",
"contents":{
}
},
{
"shipment_id":"BA904209",
"delivery_is_available":"X",
"box_volume_occupied > box_capacity/2":"Y",
"box_type_is_standard":"Y",
"box_contents_value":78.00,
"delivered":"N",
"contents":{
}
},
{
"shipment_id":"HT5928",
"delivery_is_available":"X",
"box_half_full":"Y",
"box_type_is_standard":"Y",
"box_contents_value":847.00,
"delivered":"N",
"contents":{
}
},
{
"shipment_id":"HT5928",
"delivery_is_available":"X",
"box_half_full":"Y",
"box_type_is_standard":"N",
"box_contents_value":14.00,
"delivered":"N",
"contents":{
}
}
]
This attempt yields a final table with a list of deliveries that should have free shipping. It's incomplete though. I'd like to be able to generate a final table that has the final delivery charge for every package.
First, the tables:
[
{ "id": 1, "box" : "standard", "volume" : 2, "weight" : 5, "value" : 100 },
{ "id": 2, "box" : "standard", "volume" : 5, "weight" : 6, "value" : 105 },
{ "id": 3, "box" : "non_standard", "volume" : 7, "weight" : 10, "value" : 300 },
{ "id": 4, "box" : "non_standard", "volume" : 3, "weight" : 6, "value" : 200 },
{ "id": 5, "box" : "standard", "volume" : 5, "weight" : 6, "value" : 75 },
{ "id": 6, "box" : "standard", "volume" : 6, "weight" : 6, "value" : 120 }
]
The boxes
table lists all of the deliveries to be made. It has some columns:
id
: some sort of unique idbox
: the type of boxvolume
: the occupied volumeweight
: the weight (unused)value
: the price of the box[
{ "name" : "standard", "capacity" : 6 },
{ "name" : "non_standard", "capacity" : 8 }
]
The types
table contains name
and capacity
which tells us the full capacity of each type of box.
[
{ "code" : "standard", "charge" : 4, "min_value" : 0 },
{ "code" : "standard_free", "charge" : 0, "min_value" : 99.99 },
{ "code" : "non_standard", "charge" : 5, "min_value" : 0 }
]
The costs
table lists different types of deliveries, their charge
for delivery and the min_value
needed for the charge.
The rule:
META
VERSION "0.0.1"
MAINTAINER "Don Kelly <karfai@gmail.com>";
REQUIRE grocery:boxes:0.0.1 AS boxes;
REQUIRE grocery:types:0.0.1 AS types;
REQUIRE grocery:costs:0.0.1 AS costs;
ASSEMBLE possibilities
COLUMNS FROM boxes
COLUMNS FROM types
COLUMNS FROM costs;
# standard_free w/ '_' because '-' fails to be a string in the parser
REFINE possibilities AS standard_boxes_with_free
FILTER @box==@name
FILTER @code=="standard_free"
FILTER @name=="standard";
REFINE standard_boxes_with_free AS standard_boxes_over_value
FILTER @value>@min_value
MAP over_cap=subtract(@volume, divide(@capacity, 2));
# wart b/c we can't seem to access @over_cap ^^ - probably a bug
REFINE standard_boxes_over_value AS free_deliveries
MAP charge_id=@id
FILTER @over_cap>0;
The rule loads all the tables and combines them into a full large dot product (possibilities
). This is refined into standard_boxes_with_free
that contains matching of the types of boxes in boxes
to those in types
. It is also filtered to only rows containing boxes of type standard
where they have a code
of standard_free
. We do this because this is the only data we want to work with.
The next REFINE
filters to only contain rows where the value
is greater than the min_value
for the code
. It also MAP
s a column called over_cap
that determines whether the box's volume
. This will be used in the next REFINE
to select only boxes that are over the volume
for free shipping. We do this in two REFINE
steps because there seems to be a bug where functions (divide(@capacity, 2)
) do not work in FILTER
statements. Otherwise, we'd just filter by volume>divide(@capacity, 2)
.
The final table (free_deliveries
) contains the boxes with free shipping:
0 | weight: 6 | name: standard | over_cap: 2 | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 6 | charge_id: 2 |
1 | weight: 6 | name: standard | over_cap: 3 | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 6 | charge_id: 6 |
Full output of ☝️:
tables:standard_boxes_over_value
0 | weight: 5 | name: standard | over_cap: -1 | code: standard_free | id: 1 | min_value: 99.99 | charge: 0 | box: standard | value: 100 | volume: 2 | capacity: 6 |
1 | weight: 6 | name: standard | over_cap: 2 | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 6 |
2 | weight: 6 | name: standard | over_cap: 3 | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 6 |
tables:standard_boxes_with_free
0 | weight: 5 | name: standard | code: standard_free | id: 1 | min_value: 99.99 | charge: 0 | box: standard | value: 100 | volume: 2 | capacity: 6 |
1 | weight: 6 | name: standard | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 6 |
2 | weight: 6 | name: standard | code: standard_free | id: 5 | min_value: 99.99 | charge: 0 | box: standard | value: 75 | volume: 5 | capacity: 6 |
3 | weight: 6 | name: standard | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 6 |
tables:boxes
0 | weight: 5 | id: 1 | box: standard | value: 100 | volume: 2 |
1 | weight: 6 | id: 2 | box: standard | value: 105 | volume: 5 |
2 | weight: 10 | id: 3 | box: non_standard | value: 300 | volume: 7 |
3 | weight: 6 | id: 4 | box: non_standard | value: 200 | volume: 3 |
4 | weight: 6 | id: 5 | box: standard | value: 75 | volume: 5 |
5 | weight: 6 | id: 6 | box: standard | value: 120 | volume: 6 |
tables:free_deliveries
0 | weight: 6 | name: standard | over_cap: 2 | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 6 | charge_id: 2 |
1 | weight: 6 | name: standard | over_cap: 3 | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 6 | charge_id: 6 |
tables:possibilities
0 | weight: 5 | name: standard | code: standard | id: 1 | min_value: 0 | charge: 4 | box: standard | value: 100 | volume: 2 | capacity: 6 |
1 | weight: 5 | name: standard | code: standard_free | id: 1 | min_value: 99.99 | charge: 0 | box: standard | value: 100 | volume: 2 | capacity: 6 |
2 | weight: 5 | name: standard | code: non_standard | id: 1 | min_value: 0 | charge: 5 | box: standard | value: 100 | volume: 2 | capacity: 6 |
3 | weight: 5 | name: non_standard | code: standard | id: 1 | min_value: 0 | charge: 4 | box: standard | value: 100 | volume: 2 | capacity: 8 |
4 | weight: 5 | name: non_standard | code: standard_free | id: 1 | min_value: 99.99 | charge: 0 | box: standard | value: 100 | volume: 2 | capacity: 8 |
5 | weight: 5 | name: non_standard | code: non_standard | id: 1 | min_value: 0 | charge: 5 | box: standard | value: 100 | volume: 2 | capacity: 8 |
6 | weight: 6 | name: standard | code: standard | id: 2 | min_value: 0 | charge: 4 | box: standard | value: 105 | volume: 5 | capacity: 6 |
7 | weight: 6 | name: standard | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 6 |
8 | weight: 6 | name: standard | code: non_standard | id: 2 | min_value: 0 | charge: 5 | box: standard | value: 105 | volume: 5 | capacity: 6 |
9 | weight: 6 | name: non_standard | code: standard | id: 2 | min_value: 0 | charge: 4 | box: standard | value: 105 | volume: 5 | capacity: 8 |
10 | weight: 6 | name: non_standard | code: standard_free | id: 2 | min_value: 99.99 | charge: 0 | box: standard | value: 105 | volume: 5 | capacity: 8 |
11 | weight: 6 | name: non_standard | code: non_standard | id: 2 | min_value: 0 | charge: 5 | box: standard | value: 105 | volume: 5 | capacity: 8 |
12 | weight: 10 | name: standard | code: standard | id: 3 | min_value: 0 | charge: 4 | box: non_standard | value: 300 | volume: 7 | capacity: 6 |
13 | weight: 10 | name: standard | code: standard_free | id: 3 | min_value: 99.99 | charge: 0 | box: non_standard | value: 300 | volume: 7 | capacity: 6 |
14 | weight: 10 | name: standard | code: non_standard | id: 3 | min_value: 0 | charge: 5 | box: non_standard | value: 300 | volume: 7 | capacity: 6 |
15 | weight: 10 | name: non_standard | code: standard | id: 3 | min_value: 0 | charge: 4 | box: non_standard | value: 300 | volume: 7 | capacity: 8 |
16 | weight: 10 | name: non_standard | code: standard_free | id: 3 | min_value: 99.99 | charge: 0 | box: non_standard | value: 300 | volume: 7 | capacity: 8 |
17 | weight: 10 | name: non_standard | code: non_standard | id: 3 | min_value: 0 | charge: 5 | box: non_standard | value: 300 | volume: 7 | capacity: 8 |
18 | weight: 6 | name: standard | code: standard | id: 4 | min_value: 0 | charge: 4 | box: non_standard | value: 200 | volume: 3 | capacity: 6 |
19 | weight: 6 | name: standard | code: standard_free | id: 4 | min_value: 99.99 | charge: 0 | box: non_standard | value: 200 | volume: 3 | capacity: 6 |
20 | weight: 6 | name: standard | code: non_standard | id: 4 | min_value: 0 | charge: 5 | box: non_standard | value: 200 | volume: 3 | capacity: 6 |
21 | weight: 6 | name: non_standard | code: standard | id: 4 | min_value: 0 | charge: 4 | box: non_standard | value: 200 | volume: 3 | capacity: 8 |
22 | weight: 6 | name: non_standard | code: standard_free | id: 4 | min_value: 99.99 | charge: 0 | box: non_standard | value: 200 | volume: 3 | capacity: 8 |
23 | weight: 6 | name: non_standard | code: non_standard | id: 4 | min_value: 0 | charge: 5 | box: non_standard | value: 200 | volume: 3 | capacity: 8 |
24 | weight: 6 | name: standard | code: standard | id: 5 | min_value: 0 | charge: 4 | box: standard | value: 75 | volume: 5 | capacity: 6 |
25 | weight: 6 | name: standard | code: standard_free | id: 5 | min_value: 99.99 | charge: 0 | box: standard | value: 75 | volume: 5 | capacity: 6 |
26 | weight: 6 | name: standard | code: non_standard | id: 5 | min_value: 0 | charge: 5 | box: standard | value: 75 | volume: 5 | capacity: 6 |
27 | weight: 6 | name: non_standard | code: standard | id: 5 | min_value: 0 | charge: 4 | box: standard | value: 75 | volume: 5 | capacity: 8 |
28 | weight: 6 | name: non_standard | code: standard_free | id: 5 | min_value: 99.99 | charge: 0 | box: standard | value: 75 | volume: 5 | capacity: 8 |
29 | weight: 6 | name: non_standard | code: non_standard | id: 5 | min_value: 0 | charge: 5 | box: standard | value: 75 | volume: 5 | capacity: 8 |
30 | weight: 6 | name: standard | code: standard | id: 6 | min_value: 0 | charge: 4 | box: standard | value: 120 | volume: 6 | capacity: 6 |
31 | weight: 6 | name: standard | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 6 |
32 | weight: 6 | name: standard | code: non_standard | id: 6 | min_value: 0 | charge: 5 | box: standard | value: 120 | volume: 6 | capacity: 6 |
33 | weight: 6 | name: non_standard | code: standard | id: 6 | min_value: 0 | charge: 4 | box: standard | value: 120 | volume: 6 | capacity: 8 |
34 | weight: 6 | name: non_standard | code: standard_free | id: 6 | min_value: 99.99 | charge: 0 | box: standard | value: 120 | volume: 6 | capacity: 8 |
35 | weight: 6 | name: non_standard | code: non_standard | id: 6 | min_value: 0 | charge: 5 | box: standard | value: 120 | volume: 6 | capacity: 8 |
tables:types
0 | name: standard | capacity: 6 |
1 | name: non_standard | capacity: 8 |
tables:costs
0 | code: standard | charge: 4 | min_value: 0 |
1 | code: standard_free | charge: 0 | min_value: 99.99 |
2 | code: non_standard | charge: 5 | min_value: 0 |
I still need to step through fully what you have above, but here is a possible target Xalgo expression for this which contains all the required information (I reckon) and is readily understandable and expressible by a non-technical 'rule author':
META; |R4qG9UeKH5hW| : “ruleid”; |0.0.1| : “version”; |0.5.0| : “runtime”; |experimental| : “criticality”; |010 001 100 001 100 010 010 111 011| : “semantics”; |Joseph Potvin|<jpotvin@xalgorithms.org| : “manager”; |Joseph Potvin|<jpotvin@xalgorithms.org| : “author”; |Ryan Fleck|ryan.fleck@protonmail.com| : “maintainer”; GIVEN; |CA|ON| : “jurisdictions”; |2018-04-01T00:00|9999-12-30T23:50| : “dates-times”; |UTC-05:00| : “timezones”; WHEN; 0|A|B|C|D| 1|N|Y|Y|N| : "box_at_least_half_full"; 2|-|Y|Y|N| : "box_type_is_standard"; 3|-|N|Y|-| : "box_contents_value" >= $100.00; THEN; 4| |X|X|X| : "delivery_is_available"; 5| |X| |X| : "delivery_is_charged".
Here's what the Singapore Stamp Duty looks like to me, adapting from the grocery store policy example: [NOTE: bah, Git changes underscore to a diagonal line and makes text italic, and I can't find a substitute character that works.]
WHEN 0|A|B|C|D|E|F| : 1|Y|Y|Y|Y|Y|Y| : "envelope:type" == "purchase-order"; 2|Y|Y|Y|Y|Y|Y| : "envelope:parties.buyer.industry.list_id" == "ISIC"; 3|Y|Y|Y|Y|Y|Y| : "envelope:parties.buyer.industry.value == "L6810"; 4|Y|Y|Y|Y|Y|Y| : "item:classification.listname" == "UNSPSC"; 5|Y|Y|Y|Y|Y|Y| : "item:classification.value == "80131600"; 6|Y|Y|Y|Y|Y|Y| : "item:price.currency.code == "SGD"; 7|Y|Y|Y|Y|Y|Y| : "item:price.value" > 0; 8|Y|||||| : "buyerprofile" == "sgc-1"; 9||Y||||| : "buyerprofile" == "sgc-2"; 10|||Y|||| : "buyerprofile" == "sgc-n"; 11||||Y||| : "buyerprofile" == "spr-1"; 12|||||Y|| : "buyerprofile" == "spr-n"; 13||||||Y| : "buyerprofile" == "sfe-n". THEN 14|X|||||| : "absdrate" = 0.00 * price.value; 15||X||||| : "absdrate" = 0.07 * price.value; 16|||X|||| : "absdrate" = 0.10 * price.value; 17||||X||| : "absdrate" = 0.05 * price.value; 18|||||X|| : "absdrate" = 0.10 * price.value; 19||||||X| : "absd_rate" = 0.15 * price.value;
Could you please write this example in the latest Xalgo? In the formal paper I'd like to finish off this comparison with our method. natural-structured-tree-table_comparison.pdf