1+ /* =====================================================
2+ * Gold Standard CML Constraint Model for Auto Silver Product
3+ * =====================================================
4+ A product configuration constraints CML file for Auto Silver Product
5+ covering use cases of:
6+ - Base constraint
7+ - Multi-instance constraint
8+ - Require coverage constraint
9+ - Exclude coverage constraint
10+ - Cardinality constraint
11+ - Parent cardinality constraint
12+ - Cross Product constraint
13+ - Grandchild products constraint
14+ - Siblings products constraint
15+ */
16+
17+ /*
18+ Get Quote/Sales Transaction Level Attribute from Context Definition, such as User Profile, Quote Start Date, etc.
19+ */
20+ @(contextPath = "SalesTransaction.UserProfile" , attributeSource = "ST" )
21+ extern string UserProfile;
22+
23+ /*
24+ Root Product Bundle: Auto Silver
25+ @closeRelation = true: tell the constraint engine not to add any new instances in the relation, but the ones added by users or require rules are allowed.
26+ @propagateUp = true: tell the constraint engine only propagate from child to parent for aggregation function such as sum, max, count, etc.
27+ @sequence = 1: Indicates the sequence in which the variables are configured. The constraint engine assigns the values based on the sequence, with the lowest in the
28+ sequence assigned first. (only effective within a type)
29+
30+ Multi-instance use case: using aggregate functions for attributes from child relations (see "relation auto : Vehicle").
31+ If there's only 1 instance allowed for this relation, then OK to get attributes without aggregate functions
32+ */
33+ type AutoSilver {
34+ @(closeRelation = true , propagateUp = true , sequence = 1 )
35+ relation auto : Vehicle {
36+ maxAutoValue = max (Auto_Value);
37+ minAutoYear = min (Year);
38+ maxDriverAccPoint1 = max (maxDriverAccPoint);
39+ autoHasAntiTheft = count (Has_Anti_Theft > 0 );
40+ }
41+
42+ @(sequence = 2 )
43+ relation bodilyinjurypropertydamage : BodilyInjuryPropertyDamage[0 ..1 ];
44+
45+ @(sequence = 3 )
46+ relation medicalpayments : MedicalPayments[0 ..1 ] {
47+ medicalLimit = Limit;
48+ medicalDeductible = Deductible;
49+ medicalInputUnitPrice = inputUnitPrice;
50+ }
51+
52+ /*
53+ Get Quote Line Item/Sales Transaction Item Level Attribute from Context Definition, such as Item Total Price, Item Discount Amount, etc.
54+ tagName should be the same as Attribute Tag
55+ */
56+ @(tagName = "ItemTotalPrice" )
57+ decimal (2 ) totalPrice;
58+
59+ /*
60+ Constraint 1: A bundle with the most expensive car over $50,000 and the oldest car before 2020 must have Medical Payment Coverage with $2000 limit
61+
62+ Multi-instance Constraint
63+ Require coverage constraint
64+ Cross Product constraint
65+ */
66+ boolean constraint1 = auto.maxAutoValue >= 50000 && auto.minAutoYear < 2020 ;
67+ require (constraint1, medicalpayments[MedicalPayments], "Auto add Medical Payments" );
68+ constraint (constraint1 && medicalpayments[MedicalPayments] > 0 -> medicalpayments[MedicalPayments].Limit == 2000 , "Medical coverage set to $2000 for older high-value vehicles" );
69+
70+ /*
71+ Constraint 5: A bundle of at least one car doesn't have Anti-Theft, at least one driver has accident point greater than 5, no medical payment coverage, and total price for product bundle over $100:
72+ - Must have BIPD coverage (bodily injury & property damage)
73+ - Attribute: Property damage per Accident Limit of BIPD must be hidden (see under type BodilyInjuryPropertyDamage)
74+ - Value: $2000 of Bodily Injury Per Accident Limit of BIPD must be hidden (see under type BodilyInjuryPropertyDamage)
75+
76+ @abort = true: preventing the constraint engine from recursively backtracking and causing timeout, instead, surface configuration error message when encountering.
77+
78+ Multi-instance Constraint
79+ Require coverage constraint
80+ Cardinality constraint
81+ Parent cardinality constraint
82+ Cross Product constraint
83+ Grandchild products constraint
84+ Siblings products constraint
85+ */
86+ boolean autoCondition = auto[Vehicle] > 0 && !auto.autoHasAntiTheft;
87+ boolean driverCondition = auto.maxDriverAccPoint1 > 5 ;
88+ boolean totalPriceCondition = totalPrice > 10 ;
89+ boolean constraint5 = autoCondition && driverCondition && totalPriceCondition && medicalpayments[MedicalPayments] == 0 ;
90+ @(abort = true )
91+ require (constraint5, bodilyinjurypropertydamage[BodilyInjuryPropertyDamage], "BIPD is required when Auto IsElectricVehicle and Antitheft is false and Driver accident points > 5 and Collision is selected and MedicalPayments is NOT selected" );
92+
93+
94+ /*
95+ Constraint 6: A bundle with Medical Payment coverage with limit $1000, opearted by standard user, and the unit price for medical payment is over $220:
96+ - Must have BIPD with Bodily Injury Per Person Limit $1000
97+
98+ Require coverage constraint
99+ Cardinality constraint
100+ Cross Product constraint
101+ Siblings products constraint
102+ */
103+ boolean constraint6 = medicalpayments[MedicalPayments] == 1 && medicalpayments.medicalDeductible == 500 && UserProfile == "Standard User" && medicalpayments.medicalInputUnitPrice > 220 ;
104+ require (constraint6, bodilyinjurypropertydamage[BodilyInjuryPropertyDamage], "BIPD is required when MedicalPayments is selected at 1k limit" );
105+ constraint (constraint6 && bodilyinjurypropertydamage[BodilyInjuryPropertyDamage] -> bodilyinjurypropertydamage[BodilyInjuryPropertyDamage].Bodily_Injury_Per_Person_Limit == 1000 );
106+ }
107+
108+ /*
109+ Product Classification: Auto
110+
111+ @configurable = false: prevent the engine from modifying/assigning the value of an attribute.
112+ Best Practices for variable initialization:
113+ - for int variables, if using picklist possible, using picklist. If not possible, please add domain (possible values).
114+ - for picklist variables, set default value if possible.
115+ - for variables that are not supposed to be modified by the constraint engine, add configurable = false annotation.
116+ */
117+ type Auto {
118+ @(configurable = false )
119+ decimal (2 ) Auto_Value;
120+
121+ int Year = [1980 ..2026 ];
122+
123+ @(configurable = false )
124+ string Colour;
125+
126+ @(configurable = false )
127+ string Make;
128+
129+ @(configurable = false )
130+ int License_Number;
131+
132+ string License_State;
133+
134+ @(configurable = false )
135+ string Model;
136+
137+ boolean Is_Electric_Vehicle;
138+
139+ boolean Has_Anti_Theft;
140+
141+ string AssetRecordName;
142+
143+ }
144+
145+ /*
146+ Product Bundle: Auto
147+ */
148+ type Vehicle : Auto {
149+ int maxDriverAccPoint = driver.maxDriverAccPoint;
150+
151+ @(sequence = 4 )
152+ relation collision : Collision[0 ..1 ];
153+
154+ @(sequence = 2 )
155+ relation comprehensive : Comprehensive[0 ..1 ];
156+
157+ @(sequence = 3 )
158+ relation uninsuredMotorist : UninsuredMotorist[0 ..1 ];
159+
160+ @(closeRelation = true , propagateUp = true , sequence = 1 )
161+ relation driver : AutoDriver[0 ..5 ] {
162+ maxDriverAccPoint = max (Driver_Accident_Points);
163+ }
164+
165+ /*
166+ Constraint 2: Car is newer than 2023 must have Collision Coverage with $5000 limit.
167+
168+ Require coverage constraint
169+ Cross Product constraint
170+ */
171+ boolean constraint2 = Year > 2023 ;
172+ constraint (constraint2 -> collision[Collision], "auto add collision coverage" );
173+ constraint (constraint2 && collision[Collision] > 0 -> collision[Collision].Limit == 5000 , "set collision limit to 5000" );
174+
175+ /*
176+ Constraint 3: Car is older than 2020 and Collision Coverage is selected with $200 deductible MUST NOT have Uninsured Motorist Coverage.
177+
178+ Exlude coverage constraint
179+ Cross Product constraint
180+ */
181+ boolean constraint3 = collision[Collision] > 0 && collision[Collision].Deductible == 200 && Year < 2020 ;
182+ exclude (constraint3, uninsuredMotorist[UninsuredMotorist]);
183+
184+ require (collision[Collision] > 0 && collision[Collision].Deductible == 200 && Year < 2020 && UserProfile == "Standard User" , comprehensive[Comprehensive]);
185+ message (Auto_Value > 500000 , "Error: Insurance cant be provided" ,"Error" );
186+ message (Auto_Value == null , "Error: Auto value cant be null" ,"Error" );
187+
188+ }
189+
190+ /*
191+ Product Classification: Driver
192+ Best Practices:
193+ */
194+ type Driver {
195+ int Driver_Accident_Points = [0 ..10 ];
196+
197+ int Driver_MVR_Points;
198+
199+ string Driving_License;
200+
201+ int Age_First_Licensed = [0 ..100 ];
202+
203+ //int Contact_Number;
204+
205+ @(configurable = false )
206+ string E_Mail;
207+
208+ @(configurable = false )
209+ string State;
210+
211+ @(configurable = false )
212+ int Age = [0 ..100 ];
213+
214+ @(configurable = false )
215+ string First_Name;
216+
217+ @(configurable = false )
218+ string Last_Name;
219+
220+ /*
221+ Constraint 4: Driver Age and First Licensed Age must be equal or greater than 16.
222+
223+ Base constraint
224+ */
225+ message (Age_First_Licensed < 16 , "Don’t know who gave you license to driver before 16, legally!" , "Warning" );
226+ message (Age < 16 , "Error”: Driver is underaged to be added to the quote" , "Error" );
227+
228+ }
229+
230+ /*
231+ Product: Driver
232+ */
233+ type AutoDriver : Driver;
234+
235+ /*
236+ Product: UninsuredMotorist
237+ */
238+ @(split = false )
239+ type UninsuredMotorist {
240+ int Deductible = [0 , 50 , 100 , 200 , 500 ];
241+
242+ int Property_Damage_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
243+
244+ int Bodily_Injury_Per_Person_Limit = [500 , 1000 , 1500 , 2000 ];
245+
246+ int Bodily_Injury_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
247+
248+ int Limit = [1000 , 2000 , 5000 , 10000 , 25000 , 50000 ];
249+
250+ int Number_Of_Days = [1 , 3 , 5 , 10 , 15 , 30 ];
251+
252+
253+ }
254+
255+ /*
256+ Product: Collision
257+ */
258+ @(split = false )
259+ type Collision {
260+ @(domainComputation = false )
261+ int Deductible = [0 , 50 , 100 , 200 , 500 ];
262+
263+ @(domainComputation = false )
264+ int Property_Damage_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
265+
266+ @(domainComputation = false )
267+ int Bodily_Injury_Per_Person_Limit = [500 , 1000 , 1500 , 2000 ];
268+
269+ @(domainComputation = false )
270+ int Bodily_Injury_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
271+
272+ @(defaultValue = "1000" , domainComputation = false )
273+ int Limit = [1000 , 2000 , 5000 , 10000 , 25000 , 50000 ];
274+
275+ int Number_Of_Days = [1 , 3 , 5 , 10 , 15 , 30 ];
276+
277+ }
278+
279+ /*
280+ Product: Bodily Injury Property Damage
281+ */
282+ @(split = false )
283+ type BodilyInjuryPropertyDamage {
284+ @(defaultValue = "100" , domainComputation = false )
285+ int Deductible = [0 , 50 , 100 , 200 , 500 ];
286+
287+ @(defaultValue = "1000" , domainComputation = false )
288+ int Bodily_Injury_Per_Person_Limit = [500 , 1000 , 1500 , 2000 ];
289+
290+ @(defaultValue = "1000" , domainComputation = false )
291+ int Property_Damage_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
292+
293+ @(defaultValue = "1000" , domainComputation = false )
294+ int Bodily_Injury_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
295+
296+ @(defaultValue = "1000" , domainComputation = false )
297+ int Limit = [1000 , 2000 , 5000 , 10000 , 25000 , 50000 ];
298+
299+ int Number_Of_Days = [1 , 3 , 5 , 10 , 15 , 30 ];
300+ /*
301+ Constraint 5: A bundle of at least one car doesn't have Anti-Theft, at least one driver has accident point greater than 5, no medical payment coverage, and total price for product bundle over $100:
302+ - Must have BIPD coverage (see under AutoSilver)
303+ - Attribute: Property damage per Accident Limit of BIPD must be hidden
304+ - Value: $2000 of Bodily Injury Per Accident Limit of BIPD must be hidden
305+ */
306+ boolean rootconstraint5 = parent (constraint5);
307+ rule (rootconstraint5, "hide" , "attribute" , "Property_Damage_Per_Accident_Limit" );
308+ rule (rootconstraint5, "hide" , "attribute" , "Bodily_Injury_Per_Accident_Limit" ,"value" , 2000 );
309+
310+ }
311+
312+ /*
313+ Product: Medical Payments
314+ @split = false: there is only one instance in the relationship.
315+ */
316+ @(split = false )
317+ type MedicalPayments {
318+ @(defaultValue = "100" , domainComputation = false )
319+ int Deductible = [0 , 50 , 100 , 200 , 500 ];
320+
321+ int Property_Damage_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
322+
323+ int Bodily_Injury_Per_Person_Limit = [500 , 1000 , 1500 , 2000 ];
324+
325+ int Bodily_Injury_Per_Accident_Limit = [500 , 1000 , 1500 , 2000 ];
326+
327+ @(defaultValue = "1000" , domainComputation = false )
328+ int Limit = [1000 , 2000 , 5000 , 10000 , 25000 , 50000 ];
329+
330+ int Number_Of_Days = [1 , 3 , 5 , 10 , 15 , 30 ];
331+
332+ @(tagName = "InputUnitPrice" )
333+ decimal (2 ) inputUnitPrice;
334+
335+ }
336+
337+ /*
338+ Product: Comprehensive
339+ */
340+ @(split = false )
341+ type Comprehensive {
342+ @(defaultValue = "100" , domainComputation = false )
343+ int Deductible = [0 , 50 , 100 , 200 , 500 ];
344+
345+ @(defaultValue = "1000" , domainComputation = false )
346+ int Limit = [1000 , 2000 , 5000 , 10000 , 25000 , 50000 ];
347+
348+ int Number_Of_Days = [1 , 3 , 5 , 10 , 15 , 30 ];
349+
350+ }
0 commit comments