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 { $normalizedFormula = $this->normalizeFormulaVariables($formula, $variables); $expression = $this->prepareExpression($normalizedFormula, $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 formulas that reference raw variable names instead of placeholders. */ protected function normalizeFormulaVariables(string $formula, array $variables): string { if (str_contains($formula, '{')) { return $formula; } if (empty($variables)) { return $formula; } $keys = array_keys($variables); usort($keys, fn($a, $b) => mb_strlen($b) <=> mb_strlen($a)); foreach ($keys as $key) { $escaped = preg_quote($key, '/'); $formula = preg_replace_callback('/\b' . $escaped . '\b/i', function () use ($key) { return '{' . $key . '}'; }, $formula); } return $formula; } /** * 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); } }