formula element toStringLiteral

This commit is contained in:
Your Name 2026-02-11 08:56:43 +01:00
parent 034f87945f
commit 6753fd99ea
4 changed files with 133 additions and 7 deletions

View file

@ -14,9 +14,11 @@
# MANDATORY WORKFLOW # MANDATORY WORKFLOW
1. Only one TODO.md feature at a time 1. Only one TODO.md feature at a time
2. Create a git branch for each new feature 2. Create a git branch for each new feature
3. After making any change 3. Implement then new feature, and create tests for the new feature
4. After making any change
- Allways pass all the tests and integration tests - Allways pass all the tests and integration tests
- Build the application for linux and web-server - Build the application for linux and web-server
- Launch the apllication for web-server, with a timeout of 60s - Launch the apllication for web-server, with a timeout of 60s
4. If any test or build or web-server launch fails, go to step 3 5. If any test or build or web-server launch fails, go to step 3
5. Dont merge the feature branch into master, the work will be reviewed by a human. 6. Change TODO.md. Mark the task with [R], than means than the work will be reviewed by a human.
7. Dont merge the feature branch into master, the work will be reviewed by a human.

10
TODO.md
View file

@ -1,6 +1,10 @@
# Conventions
[ ] Means not done [ ] Means not done
[x] Means done [R] Means done, but needs a human review
[X] Means done
# List of tasks
- [X] Unify error reporting. Create class ErrorHandler that get notified of every catched exception. This class prints the exception in stdout. - [X] Unify error reporting. Create class ErrorHandler that get notified of every catched exception. This class prints the exception in stdout.
- [X] Make formula description collapsable in FormulaScreen. Initialy, the description is visible, but the user can hide it. - [X] Make formula description collapsable in FormulaScreen. Initialy, the description is visible, but the user can hide it.
- Refactor formula and unit loading: - Refactor formula and unit loading:
@ -11,8 +15,8 @@
- [X] Change createDefaultCorpus to use loadFormulaElements instead of loadUnits and loadFormula. Make loadUnits and loadFormula private. - [X] Change createDefaultCorpus to use loadFormulaElements instead of loadUnits and loadFormula. Make loadUnits and loadFormula private.
- [X] Use a single table in database `FORMULAELEMENT` to store formulas and units. The table contains only two columns: autonumeric id and text. - [X] Use a single table in database `FORMULAELEMENT` to store formulas and units. The table contains only two columns: autonumeric id and text.
- Drift files have a lot of duplicate code. "web" version is the same as native version, only _openConnection is diferrent. Refactor to not duplicate code. - Drift files have a lot of duplicate code. "web" version is the same as native version, only _openConnection is diferrent. Refactor to not duplicate code.
- [ ] Create Formula.toStringLiteral. It is the reverse of Formula.fromSet( Formula.fromArrayStringLiteral(string)[0] ) - [X] Create Formula.toStringLiteral. It is the reverse of Formula.fromSet( Formula.fromArrayStringLiteral(string)[0] )
- [ ] Create UnitSpec.toStringLiteral, like Formula.toStringLiteral - [X] Create UnitSpec.toStringLiteral, like Formula.toStringLiteral
- Database file location: - Database file location:
- [ ] In linux, the sqlite database file will be located following rules at https://specifications.freedesktop.org/basedir/latest/ - [ ] In linux, the sqlite database file will be located following rules at https://specifications.freedesktop.org/basedir/latest/
- [ ] In Windows, the sqlite database file will be in %appdata%/Roaming - [ ] In Windows, the sqlite database file will be in %appdata%/Roaming

View file

@ -134,6 +134,28 @@ class UnitSpec {
return units.toList(growable: false); return units.toList(growable: false);
} }
/// Creates a string literal representation of the UnitSpec that can be parsed
/// by the D4RT parser to recreate the same UnitSpec object.
String toStringLiteral() {
final buffer = StringBuffer('{');
buffer.write('"name": "$name", "symbol": "$symbol"');
if (name == baseUnit && factorFromUnitToBase == 1) {
// This is a base unit
buffer.write(', "isBase": true');
} else {
buffer.write(', "baseUnit": "$baseUnit"');
if (factorFromUnitToBase != null) {
buffer.write(', "factor": $factorFromUnitToBase');
} else if (codeFromUnitToBase != null && codeFromBaseToUnit != null) {
buffer.write(', "toBase": "$codeFromUnitToBase", "fromBase": "$codeFromBaseToUnit"');
}
}
buffer.write('}');
return buffer.toString();
}
} }
class VariableSpec { class VariableSpec {
@ -169,6 +191,30 @@ class VariableSpec {
@override @override
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0); int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
/// Creates a string literal representation of the VariableSpec that can be parsed
/// by the D4RT parser.
String toStringLiteral() {
final buffer = StringBuffer('{');
buffer.write('"name": "$name"');
if (unit != null) {
buffer.write(', "unit": "$unit"');
}
if (values != null && values!.isNotEmpty) {
buffer.write(', "values": [${values!.map((value) {
if (value is String) {
return '"$value"';
} else {
return value.toString();
}
}).join(", ")}]');
}
buffer.write('}');
return buffer.toString();
}
} }
class Formula { class Formula {
@ -282,4 +328,28 @@ class Formula {
d4rtCode: d4rtCode, d4rtCode: d4rtCode,
); );
} }
/// Creates a string literal representation of the Formula that can be parsed
/// by the D4RT parser to recreate the same Formula object.
String toStringLiteral() {
final inputStrings = input.map((varSpec) => varSpec.toStringLiteral()).toList();
final buffer = StringBuffer('{');
buffer.write('"name": "$name"');
if (description != null) {
buffer.write(', "description": "$description"');
}
buffer.write(', "input": [${inputStrings.join(", ")}]');
buffer.write(', "output": ${output.toStringLiteral()}');
buffer.write(', "d4rtCode": ${d4rtCode.contains('\n') || d4rtCode.contains('"') ? 'r"""$d4rtCode"""' : '"$d4rtCode"'}');
if (tags.isNotEmpty) {
buffer.write(', "tags": [${tags.map((tag) => '"$tag"').join(", ")}]');
}
buffer.write('}');
return buffer.toString();
}
} }

View file

@ -99,7 +99,7 @@ void main() {
"d4rtCode": ''' "d4rtCode": '''
F = a * m; F = a * m;
''' '''
} }
"""; """;
final formula = Formula.fromStringLiteral(literal); final formula = Formula.fromStringLiteral(literal);
@ -113,6 +113,56 @@ void main() {
expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N
}); });
test('Formula.toStringLiteral creates reversible string', () {
final originalFormula = Formula(
name: "Test Formula",
description: "A test formula for toStringLiteral",
input: [
VariableSpec(name: 'x', unit: 'meter'),
VariableSpec(name: 'y', unit: 'second', values: ['1', '2', '3']) // Using strings to match D4RT parsing behavior
],
output: VariableSpec(name: 'result', unit: 'meter_per_second'),
d4rtCode: 'result = x / y;',
tags: ['test', 'simple'],
);
final literal = originalFormula.toStringLiteral();
final parsedFormula = Formula.fromStringLiteral(literal);
expect(parsedFormula.name, originalFormula.name);
expect(parsedFormula.description, originalFormula.description);
expect(parsedFormula.input.length, originalFormula.input.length);
expect(parsedFormula.output, originalFormula.output);
expect(parsedFormula.d4rtCode, originalFormula.d4rtCode);
expect(parsedFormula.tags, originalFormula.tags);
// Check inputs individually
for (int i = 0; i < originalFormula.input.length; i++) {
expect(parsedFormula.input[i].name, originalFormula.input[i].name);
expect(parsedFormula.input[i].unit, originalFormula.input[i].unit);
expect(parsedFormula.input[i].values, originalFormula.input[i].values);
}
});
test('UnitSpec.toStringLiteral creates reversible string', () {
final originalUnit = UnitSpec(
name: "test_unit",
baseUnit: "base_unit",
symbol: "tu",
factorFromUnitToBase: 10.0,
);
final literal = originalUnit.toStringLiteral();
final parsedList = parseD4rtLiteral('[${literal}]');
final parsedMap = parsedList[0] as Map<Object?, Object?>;
final parsedUnit = UnitSpec.fromSet(parsedMap);
expect(parsedUnit.name, originalUnit.name);
expect(parsedUnit.baseUnit, originalUnit.baseUnit);
expect(parsedUnit.symbol, originalUnit.symbol);
expect(parsedUnit.factorFromUnitToBase, originalUnit.factorFromUnitToBase);
});
group('APGAR Score', () { group('APGAR Score', () {
test('evaluates APGAR score formula - Normal case', () async { test('evaluates APGAR score formula - Normal case', () async {
final corpus = await testCorpus; final corpus = await testCorpus;