Skip to content

Commit a6e7449

Browse files
author
Kenneth Ocastro
committed
Add decision support
1 parent 4100616 commit a6e7449

File tree

3 files changed

+346
-2
lines changed

3 files changed

+346
-2
lines changed

src/Rule.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
class Rule
88
{
99
private array $conditions;
10+
private array $decisions;
1011
private array $event;
1112
private array $failureEvent;
1213
private ?string $name;
@@ -17,11 +18,12 @@ class Rule
1718
public function __construct(array $options)
1819
{
1920
$this->conditions = $options['conditions'] ?? [];
21+
$this->decisions = $options['decisions'] ?? [];
2022
$this->event = $options['event'] ?? [];
2123
$this->failureEvent = $options['failureEvent'] ?? [];
2224
$this->name = $options['name'] ?? null;
2325

24-
if (empty($this->conditions)) {
26+
if (empty($this->conditions) && empty($this->decisions)) {
2527
throw new Exception('Invalid rule: conditions are required');
2628
}
2729
if (empty($this->event)) {
@@ -44,10 +46,36 @@ public function getName(): ?string
4446
return $this->name;
4547
}
4648

49+
private function transformDecisionsToConditionWithAny(array $decisions): array
50+
{
51+
$newDecisions = array_map(function ($decision) {
52+
$newDecision = [
53+
'name' => $decision['name'],
54+
'event' => $decision['event'],
55+
];
56+
57+
foreach (['all', 'any', 'not'] as $conditionType) {
58+
if (isset($decision['conditions'][$conditionType])) {
59+
$newDecision[$conditionType] = $decision['conditions'][$conditionType];
60+
break;
61+
}
62+
}
63+
64+
return $newDecision;
65+
}, $decisions);
66+
67+
return ['any' => $newDecisions];
68+
}
69+
4770
public function evaluate(Facts $facts, array $allRules): bool
4871
{
4972
$this->failedConditions = []; // Reset failed conditions before evaluation
5073

74+
// if there's a 'decision' key, convert it to conditions with 'any' key
75+
if (!empty($this->decisions)) {
76+
$this->conditions = $this->transformDecisionsToConditionWithAny($this->decisions);
77+
}
78+
5179
if (isset($this->conditions['all'])) {
5280
return $this->evaluateAll($this->conditions['all'], $facts, $allRules);
5381
} elseif (isset($this->conditions['any'])) {

tests/EngineTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ public function testProfileIsSearchable()
215215

216216
public function testLivenessEncourageSelfieModalCongrats()
217217
{
218-
$rule = json_decode(file_get_contents('tests/data/rule.profile.liveness.encourageSelfieModal.json'), true);
218+
//$rule = json_decode(file_get_contents('tests/data/rule.profile.liveness.encourageSelfieModal.json'), true);
219+
$rule = json_decode(file_get_contents('tests/data/rule.profile.liveness.encourageSelfieModal-decisions.json'), true);
219220

220221
// Helper function to setup and evaluate the engine
221222
$evaluateEngine = function (array $factData) use ($rule) {
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
{
2+
"name": "rule.profile.liveness.encourageSelfieModal",
3+
"failureEvent": {
4+
"type": "rule.profile.liveness.encourageSelfieModal",
5+
"params": {
6+
"value": false,
7+
"message": "Encourage Selfie Modal not shown"
8+
}
9+
},
10+
"event": {
11+
"type": "Default",
12+
"params": {
13+
"value": true,
14+
"message": "Encourage Selfie Modal shown"
15+
}
16+
},
17+
"decisions": [
18+
{
19+
"name": "CongratsModal",
20+
"conditions": {
21+
"all": [
22+
{
23+
"fact": "profile",
24+
"path": "$.liveness.hasBadge",
25+
"operator": "equal",
26+
"value": true
27+
},
28+
{
29+
"fact": "profile",
30+
"path": "$.liveness.modalAlreadyShown",
31+
"operator": "equal",
32+
"value": false
33+
}
34+
]
35+
},
36+
"event": {
37+
"type": "CongratsModal",
38+
"params": {
39+
"value": true,
40+
"message": "Encourage Selfie Modal shown for CongratsModal"
41+
}
42+
}
43+
},
44+
{
45+
"name": "VoluntaryModal - (Within Dismissal Limit): Shows the modal every X hours unless dismissed Y times.",
46+
"conditions": {
47+
"all": [
48+
{
49+
"fact": "profile",
50+
"path": "$.liveness.hasPendingOrApprovedPhotoVerification",
51+
"operator": "equal",
52+
"value": false
53+
},
54+
{
55+
"fact": "profile",
56+
"path": "$.liveness.isLivenessApproved",
57+
"operator": "equal",
58+
"value": false
59+
},
60+
{
61+
"any": [
62+
{
63+
"fact": "profile",
64+
"path": "$.liveness.timeInHoursSinceLastModal",
65+
"operator": "greaterThanInclusive",
66+
"value": 12
67+
},
68+
{
69+
"fact": "profile",
70+
"path": "$.liveness.timeInHoursSinceLastModal",
71+
"operator": "equal",
72+
"value": 0
73+
}
74+
]
75+
},
76+
{
77+
"fact": "profile",
78+
"path": "$.liveness.dismissCount",
79+
"operator": "lessThan",
80+
"value": 3
81+
}
82+
]
83+
},
84+
"event": {
85+
"type": "VoluntaryModal",
86+
"params": {
87+
"value": true,
88+
"message": "Encourage Selfie Modal shown for VoluntaryModal (Within Dismissal Limit)"
89+
}
90+
}
91+
},
92+
{
93+
"name": "VoluntaryModal - (After 7-Day Cooldown): Shows the modal every X hours unless dismissed Y times.",
94+
"conditions": {
95+
"all": [
96+
{
97+
"fact": "profile",
98+
"path": "$.liveness.hasPendingOrApprovedPhotoVerification",
99+
"operator": "equal",
100+
"value": false
101+
},
102+
{
103+
"fact": "profile",
104+
"path": "$.liveness.isLivenessApproved",
105+
"operator": "equal",
106+
"value": false
107+
},
108+
{
109+
"fact": "profile",
110+
"path": "$.liveness.timeInHoursSinceLastModal",
111+
"operator": "greaterThanInclusive",
112+
"value": 168
113+
},
114+
{
115+
"fact": "profile",
116+
"path": "$.liveness.dismissCount",
117+
"operator": "greaterThanInclusive",
118+
"value": 3
119+
}
120+
]
121+
},
122+
"event": {
123+
"type": "VoluntaryModal",
124+
"params": {
125+
"value": true,
126+
"message": "Encourage Selfie Modal shown for VoluntaryModal (After 7-Day Cooldown)"
127+
}
128+
}
129+
},
130+
{
131+
"name": "VerificationJustGotBetterModal - Non-Badge Logic (Within Dismissal Limit): Shows the modal every X hours unless dismissed Y times.",
132+
"conditions": {
133+
"all": [
134+
{
135+
"fact": "profile",
136+
"path": "$.liveness.hasPendingOrApprovedPhotoVerification",
137+
"operator": "equal",
138+
"value": true
139+
},
140+
{
141+
"fact": "profile",
142+
"path": "$.liveness.hasBadge",
143+
"operator": "equal",
144+
"value": false
145+
},
146+
{
147+
"fact": "profile",
148+
"path": "$.liveness.isLivenessApproved",
149+
"operator": "equal",
150+
"value": false
151+
},
152+
{
153+
"any": [
154+
{
155+
"fact": "profile",
156+
"path": "$.liveness.timeInHoursSinceLastModal",
157+
"operator": "greaterThanInclusive",
158+
"value": 12
159+
},
160+
{
161+
"fact": "profile",
162+
"path": "$.liveness.timeInHoursSinceLastModal",
163+
"operator": "equal",
164+
"value": 0
165+
}
166+
]
167+
},
168+
{
169+
"fact": "profile",
170+
"path": "$.liveness.dismissCount",
171+
"operator": "lessThan",
172+
"value": 3
173+
}
174+
]
175+
},
176+
"event": {
177+
"type": "VerificationJustGotBetterModal",
178+
"params": {
179+
"value": true,
180+
"message": "Encourage Selfie Modal shown for VerificationJustGotBetterModal (Within Dismissal Limit)"
181+
}
182+
}
183+
},
184+
{
185+
"name": "VerificationJustGotBetterModal - Non-Badge Logic (After 7-Day Cooldown): Restarts the modal display loop after a X hours cooldown for those who dismissed the modal Y times.",
186+
"conditions": {
187+
"all": [
188+
{
189+
"fact": "profile",
190+
"path": "$.liveness.hasPendingOrApprovedPhotoVerification",
191+
"operator": "equal",
192+
"value": true
193+
},
194+
{
195+
"fact": "profile",
196+
"path": "$.liveness.isLivenessApproved",
197+
"operator": "equal",
198+
"value": false
199+
},
200+
{
201+
"fact": "profile",
202+
"path": "$.liveness.hasBadge",
203+
"operator": "equal",
204+
"value": false
205+
},
206+
{
207+
"fact": "profile",
208+
"path": "$.liveness.timeInHoursSinceLastModal",
209+
"operator": "greaterThanInclusive",
210+
"value": 168
211+
},
212+
{
213+
"fact": "profile",
214+
"path": "$.liveness.dismissCount",
215+
"operator": "greaterThanInclusive",
216+
"value": 3
217+
}
218+
]
219+
},
220+
"event": {
221+
"type": "VerificationJustGotBetterModal",
222+
"params": {
223+
"value": true,
224+
"message": "Encourage Selfie Modal shown for VerificationJustGotBetterModal (After 7-Day Cooldown)"
225+
}
226+
}
227+
},
228+
{
229+
"name": "AlmostSelfieVerifiedModal (Within Dismissal Limit): Shows the modal every X hours unless dismissed Y times.",
230+
"conditions": {
231+
"all": [
232+
{
233+
"fact": "profile",
234+
"path": "$.liveness.isLivenessApproved",
235+
"operator": "equal",
236+
"value": true
237+
},
238+
{
239+
"fact": "profile",
240+
"path": "$.liveness.matchCompareFacesAttributeCount",
241+
"operator": "equal",
242+
"value": 0
243+
},
244+
{
245+
"any": [
246+
{
247+
"fact": "profile",
248+
"path": "$.liveness.timeInHoursSinceLastModal",
249+
"operator": "greaterThanInclusive",
250+
"value": 12
251+
},
252+
{
253+
"fact": "profile",
254+
"path": "$.liveness.timeInHoursSinceLastModal",
255+
"operator": "equal",
256+
"value": 0
257+
}
258+
]
259+
},
260+
{
261+
"fact": "profile",
262+
"path": "$.liveness.dismissCount",
263+
"operator": "lessThan",
264+
"value": 3
265+
}
266+
]
267+
},
268+
"event": {
269+
"type": "AlmostSelfieVerifiedModal",
270+
"params": {
271+
"value": true,
272+
"message": "Encourage Selfie Modal shown for AlmostSelfieVerifiedModal (Within Dismissal Limit)"
273+
}
274+
}
275+
},
276+
{
277+
"name": "AlmostSelfieVerifiedModal (After 7-Day Cooldown): Restarts the modal display loop after a X hours cooldown for those who dismissed the modal Y times.",
278+
"conditions": {
279+
"all": [
280+
{
281+
"fact": "profile",
282+
"path": "$.liveness.isLivenessApproved",
283+
"operator": "equal",
284+
"value": true
285+
},
286+
{
287+
"fact": "profile",
288+
"path": "$.liveness.matchCompareFacesAttributeCount",
289+
"operator": "equal",
290+
"value": 0
291+
},
292+
{
293+
"fact": "profile",
294+
"path": "$.liveness.timeInHoursSinceLastModal",
295+
"operator": "greaterThanInclusive",
296+
"value": 168
297+
},
298+
{
299+
"fact": "profile",
300+
"path": "$.liveness.dismissCount",
301+
"operator": "greaterThanInclusive",
302+
"value": 3
303+
}
304+
]
305+
},
306+
"event": {
307+
"type": "AlmostSelfieVerifiedModal",
308+
"params": {
309+
"value": true,
310+
"message": "Encourage Selfie Modal shown for AlmostSelfieVerifiedModal (After 7-Day Cooldown)"
311+
}
312+
}
313+
}
314+
]
315+
}

0 commit comments

Comments
 (0)