Merge branch 'apgar-test'
This commit is contained in:
commit
b053fc3900
31 changed files with 306 additions and 152 deletions
21
Dockerfile
Normal file
21
Dockerfile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Use the official Flutter SDK image
|
||||
FROM ghcr.io/cirruslabs/flutter:stable
|
||||
|
||||
# Install cmake, ninja, clang, pkg-config for flutter linux
|
||||
RUN apt-get update && apt-get install -y cmake ninja-build clang pkg-config libgtk-3-dev liblzma-dev
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Configure cache directories
|
||||
ENV PUB_CACHE=/cache/pub-cache
|
||||
ENV GRADLE_USER_HOME=/cache/gradle-cache
|
||||
RUN mkdir -p $PUB_CACHE $GRADLE_USER_HOME
|
||||
|
||||
# Copy pubspec files and get dependencies
|
||||
# COPY pubspec.yaml pubspec.lock ./
|
||||
# RUN flutter pub get
|
||||
|
||||
# Copy the rest of the application code and build
|
||||
# Commented out to avoid building the app during image creation, this will be handled externally by makefile
|
||||
# COPY . .
|
||||
# RUN flutter build apk --release
|
||||
25
Makefile
Normal file
25
Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
flutter-container-exec = podman-compose run --entrypoint "$(1)" flutter
|
||||
|
||||
all: clean-podman build-linux-debug-podman build-linux-debug-podman
|
||||
|
||||
build-podman:
|
||||
podman-compose build
|
||||
|
||||
clean-podman: build-podman
|
||||
$(call flutter-container-exec, flutter clean)
|
||||
|
||||
pub-get-podman: build-podman
|
||||
$(call flutter-container-exec, flutter pub get)
|
||||
|
||||
build-android-release-podman: pub-get-podman
|
||||
$(call flutter-container-exec, flutter build apk --release)
|
||||
|
||||
build-linux-debug-podman: pub-get-podman
|
||||
$(call flutter-container-exec, flutter build linux --debug)
|
||||
|
||||
run-linux-debug: build-linux-debug-podman
|
||||
build/linux/x64/debug/bundle/d4rt_formulas
|
||||
|
||||
run-web-release-podman: build-web-release-podman
|
||||
cd build/web && python3 -m http.server 8080
|
||||
12
README.md
12
README.md
|
|
@ -1,3 +1,5 @@
|
|||
https://github.com/Shahxad-Akram/flutter_tex/blob/master/example/lib/tex_view_markdown_example.dart
|
||||
|
||||
# Math Formulae Manager
|
||||
|
||||
A comprehensive command-line application for managing and computing mathematical formulas across various disciplines including mathematics, physics, medicine, and engineering.
|
||||
|
|
@ -123,15 +125,11 @@ Each formula includes:
|
|||
- **Images** - Visual diagrams, graphs, or illustrations
|
||||
- **Examples** - Sample calculations and use cases
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `bin/` - Main executable and entry point
|
||||
- `lib/` - Core library code and formula engine
|
||||
- `test/` - Unit tests and formula validation tests
|
||||
|
||||
## Getting Started
|
||||
|
||||
[Installation and usage instructions to be added]
|
||||
This project uses `flutter`, so a valid installation is needed in order to build it.
|
||||
|
||||
For convenience, a containerized build is provided. It is based on `podman` and `podman-compose`. See [Makefile](Makefile) for details.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -120,4 +120,33 @@ Where:
|
|||
"d4rtCode": "F = m * a;",
|
||||
"tags": ["physics", "mechanics", "newton"]
|
||||
},
|
||||
|
||||
// Apgar Score
|
||||
{
|
||||
"name": "Apgar Score",
|
||||
"description": "Newborn health assessment scoring system\n\nScores 0-2 for:\n1. Heart rate\n2. Breathing\n3. Muscle tone\n4. Reflexes\n5. Skin color\nTotal score 0-10",
|
||||
"input": [
|
||||
{"name": "HeartRate", "values": ["Absent", "< 100 bpm>", "> 100 bpm"] },
|
||||
{"name": "Breathing", "values": ["Absent", "Weak, irregular", "Strong, robust cry"] },
|
||||
{"name": "MuscleTone", "values": ["None", "Some", "Flexed arms/leg, resists extension"] },
|
||||
{"name": "Reflexes", "values": ["No response", "Grimace on aggressive stimulation", "Cry on stimulation"] },
|
||||
{"name": "SkinColor", "values": ["Blue or pale", "Blue extremities, pink body", "Pink"] }
|
||||
],
|
||||
"output": {"name": "Result", "unit": "scalar"},
|
||||
"d4rtCode": """
|
||||
var total = HeartRate + Breathing + MuscleTone + Reflexes + SkinColor;
|
||||
late var interpretation;
|
||||
if( total < 4 ) {
|
||||
interpretation = 'Critical condition';
|
||||
}
|
||||
else if( total < 7 ){
|
||||
interpretation = 'Needs assistance';
|
||||
}
|
||||
else {
|
||||
interpretation = 'Normal';
|
||||
}
|
||||
Result = 'Score: \$total - \$interpretation';
|
||||
""",
|
||||
"tags": ["medical", "pediatrics", "assessment"]
|
||||
}
|
||||
]
|
||||
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},
|
||||
]
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
flutter:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: d4rt-formulas-builder
|
||||
volumes:
|
||||
- ./.build-container-cache:/cache:z
|
||||
- .:/app:z # Link the current directory to /app in the container
|
||||
environment:
|
||||
- FLUTTER_FLAVOR=prod # Example environment variable, adjust as needed
|
||||
|
|
@ -37,8 +37,8 @@ void main() {
|
|||
print(' Mass: 10.0 kg');
|
||||
print(' Acceleration: 9.8 m/s²');
|
||||
print(' Calculated Force: $force N');
|
||||
print(' Output variable: ${evaluator.getOutputVariableName(newtonFormula)}');
|
||||
print(' Output magnitude: ${evaluator.getOutputVariableMagnitude(newtonFormula)}');
|
||||
print(' Output variable: ${newtonFormula.output.name}');
|
||||
print(' Output magnitude: ${newtonFormula.output.unit}');
|
||||
} catch (e) {
|
||||
print(' Error: $e');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
# launch as: firejail --profile=firejail-warp-terminal.profile /opt/warpdotdev/warp-terminal/warp
|
||||
private /home/alvaro/repos/d4rt_formulas/
|
||||
blacklist /datos-1T
|
||||
blacklist /datos-luks/
|
||||
blacklist /media
|
||||
|
||||
# net none # disable network
|
||||
# noroot # don't run as root
|
||||
caps.drop all # drop Linux capabilities
|
||||
# seccomp # enable syscall filtering
|
||||
First version of a formula widget
|
||||
|
|
@ -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,24 +9,22 @@ 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();
|
||||
}
|
||||
|
||||
// TODO: Create VariableWidget. Depending on VariableSpec.values, it can be a ValueDropdown or a D4rtEditingController
|
||||
// The d4rtValue will be FormulaResult?
|
||||
|
||||
//// Start of D4rtEditingController class ////
|
||||
class D4rtEditingController extends TextEditingController {
|
||||
String? _lastError;
|
||||
|
||||
String? get lastError => _lastError;
|
||||
FormulaResult? _lastValue;
|
||||
|
||||
D4rtEditingController({String? text}) : super(text: text);
|
||||
D4rtEditingController({super.text});
|
||||
|
||||
bool validate() {
|
||||
try {
|
||||
|
|
@ -44,9 +40,8 @@ class D4rtEditingController extends TextEditingController {
|
|||
}
|
||||
}
|
||||
|
||||
get d4rtValue => _lastValue;
|
||||
FormulaResult? get d4rtValue => _lastValue;
|
||||
|
||||
@override
|
||||
set text(String newText) {
|
||||
super.text = newText;
|
||||
validate();
|
||||
|
|
@ -97,38 +92,51 @@ 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 ) {
|
||||
convertedValue = widget.corpus.convert(
|
||||
value,
|
||||
_selectedUnits[input.name]!,
|
||||
input.unit,
|
||||
);
|
||||
}
|
||||
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();
|
||||
final result = evaluator.evaluate(widget.formula, inputValues);
|
||||
|
||||
// Convert output to selected unit if needed
|
||||
_result = widget.corpus.convert(
|
||||
result,
|
||||
widget.formula.output.unit,
|
||||
_selectedOutputUnit!,
|
||||
).toStringAsFixed(2);
|
||||
String? unit = widget.formula.output.unit;
|
||||
if (unit != null) {
|
||||
_result = widget.corpus
|
||||
.convert(result, unit, _selectedOutputUnit!)
|
||||
.toStringAsFixed(2);
|
||||
} else {
|
||||
_result = result;
|
||||
}
|
||||
|
||||
//print( "_evaluateFormula: result:${result} _result:${_result}");
|
||||
|
||||
|
|
@ -149,9 +157,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(
|
||||
|
|
@ -180,9 +186,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(
|
||||
|
|
@ -207,9 +213,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)),
|
||||
|
|
@ -223,9 +229,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(
|
||||
|
|
@ -280,6 +286,7 @@ class _FormulaScreenState extends State<FormulaScreen> {
|
|||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
),
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Required';
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class Corpus{
|
|||
|
||||
if( checkUnits ){
|
||||
for( final inputVar in formula.input + [formula.output] ){
|
||||
if( !_allUnits.containsKey(inputVar.unit) ){
|
||||
if( inputVar.unit != null && !_allUnits.containsKey(inputVar.unit) ){
|
||||
throw ArgumentError( "Unit not found: ${inputVar.unit}");
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,10 @@ class Corpus{
|
|||
return _allFormulas.values.toList(growable:false);
|
||||
}
|
||||
|
||||
Formula? getFormula(String name) {
|
||||
return _allFormulas.get(name);
|
||||
}
|
||||
|
||||
final Multimap<String, String> _baseToUnits = Multimap.create();
|
||||
final Map<String, UnitSpec> _allUnits = {};
|
||||
|
||||
|
|
@ -71,7 +75,10 @@ class Corpus{
|
|||
}
|
||||
}
|
||||
|
||||
List<String> unitsOfSameMagnitude(String unit){
|
||||
List<String> unitsOfSameMagnitude(String? unit){
|
||||
if( unit == null ){
|
||||
return ["scalar"];
|
||||
}
|
||||
final base = getUnit(unit).baseUnit;
|
||||
return _baseToUnits[base] as List<String>;
|
||||
}
|
||||
|
|
@ -87,7 +94,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;
|
||||
|
|
@ -95,7 +102,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;
|
||||
|
|
@ -113,7 +120,7 @@ class Corpus{
|
|||
}
|
||||
|
||||
final ret = _convertUsingCode(x, unit.codeFromUnitToBase as String);
|
||||
return ret as Number;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Number _convertFromBase(Number x, String toUnit) {
|
||||
|
|
@ -128,7 +135,7 @@ class Corpus{
|
|||
}
|
||||
|
||||
final ret = _convertUsingCode(x, unit.codeFromBaseToUnit as String);
|
||||
return ret as Number;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -140,17 +147,19 @@ class Corpus{
|
|||
final ret = _evaluate(completeSourceExpression);
|
||||
return ret;
|
||||
}
|
||||
catch(e1,stack){
|
||||
catch(e1, stack1){
|
||||
try{
|
||||
completeSourceStatement = _converterFromCodeStringAsStatement(x, code);
|
||||
final ret = _evaluate(completeSourceStatement);
|
||||
return ret;
|
||||
}
|
||||
catch( e2, stack ){
|
||||
catch( e2, stack2 ){
|
||||
print(completeSourceExpression);
|
||||
print(e1);
|
||||
print(stack1);
|
||||
print(completeSourceStatement);
|
||||
print(e2);
|
||||
print(stack2);
|
||||
throw FormulaEvaluationException( "Evaluation as statement and expression failed" );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +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/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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -100,8 +100,17 @@ class FormulaEvaluator {
|
|||
dynamic evaluate(Formula formula, Map<String, dynamic> inputValues) {
|
||||
_validateInputValues(formula, inputValues);
|
||||
final completeSource = _buildCompleteSource(formula, inputValues);
|
||||
final result = _interpreter.execute(source: completeSource);
|
||||
return result;
|
||||
try {
|
||||
final result = _interpreter.execute(source: completeSource);
|
||||
return result;
|
||||
}
|
||||
catch (e) {
|
||||
print( "Error evaluating formula source:\n$completeSource" );
|
||||
throw FormulaEvaluationException(
|
||||
'Error evaluating formula "${formula.name}": $e',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) {
|
||||
|
|
@ -121,13 +130,7 @@ class FormulaEvaluator {
|
|||
}
|
||||
}
|
||||
|
||||
String getOutputVariableName(Formula formula) {
|
||||
return formula.output.name;
|
||||
}
|
||||
|
||||
String getOutputVariableMagnitude(Formula formula) {
|
||||
return formula.output.unit;
|
||||
}
|
||||
|
||||
List<String> getInputVariableOrder(Formula formula) {
|
||||
return formula.inputVarNames()..sort();
|
||||
|
|
@ -166,9 +169,9 @@ class FormulaEvaluator {
|
|||
}
|
||||
}
|
||||
buffer.writeln("""
|
||||
late var ${getOutputVariableName(formula)};
|
||||
late var ${formula.output.name};
|
||||
${formula.d4rtCode}
|
||||
return ${getOutputVariableName(formula)};
|
||||
return ${formula.output.name};
|
||||
}
|
||||
"""
|
||||
);
|
||||
|
|
|
|||
|
|
@ -101,12 +101,18 @@ class UnitSpec {
|
|||
|
||||
class VariableSpec {
|
||||
final String name;
|
||||
final String unit;
|
||||
final String? unit;
|
||||
final List<dynamic>? values;
|
||||
|
||||
VariableSpec({required this.name, required this.unit});
|
||||
VariableSpec({required this.name, this.unit, this.values}){
|
||||
final valuesValid = values != null && values?.isNotEmpty == true;
|
||||
if( unit == null && !valuesValid ){
|
||||
throw ArgumentError("$name: at least unit or allowedValues should be valid");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'var($name: $unit)';
|
||||
String toString() => 'var($name: $unit${values != null ? ' allowed: $values' : ''})';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
|
|
@ -114,10 +120,11 @@ class VariableSpec {
|
|||
other is VariableSpec &&
|
||||
runtimeType == other.runtimeType &&
|
||||
unit == other.unit &&
|
||||
name == other.name;
|
||||
name == other.name &&
|
||||
const DeepCollectionEquality().equals(values, other.values);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(unit, name);
|
||||
int get hashCode => Object.hash(unit, name, values != null ? const DeepCollectionEquality().hash(values!) : 0);
|
||||
}
|
||||
|
||||
class Formula {
|
||||
|
|
@ -139,7 +146,7 @@ class Formula {
|
|||
validate();
|
||||
}
|
||||
|
||||
validate() {
|
||||
void validate() {
|
||||
if (name.trim().isEmpty) {
|
||||
throw ArgumentError('Formula name cannot be empty');
|
||||
}
|
||||
|
|
@ -196,8 +203,25 @@ class Formula {
|
|||
factory Formula.fromSet(Map<Object?, Object?> theSet) {
|
||||
VariableSpec parseVar(Map<Object?, Object?> varSpec) {
|
||||
String name = SetUtils.stringValue(varSpec, "name");
|
||||
String unit = SetUtils.stringValue(varSpec, "unit");
|
||||
return VariableSpec(name: name, unit: unit);
|
||||
String? unit;
|
||||
if (varSpec.containsKey("unit")) {
|
||||
unit = SetUtils.stringValue(varSpec, "unit");
|
||||
}
|
||||
final allowed = varSpec['values'] as List<dynamic>?;
|
||||
if (allowed != null) {
|
||||
final types = allowed.map((v) => v.runtimeType).toSet();
|
||||
if (types.length > 1) {
|
||||
throw ArgumentError('Allowed values must be all Strings or all Numbers');
|
||||
}
|
||||
if (!types.contains(String) && !types.contains(double) && !types.contains(int)) {
|
||||
throw ArgumentError('Allowed values must be Strings or Numbers');
|
||||
}
|
||||
}
|
||||
return VariableSpec(
|
||||
name: name,
|
||||
unit: unit,
|
||||
values: allowed?.toList(growable: false),
|
||||
);
|
||||
}
|
||||
|
||||
String name = SetUtils.stringValue(theSet, "name");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'corpus.dart';
|
|||
import 'defaults/default_corpus.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(MaterialApp(
|
||||
home: FutureBuilder<Corpus>(
|
||||
future: createDefaultCorpus(),
|
||||
|
|
|
|||
52
pubspec.lock
52
pubspec.lock
|
|
@ -109,10 +109,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
@ -125,18 +125,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: d4rt
|
||||
sha256: "1eb626145e2ed97332f9e6e842e626f973d3969ce30e2794efb4744bd8aeba63"
|
||||
sha256: eff6a10f31e9e5b60b99146a33204c5f2d74e20ac3eeb14132d8a8ed0921c6e1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.7"
|
||||
version: "0.1.9"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
||||
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
version: "2.0.8"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -244,10 +244,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
version: "1.6.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -356,10 +356,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -585,34 +585,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9"
|
||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.24"
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
version: "6.3.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -625,18 +625,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
||||
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.1.5"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -657,10 +657,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -702,5 +702,5 @@ packages:
|
|||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
dart: ">=3.10.7 <4.0.0"
|
||||
flutter: ">=3.38.0"
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
sdk: ^3.10.7
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
|
|
@ -50,8 +50,8 @@ dev_dependencies:
|
|||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
test: ^1.26.3
|
||||
flutter_lints:
|
||||
test:
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
@ -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 = """
|
||||
|
|
@ -29,6 +29,7 @@ main(){
|
|||
}
|
||||
""";
|
||||
final interpreter = D4rt();
|
||||
interpreter.grant(FilesystemPermission.readPath("/etc/passwd"));
|
||||
final result = interpreter.execute(source: completeSource);
|
||||
|
||||
expect(result, contains("root"));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:d4rt/d4rt.dart';
|
|||
import 'dart:math' as Math;
|
||||
|
||||
|
||||
main(){
|
||||
void main(){
|
||||
test('for dart grammar tests', () {
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ void main() {
|
|||
d4rtCode: 'force = x;',
|
||||
);
|
||||
|
||||
expect(evaluator.getOutputVariableName(formula), 'force');
|
||||
expect(formula.output.name, 'force');
|
||||
});
|
||||
|
||||
test(
|
||||
|
|
@ -165,7 +165,7 @@ void main() {
|
|||
d4rtCode: 'force = x;',
|
||||
);
|
||||
|
||||
expect(evaluator.getOutputVariableMagnitude(formula), 'Newton');
|
||||
expect(formula.output.unit, 'Newton');
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -177,8 +177,8 @@ void main() {
|
|||
d4rtCode: 'result = x;',
|
||||
);
|
||||
|
||||
expect(evaluator.getOutputVariableName(validFormula), 'result');
|
||||
expect(evaluator.getOutputVariableMagnitude(validFormula), 'Newton');
|
||||
expect(validFormula.output.name, 'result');
|
||||
expect(validFormula.output.unit, 'Newton');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
import 'package:d4rt_formulas/corpus.dart';
|
||||
import 'package:d4rt_formulas/defaults/default_corpus.dart';
|
||||
import 'package:d4rt_formulas/formula_evaluator.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:d4rt_formulas/formula_models.dart';
|
||||
|
||||
|
||||
void main() {
|
||||
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
Future<Corpus> createTestCorpus() async {
|
||||
return createDefaultCorpus();
|
||||
}
|
||||
|
||||
Future<Corpus> testCorpus = createTestCorpus();
|
||||
|
||||
|
||||
test("Parses unit", () {
|
||||
final setLiteral = {"name": "kilometer", "symbol": "km", "baseUnit": "meter", "factor": 1000};
|
||||
final unit = UnitSpec.fromSet(setLiteral);
|
||||
|
|
@ -23,37 +28,37 @@ void main() {
|
|||
});
|
||||
|
||||
test("From km to in", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final inches = corpus.convert(1, "kilometer", "inch");
|
||||
expect( inches, closeTo(39370.078,0.001) );
|
||||
});
|
||||
|
||||
test("From furlong to base", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final m = corpus.convert(1, "furlong", "meter");
|
||||
expect(m,closeTo(201.168,0.001));
|
||||
});
|
||||
|
||||
test("From base to furlong", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final m = corpus.convert(201.168, "meter", "furlong");
|
||||
expect(m,closeTo(1,0.001));
|
||||
});
|
||||
|
||||
test("From C to F", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final m = corpus.convert(37, "Celsius", "Fahrenheit");
|
||||
expect(m,closeTo(98.6,0.001));
|
||||
});
|
||||
|
||||
test("From K to F", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final m = corpus.convert(37, "Kelvin", "Fahrenheit");
|
||||
expect(m,closeTo(-393.07,0.001));
|
||||
});
|
||||
|
||||
test("From C to K", () async {
|
||||
final corpus = await createTestCorpus();
|
||||
final corpus = await testCorpus;
|
||||
final m = corpus.convert(100, "Celsius", "Kelvin");
|
||||
expect(m,closeTo(373.15,0.001));
|
||||
});
|
||||
|
|
@ -108,5 +113,22 @@ void main() {
|
|||
expect(result, 98.0); // F = m * a = 10 * 9.8 = 98 N
|
||||
});
|
||||
|
||||
group('APGAR Score', () {
|
||||
test('evaluates APGAR score formula - Normal case', () async {
|
||||
final corpus = await testCorpus;
|
||||
final formula = corpus.getFormula("Apgar Score")!;
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 2,
|
||||
'Breathing': 2,
|
||||
'MuscleTone': 2,
|
||||
'Reflexes': 2,
|
||||
'SkinColor': 2
|
||||
});
|
||||
|
||||
expect(result, 'Score: 10 - Normal');
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
firejail --profile=firejail-warp-terminal.profile warp-terminal
|
||||
Loading…
Reference in a new issue