Skip to content

Commit

Permalink
Add d2 evaluation and parsing date objects
Browse files Browse the repository at this point in the history
  • Loading branch information
BaharaJr committed Jul 23, 2024
1 parent 2ae6aee commit 6215efc
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 174 deletions.
3 changes: 2 additions & 1 deletion lib/modules/engine/program_rule/d2-functions.util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ final Map<String, Function> d2FunctionsEval = {
) {
final date1 = removeQuotes(parameters[0]);
final date2 = removeQuotes(parameters[1]);

final daysBetween = DateUtils.daysBetween(date1, date2);
final newExpression = expression.replaceFirst(regexFunct, '$daysBetween');
return {'expression': newExpression, 'expressionUpdated': true};
Expand All @@ -28,4 +29,4 @@ class D2Function {

String removeQuotes(String input) {
return input.replaceAll(RegExp(r"[']"), "").replaceAll(RegExp(r'["]'), "");
}
}
164 changes: 89 additions & 75 deletions lib/modules/engine/program_rule/run-d2-expression.util.dart
Original file line number Diff line number Diff line change
@@ -1,90 +1,104 @@
import 'package:d2_touch/modules/engine/program_rule/d2-functions.util.dart';

String dhisD2Functions(String expression, Map<String, dynamic> variableHash) {
// 1. Remove whitespace
String evalExpression = expression.replaceAll(RegExp(r'\s+'), '');

if (evalExpression.contains('d2:')) {
bool continueLooping = true;

for (int i = 0; i < 10 && continueLooping; i++) {
bool expressionUpdated = false;
bool brokenExecution = false;

d2FunctionsVariables.forEach((d2FnVar) {
String name = d2FnVar.name;

Iterable<Match> fnRegexCallArr =
getMatches(evalExpression, name).toList();

if (fnRegexCallArr is List<Match> && fnRegexCallArr.isNotEmpty) {
fnRegexCallArr.forEach((fnRegexCall) {
String fnParameters = fnRegexCall
.group(0)!
.replaceAll(RegExp('(^[^\\(]+\\()|\\)\$'), '')
.trim();

List<String?>? parameters = fnParameters.split(',');

if (d2FnVar.parameters != null) {
int numOfParameters = parameters.length;
if (numOfParameters != d2FnVar.parameters) {
brokenExecution = true;
}
}

// if (!brokenExecution ) {
// for (int i = 0; i < parameters.length; i++) {
// parameters[i] = runRuleExpression(
// parameters[i] ?? '',
// d2FnVar.name,
// 'parameter:$i',
// variableHash,
// ) as String?;
// }
// }

if (brokenExecution) {
evalExpression =
evalExpression.replaceFirst(fnRegexCall.group(0)!, 'false');
expressionUpdated = true;
}

Map<String, dynamic> results = d2FunctionsEval[d2FnVar.name]!(
evalExpression,
parameters,
variableHash,
fnRegexCall.group(0)!,
);

evalExpression = results['expression'];
expressionUpdated = results['expressionUpdated'];
});
}
});

if (expressionUpdated && evalExpression.contains('d2:')) {
continueLooping = true;
} else {
continueLooping = false;
}
import 'package:d2_touch/modules/engine/program_rule/utilities/date.utils.dart';

String d2hasValue(String expression) {
RegExp hasValueRegex = RegExp(r'd2:hasValue\(([^)]+)\)');
String replaceHasValue(Match match) {
String content = match.group(1)?.trim() ?? '';
if (content.trim().contains("''")) {
return '1 == 0';
} else {
return '1 == 1';
}
}

return evalExpression.contains("d2:") ? "" : evalExpression;
return expression
.replaceAll('d2:hasValue(' ')', '1 == 0')
.replaceAllMapped(hasValueRegex, (match) => replaceHasValue(match));
}

String d2Length(String expression) {
RegExp hasValueRegex = RegExp(r'd2:length\(([^)]+)\)');
String replaceHasValue(Match match) {
String content = match.group(1)?.trim() ?? '';
if (content.trim().contains("''")) {
return '0';
} else {
return '${content.length}';
}
}

String value = '';

expression.replaceAllMapped(hasValueRegex, (match) {
value = replaceHasValue(match);
return value;
});

return value;
}

String dhisD2Functions(String expression) {
String updatedExpression = expression;

RegExp regex = RegExp(r'd2:(\w+)\((.*?)\)');

String replaceMatch(Match match) {
String d2Argument = match.group(1) ?? '';
String d2Value = match.group(2) ?? '';

switch (d2Argument) {
case 'hasValue':
return d2hasValue(expression);
case 'length':
return d2Length(expression);
case 'daysBetween':
List<String> dates = d2Value.split(',');
return DateUtils.daysBetween(dates[0], dates[1]).toString();
case 'yearsBetween':
List<String> dates = d2Value.split(',');
return DateUtils.yearsBetween(dates[0], dates[1]).toString();
case 'monthsBetween':
List<String> dates = d2Value.split(',');
return DateUtils.monthsBetween(dates[0], dates[1]).toString();
default:
return d2Value;
}
}

// Replace all matches in the expression
updatedExpression =
expression.replaceAllMapped(regex, (match) => replaceMatch(match));

updatedExpression = updatedExpression
.replaceAll(r"!''", '1 == 1')
.replaceAll('d2:length(' ')', '0')
.replaceAll('d2:length( ' ' )', '0');

RegExp notValueRegex = RegExp(r'!([^"]+)', caseSensitive: false);

updatedExpression =
updatedExpression.replaceAllMapped(notValueRegex, (match) {
String content = match.group(1)?.trim() ?? '';
if (content.isNotEmpty) {
return '1 == 0';
} else {
return match.group(0) ?? '';
}
});

return updatedExpression;
}

// d2:daysBetween(2020-01-01, 2020-01-10)
Iterable<Match> getMatches(String inputString, String name) {
RegExp regex = RegExp(name +
r"\(((\d[\d/*+%-.\s]*)|(\'[^\']*\')) *, *((\d[\d/*+%-.\s]*)|(\'[^\']*\'))\)");
return regex.allMatches(inputString);
}

bool runRuleExpression(String expression, String beforeReplacement,
String identifier, Map<String, dynamic> variablesHash) {
bool runRuleExpressions(
String expression, String beforeReplacement, String identifier) {
try {
dynamic canEvalRule = dhisD2Functions(expression, variablesHash);
dynamic canEvalRule = dhisD2Functions(expression);
return canEvalRule;
} catch (e) {
return false;
Expand Down
24 changes: 24 additions & 0 deletions lib/modules/engine/program_rule/tracker_rule_engine.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:d2_touch/modules/data/tracker/entities/enrollment.entity.dart';
import 'package:d2_touch/modules/data/tracker/entities/tracked-entity.entity.dart';
import 'package:d2_touch/modules/data/tracker/entities/tracked_entity_attribute_value.entity.dart';
import 'package:d2_touch/modules/data/tracker/queries/tracked_entity_attribute_value.query.dart';
Expand Down Expand Up @@ -39,12 +40,35 @@ class TrackerRuleEngine {
value: trackedEntityInstance.trackedEntityInstance)
.get();

trackedEntityInstance =
await TrackedEntityInstanceQuery(database: database)
.withEnrollments()
.byId(trackedEntityInstance.trackedEntityInstance ?? '')
.getOne() ??
trackedEntityInstance;

final dataValueEntities =
DataValueEntities.fromAttributeValues(attributes);

List<Enrollment> enrollments = (trackedEntityInstance.enrollments ?? []);

List<ProgramRuleAction> programRuleActions = ProgramRuleEngine.execute(
dataValueEntities: dataValueEntities,
programRules: programRules,
additionalValues: {
'incident_date':
enrollments.isNotEmpty ? enrollments[0].incidentDate : null,
'enrollment_date':
enrollments.isNotEmpty ? enrollments[0].enrollmentDate : null,
'enrollment_id':
enrollments.isNotEmpty ? enrollments[0].enrollment : null,
'current_date': DateTime.now().toIso8601String(),
'event_date': enrollments.isNotEmpty
? (enrollments[0].events ?? []).isNotEmpty
? enrollments[0].events![0].eventDate
: DateTime.now().toIso8601String()
: DateTime.now().toIso8601String(),
},
programRuleVariables: programRuleVariables);

final queue = Queue(parallel: 50);
Expand Down
54 changes: 17 additions & 37 deletions lib/modules/engine/program_rule/utilities/date.utils.dart
Original file line number Diff line number Diff line change
@@ -1,49 +1,29 @@
import 'package:intl/intl.dart';

class DateUtils {
static final DateFormat _dateFormat = DateFormat('yyyy-MM-dd');

static String getToday() {
final todayMoment = DateTime.now();
return _dateFormat.format(todayMoment);
}

static int daysBetween(String firstRulesDate, String secondRulesDate) {
final firstDate = _parseDate(firstRulesDate);
final secondDate = _parseDate(secondRulesDate);
return secondDate.difference(firstDate).inDays;
}

static int weeksBetween(String firstRulesDate, String secondRulesDate) {
final firstDate = _parseDate(firstRulesDate);
final secondDate = _parseDate(secondRulesDate);
return secondDate.difference(firstDate).inDays ~/ 7;
}

static int monthsBetween(String firstRulesDate, String secondRulesDate) {
final firstDate = _parseDate(firstRulesDate);
final secondDate = _parseDate(secondRulesDate);
return _differenceInMonths(secondDate, firstDate);
static Duration difference(String firstRulesDate, String secondRulesDate) {
try {
final firstDate =
DateTime.parse(firstRulesDate.replaceAll("'", "").trim());
final secondDate =
DateTime.parse(secondRulesDate.replaceAll("'", "").trim());
return secondDate.difference(firstDate);
} catch (e) {
return Duration(days: 0, hours: 0, minutes: 0, seconds: 0);
}
}

static int yearsBetween(String firstRulesDate, String secondRulesDate) {
final firstDate = _parseDate(firstRulesDate);
final secondDate = _parseDate(secondRulesDate);
return secondDate.year - firstDate.year;
return (difference(firstRulesDate, secondRulesDate).inDays / 365).round();
}

static String addDays(String rulesDate, String daysToAdd) {
final dateMoment = _parseDate(rulesDate);
final newDateMoment = dateMoment.add(Duration(days: int.parse(daysToAdd)));
final newRulesDate = _dateFormat.format(newDateMoment);
return "'$newRulesDate'";
static weeksBetween(String firstRulesDate, String secondRulesDate) {
return (difference(firstRulesDate, secondRulesDate).inDays / 7).round();
}

static DateTime _parseDate(String date) {
return _dateFormat.parse(date);
static int daysBetween(String firstRulesDate, String secondRulesDate) {
return difference(firstRulesDate, secondRulesDate).inDays;
}

static int _differenceInMonths(DateTime later, DateTime earlier) {
return (later.year - earlier.year) * 12 + later.month - earlier.month;
static int monthsBetween(String firstRulesDate, String secondRulesDate) {
return (difference(firstRulesDate, secondRulesDate).inDays / 30).round();
}
}
Loading

0 comments on commit 6215efc

Please sign in to comment.