RESOStandards / transport

RESO Transport Workgroup - Specifications and Change Proposals
https://transport.reso.org
Other
18 stars 15 forks source link

Add specification for RCP-019: Business Rules #114

Closed bryanburgers closed 8 months ago

bryanburgers commented 8 months ago

Add a specification for the Business Rules (RCP-019) standard.

Based on some of the things @darnjo and I talked about, especially regarding audiences. The standard includes a non-normative explainer for people who want to read what the spec is about, and more a detailed specification of the api, language, and execution of rules.

Rendered

The grammar in this pull request succeeds against a large number of test rules (mostly pulled from an open source corpus of RETS VE test rules found at here).

Complete list of test rules, one per line ``` Number Bool String [Number] [Bool] [String] LAST Number LAST Bool LAST String [LAST Number] [LAST Bool] [LAST String] Number = Number Bool = Bool String = String Two + Three Three - Two Two * Three Six / Two Tau + Two Tau / Two Tau * Euler > 17.079 .AND. Tau * Euler < 17.081 Six .MOD. Two Three .MOD. Two Greeting || Subject Greeting || ', ' || Subject Greeting || ", " || Subject Date + 1 1 + Date Date - 1 Timestamp + 1 1 + Timestamp Timestamp - 1 Timestamp + 1.0 1.0 + Timestamp Timestamp - 1.0 Timestamp + (1.0 / (24 * 60)) Timestamp - (1.0 / (24 * 60)) Date - DateTwo (Timestamp - TimestampTwo) > (0.9 / (24 * 60)) .AND. (Timestamp - TimestampTwo) < (1.1 / (24 * 60)) T .AND. F T .AND. T T .OR. F F .OR. F One = 1 .AND. Two = 1 One = 1 .AND. Two = 2 One = 1 .OR. Two = 1 Two = 1 .OR. Two = 1 T .OR. F .AND. F (T .OR. F) .AND. F T .OR. (F .AND. F) .NOT. T .NOT. F .NOT. .NOT. T .NOT. .NOT. (T .OR. F) .NOT..NOT.T .NOT..NOT.(T.OR.F) .NOT. F .AND. T (.NOT. F) .AND. T .NOT. (F .AND. T) .NOT. T .OR. F (.NOT. T) .OR. F .NOT. (T .OR. F) .TRUE. .OR. 1 / 0 .FALSE. .AND. 1 / 0 UnspecifiedField != .EMPTY. .AND. UnspecifiedField > 0 UnspecifiedField = .EMPTY. .OR. UnspecifiedField > 0 IIF(Two > 1, 1+2, 1+4) IIF(Two < 1, 1+2, 1+4) IIF(Two > 1, 1, 1/0) IIF(UnspecifiedField = .EMPTY., 1, UnspecifiedField) IIF(Two = .EMPTY., 1, Two) BOOL(.TRUE.) BOOL(.FALSE.) BOOL('0') BOOL('1') BOOL('NO') BOOL('no') BOOL('YES') BOOL('yes') BOOL('faLSE') BOOL('True') CHAR('1') CHAR(1) CHAR(.FALSE.) CHAR(.TRUE.) CHARF(1.1, 1) CHARF(1, 1) TIME('2023-04-21') TIME('2023-04-21T01:02:03Z') DATE('2023-04-21') DATE('2023-04-21T01:02:03Z') INT(.TRUE.) INT(.FALSE.) INT(7) INT(7.32) INT('7') INT('7.32') FLOAT(.TRUE.) FLOAT(.FALSE.) FLOAT(7) FLOAT(7.32) FLOAT('7') FLOAT('7.32') SUBSTR('Example', 1, 1) SUBSTR('Example', 1, 2) SUBSTR('Example', 1, 4) SUBSTR('Example', 6, 8) SUBSTR('Example', 6, 10) SUBSTR('Example', 9, 10) STRLEN('') STRLEN('A') STRLEN('Example') LOWER('Example') UPPER('Example') YEAR(Date) YEAR(Timestamp) MONTH(Date) MONTH(Timestamp) DAY(Date) DAY(Timestamp) WEEKDAY(Date) WEEKDAY(Timestamp) TYPEOF(.TRUE.) TYPEOF(1) TYPEOF(1.1) TYPEOF('2023-04-21T01:02:03Z') TYPEOF('2023-04-21') TYPEOF('Example') SET() SET('1') SET('1', '2') SET('2', '1') SET('1', '2', '1') SET('a' || 'b' || 'c', 4 + 5, 'd' || 'e', 2 + 7) LIST() LIST('1') LIST('1', '2') LIST('2', '1') LIST('1', '2', '1') LIST('a' || 'b' || 'c', 4 + 5, 'd' || 'e', 2 + 7) () ('1') ('1', '2') ('2', '1') ('1', '2', '1') ('a' || 'b' || 'c', 4 + 5, 'd' || 'e', 2 + 7) UNION(LIST(1, 2, 3), LIST(4, 5, 6), LIST(7, 8, 9)) DIFFERENCE(LIST(1, 2, 3), LIST(1, 2, 4)) INTERSECTION(LIST(1, 2, 3), LIST(1, 2, 4)) LIST(1, 2, 3) .CONTAINS. 1 LIST(1, 2, 3) .CONTAINS. 5 1 .IN. LIST(1, 2, 3) 5 .IN. LIST(1, 2, 3) (1, 2, 3) .CONTAINS. 1 (1, 2, 3) .CONTAINS. 5 1 .IN. (1, 2, 3) 5 .IN. (1, 2, 3) IIF(SingleCategory = 'One', SET('One A', 'One B'), SET()) IIF(SingleCategory = 'Two', SET('Two A', 'Two B'), SET()) IIF(SingleCategory = 'Three', SET('Three A', 'Three B'), SET()) UNION(IIF(SingleCategory = 'One', SET('One A', 'One B'), SET()), IIF(SingleCategory = 'Two', SET('Two A', 'Two B'), SET()), IIF(SingleCategory = 'Three', SET('Three A', 'Three B'), SET())) IIF(MultiCategory .CONTAINS. 'One', SET('One A', 'One B'), SET()) IIF(MultiCategory .CONTAINS. 'Two', SET('Two A', 'Two B'), SET()) IIF(MultiCategory .CONTAINS. 'Three', SET('Three A', 'Three B'), SET()) UNION(IIF(MultiCategory .CONTAINS. 'One', SET('One A', 'One B'), SET()), IIF(MultiCategory .CONTAINS. 'Two', SET('Two A', 'Two B'), SET()), IIF(MultiCategory .CONTAINS. 'Three', SET('Three A', 'Three B'), SET())) LENGTH(EmptyList) LENGTH(OneItemList) LENGTH(TwoItemList) One = Two One != Two One > Two One >= Two One < Two One <= Two Two = One Two != One Two > One Two >= One Two < One Two <= One One = Two One != Two One > Two One >= Two One < Two One <= Two Two = One Two != One Two > One Two >= One Two < One Two <= One False = True False != True False > True False >= True False < True False <= True True = False True != False True > False True >= False True < False True <= False Null = .EMPTY. Number = .EMPTY. String = .EMPTY. True = .EMPTY. False = .EMPTY. UnspecifiedField = .EMPTY. Null != .EMPTY. Number != .EMPTY. String != .EMPTY. True != .EMPTY. False != .EMPTY. UnspecifiedField != .EMPTY. Null > .EMPTY. Number > .EMPTY. String > .EMPTY. True > .EMPTY. False > .EMPTY. UnspecifiedField > .EMPTY. Null >= .EMPTY. Number >= .EMPTY. String >= .EMPTY. True >= .EMPTY. False >= .EMPTY. UnspecifiedField >= .EMPTY. Null < .EMPTY. Number < .EMPTY. String < .EMPTY. True < .EMPTY. False < .EMPTY. UnspecifiedField < .EMPTY. Null <= .EMPTY. Number <= .EMPTY. String <= .EMPTY. True <= .EMPTY. False <= .EMPTY. UnspecifiedField <= .EMPTY. IIF(ParkingTotal = .EMPTY., 0, ParkingTotal) = IIF(GarageSpaces = .EMPTY., 0, GarageSpaces) + IIF(OpenParkingSpaces = .EMPTY., 0, OpenParkingSpaces) IIF(ParkingTotal = .EMPTY., 0, ParkingTotal) > IIF(GarageSpaces = .EMPTY., 0, GarageSpaces) + IIF(OpenParkingSpaces = .EMPTY., 0, OpenParkingSpaces) IIF(ParkingTotal = .EMPTY., 0, ParkingTotal) >= IIF(GarageSpaces = .EMPTY., 0, GarageSpaces) + IIF(OpenParkingSpaces = .EMPTY., 0, OpenParkingSpaces) IIF(ParkingTotal = .EMPTY., 0, ParkingTotal) < IIF(GarageSpaces = .EMPTY., 0, GarageSpaces) + IIF(OpenParkingSpaces = .EMPTY., 0, OpenParkingSpaces) IIF(ParkingTotal = .EMPTY., 0, ParkingTotal) <= IIF(GarageSpaces = .EMPTY., 0, GarageSpaces) + IIF(OpenParkingSpaces = .EMPTY., 0, OpenParkingSpaces) .TRUE. .FALSE. .EMPTY. "abc" 'abc' "" '' 100 100.0 3.14159 () (1) LIST(1) (1, 2) MATCH(String, 'is the') MATCH(String, 'not in there') MATCH(String, 'is\\s+the') MATCH(String, '^This') MATCH(String, '^is') MATCH(String, 'This is (the test|prod)') MATCH(String, '[0-9]{3,}$') MATCH(NotExists, 'anything') .NOW. .TODAY. .TODAY. ```

@darnjo feel free to take as much or as little of this as you want.

darnjo commented 8 months ago

Closing as a PR isn't the best place for this since it means something specific in the RESO domain (that something has been approved by the workgroups in DRAFT or APPROVED status).

Additional information can go into the ticket for a given issue, or a discussion topic.