Used qwen to validate allowed values
This commit is contained in:
parent
6f352a945c
commit
ffe1326629
8 changed files with 119 additions and 399 deletions
163
CLAUDE.md
163
CLAUDE.md
|
|
@ -1,154 +1,13 @@
|
|||
# Development Guidelines
|
||||
# Tools available
|
||||
- This is a flutter app.
|
||||
- Flutter and Dart are not installed locally. They are executed via a flutter wrapper script, like gradlew (gradle wrapper) for gradle.
|
||||
- Flutter and Dart are installed in a podman container. This container is invoked via `./flutterw` bash script.
|
||||
- `./flutterw --build-container` to build the image and the container with flutter tools
|
||||
- `./flutterw --exec <args>` to execute bash commands inside the container
|
||||
- `./flutterw <args>` is a shorthand for `./flutter --exec flutter <args>`, so `./flutterw` and `flutter` are somewhat equivalent.
|
||||
- Examples:
|
||||
- `flutter pub get` --> `./flutterw pub get`
|
||||
- `flutter run -d linux` --> `./flutterw run -d linux`
|
||||
- See `./Makefile` for more use examples.
|
||||
|
||||
## Philosophy
|
||||
|
||||
### Core Beliefs
|
||||
|
||||
- **Incremental progress over big bangs** - Small changes that compile and pass tests
|
||||
- **Learning from existing code** - Study and plan before implementing
|
||||
- **Pragmatic over dogmatic** - Adapt to project reality
|
||||
- **Clear intent over clever code** - Be boring and obvious
|
||||
|
||||
### Simplicity Means
|
||||
|
||||
- Single responsibility per function/class
|
||||
- Avoid premature abstractions
|
||||
- No clever tricks - choose the boring solution
|
||||
- If you need to explain it, it's too complex
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Planning & Staging
|
||||
|
||||
Break complex work into 3-5 stages. Document in `IMPLEMENTATION_PLAN.md`:
|
||||
|
||||
```markdown
|
||||
## Stage N: [Name]
|
||||
**Goal**: [Specific deliverable]
|
||||
**Success Criteria**: [Testable outcomes]
|
||||
**Tests**: [Specific test cases]
|
||||
**Status**: [Not Started|In Progress|Complete]
|
||||
```
|
||||
- Update status as you progress
|
||||
- Remove file when all stages are done
|
||||
|
||||
### 2. Implementation Flow
|
||||
|
||||
1. **Understand** - Study existing patterns in codebase
|
||||
2. **Branch** - Create a new branch from current branch, asing a memorable name to the new branch
|
||||
3. **Test** - Write test first (red)
|
||||
4. **Implement** - Minimal code to pass (green)
|
||||
5. **Refactor** - Clean up with tests passing
|
||||
6. **Commit** - With clear message linking to plan
|
||||
|
||||
### 3. When Stuck (After 3 Attempts)
|
||||
|
||||
**CRITICAL**: Maximum 3 attempts per issue, then STOP.
|
||||
|
||||
1. **Document what failed**:
|
||||
- What you tried
|
||||
- Specific error messages
|
||||
- Why you think it failed
|
||||
|
||||
2. **Research alternatives**:
|
||||
- Find 2-3 similar implementations
|
||||
- Note different approaches used
|
||||
|
||||
3. **Question fundamentals**:
|
||||
- Is this the right abstraction level?
|
||||
- Can this be split into smaller problems?
|
||||
- Is there a simpler approach entirely?
|
||||
|
||||
4. **Try different angle**:
|
||||
- Different library/framework feature?
|
||||
- Different architectural pattern?
|
||||
- Remove abstraction instead of adding?
|
||||
|
||||
## Technical Standards
|
||||
|
||||
### Architecture Principles
|
||||
|
||||
- **Composition over inheritance** - Use dependency injection
|
||||
- **Interfaces over singletons** - Enable testing and flexibility
|
||||
- **Explicit over implicit** - Clear data flow and dependencies
|
||||
- **Test-driven when possible** - Never disable tests, fix them
|
||||
|
||||
### Code Quality
|
||||
|
||||
- **Every commit must**:
|
||||
- Compile successfully
|
||||
- Pass all existing tests
|
||||
- Include tests for new functionality
|
||||
- Follow project formatting/linting
|
||||
|
||||
- **Before committing**:
|
||||
- Run formatters/linters
|
||||
- Self-review changes
|
||||
- Ensure commit message explains "why"
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Fail fast with descriptive messages
|
||||
- Include context for debugging
|
||||
- Handle errors at appropriate level
|
||||
- Never silently swallow exceptions
|
||||
|
||||
## Decision Framework
|
||||
|
||||
When multiple valid approaches exist, choose based on:
|
||||
|
||||
1. **Testability** - Can I easily test this?
|
||||
2. **Readability** - Will someone understand this in 6 months?
|
||||
3. **Consistency** - Does this match project patterns?
|
||||
4. **Simplicity** - Is this the simplest solution that works?
|
||||
5. **Reversibility** - How hard to change later?
|
||||
|
||||
## Project Integration
|
||||
|
||||
### Learning the Codebase
|
||||
|
||||
- Find 3 similar features/components
|
||||
- Identify common patterns and conventions
|
||||
- Use same libraries/utilities when possible
|
||||
- Follow existing test patterns
|
||||
|
||||
### Tooling
|
||||
|
||||
- Use project's existing build system
|
||||
- Use project's test framework
|
||||
- Use project's formatter/linter settings
|
||||
- Don't introduce new tools without strong justification
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Definition of Done
|
||||
|
||||
- [ ] Tests written and passing
|
||||
- [ ] Code follows project conventions
|
||||
- [ ] No linter/formatter warnings
|
||||
- [ ] Commit messages are clear
|
||||
- [ ] Implementation matches plan
|
||||
- [ ] No TODOs without issue numbers
|
||||
|
||||
### Test Guidelines
|
||||
|
||||
- Test behavior, not implementation
|
||||
- One assertion per test when possible
|
||||
- Clear test names describing scenario
|
||||
- Use existing test utilities/helpers
|
||||
- Tests should be deterministic
|
||||
|
||||
## Important Reminders
|
||||
|
||||
**NEVER**:
|
||||
- Use `--no-verify` to bypass commit hooks
|
||||
- Disable tests instead of fixing them
|
||||
- *Commit code yourself*
|
||||
- Make assumptions - verify with existing code
|
||||
|
||||
**ALWAYS**:
|
||||
- Commit working code incrementally
|
||||
- Update plan documentation as you go
|
||||
- Learn from existing implementations
|
||||
- Stop after 3 failed attempts and reassess
|
||||
- *Let the user commit changes*
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -2,7 +2,7 @@
|
|||
all: clean-container build-linux-debug-container
|
||||
|
||||
build-container:
|
||||
./flutterw --build
|
||||
./flutterw --build-container
|
||||
|
||||
clean-container: build-container
|
||||
./flutterw clean
|
||||
|
|
|
|||
126
README.md
126
README.md
|
|
@ -2,135 +2,9 @@ https://github.com/Shahxad-Akram/flutter_tex/blob/master/example/lib/tex_view_ma
|
|||
|
||||
# Math Formulae Manager
|
||||
|
||||
A comprehensive command-line application for managing and computing mathematical formulas across various disciplines including mathematics, physics, medicine, and engineering.
|
||||
|
||||
This project uses dart language, and flutter framework. It leverages d4rt library to execute formulas.
|
||||
|
||||
|
||||
# Development guidelines
|
||||
If you are a contributor or an agent, please follow [CLAUDE.md](./CLAUDE.md) for development guidelines.
|
||||
|
||||
# Formula file description
|
||||
|
||||
The file is a dart array of formulas. Each formula is a dart set literal
|
||||
|
||||
```dart
|
||||
[
|
||||
{
|
||||
"name": "Newton's second law (scalar)",
|
||||
"input": {
|
||||
"m" : {
|
||||
"magnitude": "mass"
|
||||
},
|
||||
"a" : {
|
||||
"magnitude": "acceleration"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"F" : {
|
||||
"magnitude" : "Force"
|
||||
}
|
||||
},
|
||||
"d4rt_code": "F = m*a;"
|
||||
|
||||
},
|
||||
{
|
||||
"name": 'Triangle rectangle',
|
||||
"input": [
|
||||
'b':{ "magnitude": 'meter'},
|
||||
'c':{ "magnitude": 'meter'},
|
||||
],
|
||||
"output": { 'a': { "magnitude": 'meter' } },
|
||||
"d4rtCode": '''
|
||||
a = Math.sqrt(b*b + c*c);
|
||||
''',
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
# Unit file description
|
||||
|
||||
```dart
|
||||
[
|
||||
{
|
||||
"name": 'meter',
|
||||
"symbol": 'm',
|
||||
"isBase": true
|
||||
},
|
||||
{
|
||||
"name": 'inch',
|
||||
"symbol" 'in',
|
||||
"baseUnit": 'meter',
|
||||
"factor": 0.0254
|
||||
},
|
||||
{
|
||||
"name": 'nautical mile',
|
||||
"symbol": 'Nm',
|
||||
"baseUnit": 'meter',
|
||||
"factor": 1852
|
||||
},
|
||||
{
|
||||
"name": 'Kelvin',
|
||||
"symbol": "Kº",
|
||||
"isBase": true,
|
||||
},
|
||||
{
|
||||
"name": 'Celsius',
|
||||
"symbol": "Cº",
|
||||
"baseUnit" : "Kelvin",
|
||||
"toBase": "x + 273.15",
|
||||
"fromBase": "x - 273.15"
|
||||
},
|
||||
{
|
||||
"name": 'Fahrenheit',
|
||||
"symbol": "Fº",
|
||||
"baseUnit" : "Kelvin",
|
||||
"toBase": "(x - 32) × 5/9 + 273.15",
|
||||
"fromBase": "x - 273.15) * 9/5 + 32"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Formula Search and Computation
|
||||
- Search through a vast collection of formulas from multiple domains:
|
||||
- Mathematics
|
||||
- Physics
|
||||
- Medical sciences
|
||||
- Engineering
|
||||
- And more!
|
||||
- Input values for formula variables
|
||||
- Get computed results with proper units
|
||||
|
||||
### Unit Management
|
||||
- Each data value includes its magnitude and unit
|
||||
- Convert between different units seamlessly
|
||||
- Automatic unit validation and conversion
|
||||
|
||||
### Formula Editor
|
||||
- Built-in formula editor using the d4rt interpreter
|
||||
- Create and modify formulas with ease
|
||||
- Syntax highlighting and validation
|
||||
|
||||
### Formula Sharing
|
||||
- Share formulas with other users
|
||||
- Import formulas from the community
|
||||
- Collaborative formula database
|
||||
|
||||
### Rich Formula Documentation
|
||||
Each formula includes:
|
||||
- **The formula itself** - Mathematical expression
|
||||
- **Explanation** - Detailed description in Markdown format
|
||||
- **Images** - Visual diagrams, graphs, or illustrations
|
||||
- **Examples** - Sample calculations and use cases
|
||||
|
||||
## Getting Started
|
||||
|
||||
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
|
||||
|
||||
[Contribution guidelines to be added]
|
||||
|
|
|
|||
4
flutterw
4
flutterw
|
|
@ -113,7 +113,7 @@ exec_in_container(){
|
|||
main(){
|
||||
detect_container
|
||||
|
||||
if [ "$1" = "--build" ]; then
|
||||
if [ "$1" = "--build-container" ]; then
|
||||
build_image
|
||||
return $?
|
||||
elif [ "$1" = "--cleancache" ]; then
|
||||
|
|
@ -123,7 +123,7 @@ main(){
|
|||
exec_in_container ${@:2}
|
||||
return $?
|
||||
elif [ "$1" = "" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
echo "Usage: $0 {build|cleancache|exec <command>}"
|
||||
echo "Usage: $0 {--help|--build-container|--cleancache|--exec <command>|<flutter args>}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -116,19 +116,39 @@ class FormulaEvaluator {
|
|||
|
||||
void _validateInputValues(Formula formula, Map<String, dynamic> inputValues) {
|
||||
final missingVars = <String>[];
|
||||
|
||||
|
||||
for (final inputVar in formula.inputVarNames()) {
|
||||
if (!inputValues.containsKey(inputVar)) {
|
||||
missingVars.add(inputVar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (missingVars.isNotEmpty) {
|
||||
throw FormulaEvaluationException(
|
||||
'Missing required input variables for formula "${formula.name}": '
|
||||
'${missingVars.join(', ')}',
|
||||
);
|
||||
}
|
||||
|
||||
// Validate that input values are in the allowed values list if specified
|
||||
for (final vs in formula.input) {
|
||||
final values = vs.values;
|
||||
if (values != null && values.isNotEmpty) {
|
||||
final inputValue = inputValues[vs.name];
|
||||
if (inputValue != null) {
|
||||
// Convert input value to string for comparison since allowed values are stored as strings
|
||||
final inputValueAsString = inputValue.toString();
|
||||
final containsValue = values.any((allowedValue) => allowedValue.toString() == inputValueAsString);
|
||||
|
||||
if (!containsValue) {
|
||||
throw FormulaEvaluationException(
|
||||
'Invalid value for variable "${vs.name}" in formula "${formula.name}". '
|
||||
'Expected one of: [${values.join(', ')}], but got: $inputValue',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,22 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
test('throws exception for invalid values when allowed values are specified', () {
|
||||
final formula = Formula(
|
||||
name: 'Test formula with allowed values',
|
||||
input: [
|
||||
VariableSpec(name: 'status', unit: 'string', values: ['active', 'inactive']),
|
||||
],
|
||||
output: VariableSpec(name: 'result', unit: 'string'),
|
||||
d4rtCode: 'result = status;',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => evaluator.evaluate(formula, {'status': 'invalid_value'}), // Not in allowed values
|
||||
throwsA(isA<FormulaEvaluationException>()),
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
group('Utility methods', () {
|
||||
|
|
|
|||
|
|
@ -120,15 +120,77 @@ void main() {
|
|||
final evaluator = FormulaEvaluator();
|
||||
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 2,
|
||||
'Breathing': 2,
|
||||
'MuscleTone': 2,
|
||||
'Reflexes': 2,
|
||||
'SkinColor': 2
|
||||
'HeartRate': '> 100 bpm',
|
||||
'Breathing': 'Strong, robust cry',
|
||||
'MuscleTone': 'Flexed arms/leg, resists extension',
|
||||
'Reflexes': 'Cry on stimulation',
|
||||
'SkinColor': 'Pink'
|
||||
});
|
||||
|
||||
expect(result, 'Score: 10 - Normal');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Good condition case', () async {
|
||||
final corpus = await testCorpus;
|
||||
final formula = corpus.getFormula("Apgar Score")!;
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': '> 100 bpm', // 2
|
||||
'Breathing': 'Strong, robust cry', // 2
|
||||
'MuscleTone': 'Some', // 1
|
||||
'Reflexes': 'Grimace on aggressive stimulation', // 1
|
||||
'SkinColor': 'Blue extremities, pink body' // 1
|
||||
});
|
||||
|
||||
expect(result, 'Score: 7 - Normal');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Needs assistance case', () async {
|
||||
final corpus = await testCorpus;
|
||||
final formula = corpus.getFormula("Apgar Score")!;
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': '> 100 bpm', // 2
|
||||
'Breathing': 'Weak, irregular', // 1
|
||||
'MuscleTone': 'Some', // 1
|
||||
'Reflexes': 'Grimace on aggressive stimulation', // 1
|
||||
'SkinColor': 'Blue extremities, pink body' // 1
|
||||
});
|
||||
|
||||
expect(result, 'Score: 6 - Needs assistance');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Critical condition case', () async {
|
||||
final corpus = await testCorpus;
|
||||
final formula = corpus.getFormula("Apgar Score")!;
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
||||
final result = evaluator.evaluate(formula, {
|
||||
'HeartRate': 'Absent', // 0
|
||||
'Breathing': 'Absent', // 0
|
||||
'MuscleTone': 'None', // 0
|
||||
'Reflexes': 'No response', // 0
|
||||
'SkinColor': 'Blue or pale' // 0
|
||||
});
|
||||
|
||||
expect(result, 'Score: 0 - Critical condition');
|
||||
});
|
||||
|
||||
test('evaluates APGAR score formula - Invalid value throws exception', () async {
|
||||
final corpus = await testCorpus;
|
||||
final formula = corpus.getFormula("Apgar Score")!;
|
||||
final evaluator = FormulaEvaluator();
|
||||
|
||||
expect(() => evaluator.evaluate(formula, {
|
||||
'HeartRate': 'Invalid Value', // Not in allowed values
|
||||
'Breathing': 'Absent', // 0
|
||||
'MuscleTone': 'None', // 0
|
||||
'Reflexes': 'No response', // 0
|
||||
'SkinColor': 'Blue or pale' // 0
|
||||
}), throwsA(isA<FormulaEvaluationException>()));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
# Units and Formulas Definition Format
|
||||
|
||||
## Units Specification
|
||||
|
||||
Units are defined in `.d4rt.units` files using JSON arrays. Each unit can have:
|
||||
|
||||
```dart
|
||||
{
|
||||
"name": "unit name", // Full name (required)
|
||||
"symbol": "abbr", // Symbol (required)
|
||||
"isBase": true, // Mark as base unit (exclusive with baseUnit)
|
||||
"baseUnit": "parent", // Reference unit for conversions
|
||||
"factor": 1.0, // Conversion factor to base unit
|
||||
"toBase": "x * 1000", // Conversion code to base (expression)
|
||||
"fromBase": "x / 1000" // Conversion code from base (expression)
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rules:
|
||||
1. Use either `isBase` OR `baseUnit`+conversion (factor or code)
|
||||
2. Factor-based units use simple multiplicative conversions
|
||||
3. Code-based units require both `toBase` and `fromBase` Dart expressions
|
||||
4. Code expressions:
|
||||
- Use `x` as input variable
|
||||
- Must return a numeric value
|
||||
- Can use math functions via `dart:math`
|
||||
|
||||
### Examples:
|
||||
**Simple Factor-based:**
|
||||
```json
|
||||
{
|
||||
"name": "kilometer",
|
||||
"symbol": "km",
|
||||
"baseUnit": "meter",
|
||||
"factor": 1000
|
||||
}
|
||||
```
|
||||
|
||||
**Code-based Conversion:**
|
||||
```json
|
||||
{
|
||||
"name": "Fahrenheit",
|
||||
"symbol": "°F",
|
||||
"baseUnit": "Kelvin",
|
||||
"toBase": "(x - 32) * 5/9 + 273.15",
|
||||
"fromBase": "(x - 273.15) * 9/5 + 32"
|
||||
}
|
||||
```
|
||||
|
||||
## Formulas Specification
|
||||
|
||||
Formulas are defined in `.d4rt` files using JSON arrays. Each formula has:
|
||||
|
||||
```dart
|
||||
{
|
||||
"name": "Formula Name", // Required
|
||||
"description": "Markdown", // Optional
|
||||
"input": [ // List of input variables
|
||||
{
|
||||
"name": "varName", // Variable identifier
|
||||
"unit": "unitName" // Base unit for calculations
|
||||
}
|
||||
],
|
||||
"output": { // Single output variable
|
||||
"name": "resultVar",
|
||||
"unit": "outputUnit"
|
||||
},
|
||||
"d4rtCode": "Dart code", // Calculation logic
|
||||
"tags": ["physics", "energy"] // Categories
|
||||
}
|
||||
```
|
||||
|
||||
### Formula Code Rules:
|
||||
1. Input variables are declared as `final`
|
||||
2. Output variable must be assigned
|
||||
3. Can use:
|
||||
- Basic math operators
|
||||
- `dart:math` functions
|
||||
- Custom functions from FormulaEvaluator
|
||||
4. Example:
|
||||
```dart
|
||||
// Inputs: m (kg), v (m/s)
|
||||
d4rtCode: "KE = 0.5 * m * pow(v, 2);"
|
||||
```
|
||||
|
||||
### Conversion Code Examples
|
||||
|
||||
**Expression-style:**
|
||||
```dart
|
||||
// Simple factor conversion
|
||||
final x = 5; // Value in source unit
|
||||
main() => x * 1000; // Convert to base unit
|
||||
```
|
||||
|
||||
**Statement-style:**
|
||||
```dart
|
||||
// Complex conversion with multiple steps
|
||||
final x = 212; // Fahrenheit
|
||||
main() {
|
||||
var celsius = (x - 32) * 5/9;
|
||||
return celsius + 273.15; // Convert to Kelvin
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. Units must form a DAG (no circular dependencies)
|
||||
2. Formulas must declare all input variables
|
||||
3. Output variable must be assigned in d4rtCode
|
||||
4. Unit conversion code must handle numeric inputs
|
||||
5. Base units must exist before derived units
|
||||
Loading…
Reference in a new issue