diff --git a/CLAUDE.md b/CLAUDE.md index 249e395..fb77280 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,9 +14,11 @@ # MANDATORY WORKFLOW 1. Only one TODO.md feature at a time 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 - Build the application for linux and web-server - 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. Dont merge the feature branch into master, the work will be reviewed by a human. +5. If any test or build or web-server launch fails, go to step 3 +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. diff --git a/TODO.md b/TODO.md index 2bba3ad..894a039 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,10 @@ +# Conventions [ ] 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] Make formula description collapsable in FormulaScreen. Initialy, the description is visible, but the user can hide it. - 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] 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. -- [ ] Create Formula.toStringLiteral. It is the reverse of Formula.fromSet( Formula.fromArrayStringLiteral(string)[0] ) -- [ ] Create UnitSpec.toStringLiteral, like Formula.toStringLiteral +- [X] Create Formula.toStringLiteral. It is the reverse of Formula.fromSet( Formula.fromArrayStringLiteral(string)[0] ) +- [X] Create UnitSpec.toStringLiteral, like Formula.toStringLiteral - Database file location: - [ ] 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 diff --git a/lib/formula_models.dart b/lib/formula_models.dart index 42504a0..34b7499 100644 --- a/lib/formula_models.dart +++ b/lib/formula_models.dart @@ -134,6 +134,28 @@ class UnitSpec { 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 { @@ -169,6 +191,30 @@ class VariableSpec { @override 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 { @@ -282,4 +328,28 @@ class Formula { 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(); + } } diff --git a/test/formula_models_test.dart b/test/formula_models_test.dart index 7b25cb4..20e5319 100644 --- a/test/formula_models_test.dart +++ b/test/formula_models_test.dart @@ -99,7 +99,7 @@ void main() { "d4rtCode": ''' F = a * m; ''' - } + } """; final formula = Formula.fromStringLiteral(literal); @@ -113,6 +113,56 @@ void main() { 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; + 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', () { test('evaluates APGAR score formula - Normal case', () async { final corpus = await testCorpus;