can't make valiation on each user interaction
This commit is contained in:
parent
49962b95d6
commit
76769973f3
23 changed files with 198 additions and 82 deletions
3
assets/units/scalar.d4rt.units
Normal file
3
assets/units/scalar.d4rt.units
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
{"name": "scalar", "symbol": "", "isBase": true},
|
||||
]
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import '../formula_models.dart';
|
||||
import '../formula_evaluator.dart';
|
||||
|
|
@ -11,11 +9,7 @@ class FormulaScreen extends StatefulWidget {
|
|||
final Formula formula;
|
||||
final Corpus corpus;
|
||||
|
||||
const FormulaScreen({
|
||||
super.key,
|
||||
required this.formula,
|
||||
required this.corpus,
|
||||
});
|
||||
const FormulaScreen({super.key, required this.formula, required this.corpus});
|
||||
|
||||
@override
|
||||
State<FormulaScreen> createState() => _FormulaScreenState();
|
||||
|
|
@ -24,7 +18,7 @@ class FormulaScreen extends StatefulWidget {
|
|||
//// Start of D4rtEditingController class ////
|
||||
class D4rtEditingController extends TextEditingController {
|
||||
String? _lastError;
|
||||
|
||||
String _text = "";
|
||||
String? get lastError => _lastError;
|
||||
FormulaResult? _lastValue;
|
||||
|
||||
|
|
@ -46,7 +40,6 @@ class D4rtEditingController extends TextEditingController {
|
|||
|
||||
get d4rtValue => _lastValue;
|
||||
|
||||
@override
|
||||
set text(String newText) {
|
||||
super.text = newText;
|
||||
validate();
|
||||
|
|
@ -97,27 +90,37 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
final inputValues = <String, dynamic>{};
|
||||
for (final input in widget.formula.input) {
|
||||
final controller = _inputControllers[input.name]!;
|
||||
if( controller.d4rtValue == null ){
|
||||
throw FormulaEvaluationException( "Field ${input.name} is invalid" );
|
||||
if (controller.d4rtValue == null) {
|
||||
//throw FormulaEvaluationException("Field ${input.name} is invalid");
|
||||
_result = "";
|
||||
return;
|
||||
}
|
||||
final value = controller.d4rtValue.value;
|
||||
|
||||
// Convert input to base unit if needed
|
||||
// Always convert from dropdown unit to variable's base unit
|
||||
late final convertedValue;
|
||||
if( value is Number && input.unit != null ) {
|
||||
convertedValue = widget.corpus.convert(
|
||||
value,
|
||||
_selectedUnits[input.name]!,
|
||||
input.unit as String,
|
||||
);
|
||||
}
|
||||
else{
|
||||
convertedValue = value;
|
||||
late final dynamic convertedValue;
|
||||
|
||||
switch (controller.d4rtValue) {
|
||||
case NumberResult nr:
|
||||
// Convert input to base unit if needed
|
||||
// Always convert from dropdown unit to variable's base unit
|
||||
if (input.unit != null) {
|
||||
convertedValue = widget.corpus.convert(
|
||||
nr.value,
|
||||
_selectedUnits[input.name]!,
|
||||
input.unit as String,
|
||||
);
|
||||
} else {
|
||||
convertedValue = nr.value;
|
||||
}
|
||||
|
||||
case StringResult sr:
|
||||
convertedValue = sr.value;
|
||||
default:
|
||||
throw FormulaEvaluationException(
|
||||
"Field ${input.name} has unsupported type ${controller.d4rtValue!.runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
inputValues[input.name] = convertedValue;
|
||||
|
||||
}
|
||||
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
|
@ -125,14 +128,11 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
|
||||
// Convert output to selected unit if needed
|
||||
String? unit = widget.formula.output.unit;
|
||||
if( unit != null ) {
|
||||
_result = widget.corpus.convert(
|
||||
result,
|
||||
unit,
|
||||
_selectedOutputUnit!,
|
||||
).toStringAsFixed(2);
|
||||
}
|
||||
else{
|
||||
if (unit != null) {
|
||||
_result = widget.corpus
|
||||
.convert(result, unit, _selectedOutputUnit!)
|
||||
.toStringAsFixed(2);
|
||||
} else {
|
||||
_result = result;
|
||||
}
|
||||
|
||||
|
|
@ -155,9 +155,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.formula.name),
|
||||
),
|
||||
appBar: AppBar(title: Text(widget.formula.name)),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
|
|
@ -186,9 +184,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
children: [
|
||||
Text(
|
||||
'Description',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
|
|
@ -213,9 +211,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
children: [
|
||||
Text(
|
||||
'Input Variables',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...widget.formula.input.map((variable) => _buildVariableRow(variable)),
|
||||
|
|
@ -229,9 +227,9 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
children: [
|
||||
Text(
|
||||
'Result',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
|
|
@ -286,6 +284,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
),
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Required';
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ class Corpus{
|
|||
|
||||
List<String> unitsOfSameMagnitude(String? unit){
|
||||
if( unit == null ){
|
||||
return ["unitless"];
|
||||
return ["scalar"];
|
||||
}
|
||||
final base = getUnit(unit).baseUnit;
|
||||
return _baseToUnits[base] as List<String>;
|
||||
|
|
@ -90,7 +90,7 @@ class Corpus{
|
|||
|
||||
String _converterFromCodeStringAsExpression(Number x, String codeString) {
|
||||
final buffer = StringBuffer();
|
||||
buffer.writeln("final x = ${x};");
|
||||
buffer.writeln("final x = $x;");
|
||||
buffer.writeln("main(){return $codeString;}");
|
||||
final code = buffer.toString();
|
||||
return code;
|
||||
|
|
@ -98,7 +98,7 @@ class Corpus{
|
|||
|
||||
String _converterFromCodeStringAsStatement(Number x, String codeString) {
|
||||
final buffer = StringBuffer();
|
||||
buffer.writeln("final x = ${x};");
|
||||
buffer.writeln("final x = $x;");
|
||||
buffer.writeln("main(){ $codeString; return x; }");
|
||||
final code = buffer.toString();
|
||||
return code;
|
||||
|
|
|
|||
|
|
@ -1,42 +1,48 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert' show utf8;
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
import 'package:resource_portable/resource_portable.dart' show Resource;
|
||||
|
||||
import '../corpus.dart';
|
||||
import '../formula_models.dart';
|
||||
|
||||
|
||||
Future<Corpus> createDefaultCorpus() async{
|
||||
final corpus = Corpus();
|
||||
|
||||
Future<String> loadResourceAsString(String path) async {
|
||||
return await rootBundle.loadString(path, cache: false);
|
||||
}
|
||||
|
||||
|
||||
Future<void> loadUnits() async {
|
||||
final unitResources = [
|
||||
"lib/defaults/units/angle.d4rt.units",
|
||||
"lib/defaults/units/area.d4rt.units",
|
||||
"lib/defaults/units/distance.d4rt.units",
|
||||
"lib/defaults/units/energy.d4rt.units",
|
||||
"lib/defaults/units/force.d4rt.units",
|
||||
"lib/defaults/units/mass.d4rt.units",
|
||||
"lib/defaults/units/pressure.d4rt.units",
|
||||
"lib/defaults/units/scalar.d4rt.units",
|
||||
"lib/defaults/units/temperature.d4rt.units",
|
||||
"lib/defaults/units/time.d4rt.units",
|
||||
"lib/defaults/units/velocity.d4rt.units",
|
||||
"assets/units/angle.d4rt.units",
|
||||
"assets/units/area.d4rt.units",
|
||||
"assets/units/distance.d4rt.units",
|
||||
"assets/units/energy.d4rt.units",
|
||||
"assets/units/force.d4rt.units",
|
||||
"assets/units/mass.d4rt.units",
|
||||
"assets/units/pressure.d4rt.units",
|
||||
"assets/units/scalar.d4rt.units",
|
||||
"assets/units/temperature.d4rt.units",
|
||||
"assets/units/time.d4rt.units",
|
||||
"assets/units/velocity.d4rt.units",
|
||||
];
|
||||
|
||||
for (final unitRes in unitResources) {
|
||||
final resource = Resource(unitRes);
|
||||
final literal = await resource.readAsString(encoding: utf8);
|
||||
final literal = await loadResourceAsString(unitRes);
|
||||
final units = UnitSpec.fromArrayStringLiteral(literal);
|
||||
corpus.loadUnits(units);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadFormulas() async {
|
||||
final formulaResources = ["lib/defaults/formulas.d4rt"];
|
||||
final formulaResources = ["assets/formulas/formulas.d4rt"];
|
||||
|
||||
for (final formRes in formulaResources) {
|
||||
final resource = Resource(formRes);
|
||||
final literal = await resource.readAsString(encoding: utf8);
|
||||
final literal = await loadResourceAsString(formRes);
|
||||
final formulas = Formula.fromArrayStringLiteral(literal);
|
||||
corpus.loadFormulas(formulas);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
[
|
||||
{"name": "scalar", "symbol": "", "isBase": true}
|
||||
]
|
||||
|
|
@ -76,14 +76,14 @@ class FormulaEvaluator {
|
|||
final d4rtInterpreter = interpreter ?? createDefaultInterpreter();
|
||||
prepareInterpreter(d4rtInterpreter);
|
||||
final d4rtCode = """
|
||||
${d4rtImports}
|
||||
$d4rtImports
|
||||
main()
|
||||
{
|
||||
late var result;
|
||||
result = $code;
|
||||
return result;
|
||||
}""";
|
||||
//print("evaluateExpression:\n$d4rtCode");
|
||||
print("evaluateExpression:\n$d4rtCode");
|
||||
final result = d4rtInterpreter.execute(source: d4rtCode);
|
||||
switch ( result ){
|
||||
case int value:
|
||||
|
|
|
|||
|
|
@ -102,17 +102,17 @@ class UnitSpec {
|
|||
class VariableSpec {
|
||||
final String name;
|
||||
final String? unit;
|
||||
final List<dynamic>? allowedValues;
|
||||
final List<dynamic>? values;
|
||||
|
||||
VariableSpec({required this.name, this.unit, this.allowedValues}){
|
||||
final valuesValid = allowedValues != null && allowedValues?.isNotEmpty == true;
|
||||
VariableSpec({required this.name, this.unit, this.values}){
|
||||
final valuesValid = values != null && values?.isNotEmpty == true;
|
||||
if( unit == null && !valuesValid ){
|
||||
throw new ArgumentError("$name: at least unit or allowedValues should be valid");
|
||||
throw ArgumentError("$name: at least unit or allowedValues should be valid");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'var($name: $unit${allowedValues != null ? ' allowed: $allowedValues' : ''})';
|
||||
String toString() => 'var($name: $unit${values != null ? ' allowed: $values' : ''})';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
|
|
@ -121,10 +121,10 @@ class VariableSpec {
|
|||
runtimeType == other.runtimeType &&
|
||||
unit == other.unit &&
|
||||
name == other.name &&
|
||||
const DeepCollectionEquality().equals(allowedValues, other.allowedValues);
|
||||
const DeepCollectionEquality().equals(values, other.values);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(unit, name, allowedValues != null ? const DeepCollectionEquality().hash(allowedValues!) : 0);
|
||||
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
|
||||
}
|
||||
|
||||
class Formula {
|
||||
|
|
@ -146,7 +146,7 @@ class Formula {
|
|||
validate();
|
||||
}
|
||||
|
||||
validate() {
|
||||
void validate() {
|
||||
if (name.trim().isEmpty) {
|
||||
throw ArgumentError('Formula name cannot be empty');
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ class Formula {
|
|||
return VariableSpec(
|
||||
name: name,
|
||||
unit: unit,
|
||||
allowedValues: allowed?.toList(growable: false),
|
||||
values: allowed?.toList(growable: false),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'corpus.dart';
|
|||
import 'defaults/default_corpus.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(MaterialApp(
|
||||
home: FutureBuilder<Corpus>(
|
||||
future: createDefaultCorpus(),
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ flutter:
|
|||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
assets:
|
||||
- assets/units/
|
||||
- assets/formulas/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:d4rt/d4rt.dart';
|
|||
import 'dart:math' as Math;
|
||||
|
||||
|
||||
main(){
|
||||
void main(){
|
||||
test('Access to Math', () {
|
||||
|
||||
final completeSource = """
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:d4rt/d4rt.dart';
|
|||
import 'dart:math' as Math;
|
||||
|
||||
|
||||
main(){
|
||||
void main(){
|
||||
test('for dart grammar tests', () {
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -208,5 +208,112 @@ void main() {
|
|||
expect(result, closeTo(9.8596, 0.0001));
|
||||
});
|
||||
});
|
||||
|
||||
group('APGAR Score', () {
|
||||
test('evaluates APGAR score formula - Normal case', () {
|
||||
final formula = Formula(
|
||||
name: "Apgar Score",
|
||||
description: "Newborn health assessment scoring system",
|
||||
input: [
|
||||
VariableSpec(name: "HeartRate", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Breathing", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "MuscleTone", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Reflexes", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "SkinColor", values: ["hr1", "hr2", "hr3"])
|
||||
],
|
||||
output: VariableSpec(name: "Result", unit: "stringscalar"),
|
||||
d4rtCode: """
|
||||
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
|
||||
var interpretation = switch (total) {
|
||||
>= 7 => 'Normal',
|
||||
4-6 => 'Requires attention',
|
||||
_ => 'Emergency care needed'
|
||||
};
|
||||
Result = 'Score: \$total - \$interpretation';
|
||||
""",
|
||||
);
|
||||
|
||||
// Test normal case (score 7-10)
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 2,
|
||||
'Breathing': 2,
|
||||
'MuscleTone': 2,
|
||||
'Reflexes': 2,
|
||||
'SkinColor': 2
|
||||
});
|
||||
|
||||
expect(result, 'Score: 10 - Normal');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Requires attention case', () {
|
||||
final formula = Formula(
|
||||
name: "Apgar Score",
|
||||
description: "Newborn health assessment scoring system",
|
||||
input: [
|
||||
VariableSpec(name: "HeartRate", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Breathing", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "MuscleTone", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Reflexes", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "SkinColor", values: ["hr1", "hr2", "hr3"])
|
||||
],
|
||||
output: VariableSpec(name: "Result", unit: "stringscalar"),
|
||||
d4rtCode: """
|
||||
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
|
||||
var interpretation = switch (total) {
|
||||
>= 7 => 'Normal',
|
||||
4-6 => 'Requires attention',
|
||||
_ => 'Emergency care needed'
|
||||
};
|
||||
Result = 'Score: \$total - \$interpretation';
|
||||
""",
|
||||
);
|
||||
|
||||
// Test requires attention case (score 4-6)
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 1,
|
||||
'Breathing': 1,
|
||||
'MuscleTone': 1,
|
||||
'Reflexes': 1,
|
||||
'SkinColor': 2
|
||||
});
|
||||
|
||||
expect(result, 'Score: 6 - Requires attention');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Emergency case', () {
|
||||
final formula = Formula(
|
||||
name: "Apgar Score",
|
||||
description: "Newborn health assessment scoring system",
|
||||
input: [
|
||||
VariableSpec(name: "HeartRate", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Breathing", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "MuscleTone", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "Reflexes", values: ["hr1", "hr2", "hr3"]),
|
||||
VariableSpec(name: "SkinColor", values: ["hr1", "hr2", "hr3"])
|
||||
],
|
||||
output: VariableSpec(name: "Result", unit: "stringscalar"),
|
||||
d4rtCode: """
|
||||
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
|
||||
var interpretation = switch (total) {
|
||||
>= 7 => 'Normal',
|
||||
4-6 => 'Requires attention',
|
||||
_ => 'Emergency care needed'
|
||||
};
|
||||
Result = 'Score: \$total - \$interpretation';
|
||||
""",
|
||||
);
|
||||
|
||||
// Test emergency case (score 0-3)
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 0,
|
||||
'Breathing': 0,
|
||||
'MuscleTone': 1,
|
||||
'Reflexes': 0,
|
||||
'SkinColor': 1
|
||||
});
|
||||
|
||||
expect(result, 'Score: 2 - Emergency care needed');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue