clqms-be/app/Services/CalculatorService.php

171 lines
4.8 KiB
PHP

<?php
namespace App\Services;
use MathParser\StdMathParser;
use MathParser\Interpreting\Evaluator;
use MathParser\Exceptions\MathParserException;
class CalculatorService {
protected StdMathParser $parser;
protected Evaluator $evaluator;
/**
* Gender mapping for calculations
* 0 = Unknown, 1 = Female, 2 = Male
*/
protected const GENDER_MAP = [
'unknown' => 0,
'female' => 1,
'male' => 2,
'0' => 0,
'1' => 1,
'2' => 2,
];
public function __construct() {
$this->parser = new StdMathParser();
$this->evaluator = new Evaluator();
}
/**
* Calculate formula with variables
*
* @param string $formula Formula with placeholders like {result}, {factor}, {gender}
* @param array $variables Array of variable values
* @return float|null Calculated result or null on error
* @throws \Exception
*/
public function calculate(string $formula, array $variables = []): ?float {
try {
// Convert placeholders to math-parser compatible format
$expression = $this->prepareExpression($formula, $variables);
// Parse the expression
$ast = $this->parser->parse($expression);
// Evaluate
$result = $ast->accept($this->evaluator);
return (float) $result;
} catch (MathParserException $e) {
log_message('error', 'MathParser error: ' . $e->getMessage() . ' | Formula: ' . $formula);
throw new \Exception('Invalid formula: ' . $e->getMessage());
} catch (\Exception $e) {
log_message('error', 'Calculator error: ' . $e->getMessage() . ' | Formula: ' . $formula);
throw $e;
}
}
/**
* Validate formula syntax
*
* @param string $formula Formula to validate
* @return array ['valid' => bool, 'error' => string|null]
*/
public function validate(string $formula): array {
try {
// Replace placeholders with dummy values for validation
$testExpression = preg_replace('/\{([^}]+)\}/', '1', $formula);
$this->parser->parse($testExpression);
return ['valid' => true, 'error' => null];
} catch (MathParserException $e) {
return ['valid' => false, 'error' => $e->getMessage()];
}
}
/**
* Extract variable names from formula
*
* @param string $formula Formula with placeholders
* @return array List of variable names
*/
public function extractVariables(string $formula): array {
preg_match_all('/\{([^}]+)\}/', $formula, $matches);
return array_unique($matches[1]);
}
/**
* Prepare expression by replacing placeholders with values
*/
protected function prepareExpression(string $formula, array $variables): string {
$expression = $formula;
foreach ($variables as $key => $value) {
$placeholder = '{' . $key . '}';
// Handle gender specially
if ($key === 'gender') {
$value = $this->normalizeGender($value);
}
// Ensure numeric value
if (!is_numeric($value)) {
throw new \Exception("Variable '{$key}' must be numeric, got: " . var_export($value, true));
}
$expression = str_replace($placeholder, (float) $value, $expression);
}
// Check for unreplaced placeholders
if (preg_match('/\{([^}]+)\}/', $expression, $unreplaced)) {
throw new \Exception("Missing variable value for: {$unreplaced[1]}");
}
return $expression;
}
/**
* Normalize gender value to numeric (0, 1, or 2)
*/
protected function normalizeGender($gender): int {
if (is_numeric($gender)) {
$num = (int) $gender;
return in_array($num, [0, 1, 2], true) ? $num : 0;
}
$genderLower = strtolower((string) $gender);
return self::GENDER_MAP[$genderLower] ?? 0;
}
/**
* Calculate from TestDefCal record
*
* @param array $calcDef Test calculation definition
* @param array $testValues Test result values
* @return float|null
*/
public function calculateFromDefinition(array $calcDef, array $testValues): ?float {
$formula = $calcDef['FormulaCode'] ?? '';
if (empty($formula)) {
throw new \Exception('No formula defined');
}
// Build variables array
$variables = [
'result' => $testValues['result'] ?? 0,
'factor' => $calcDef['Factor'] ?? 1,
];
// Add optional variables
if (isset($testValues['gender'])) {
$variables['gender'] = $testValues['gender'];
}
if (isset($testValues['age'])) {
$variables['age'] = $testValues['age'];
}
if (isset($testValues['ref_low'])) {
$variables['ref_low'] = $testValues['ref_low'];
}
if (isset($testValues['ref_high'])) {
$variables['ref_high'] = $testValues['ref_high'];
}
// Merge any additional test values
$variables = array_merge($variables, $testValues);
return $this->calculate($formula, $variables);
}
}