# Formula Core Package

A shared formula evaluator library for FastAPI applications that synchronously evaluates string-template formulas with field-type-aware substitution.

## Features

- 56 built-in functions across Conditional, Math, Logical, String, Array, Date/Time, and Constant categories
- Field-type-aware token resolution (dropdown/radio/checkbox label mapping, association reference following, date/datetime coercion, file metadata)
- SEQUENCE() preprocessing via a Mongo `counters` collection for atomic counters
- Per-call field + record caching to avoid redundant DB lookups
- Standardized exceptions: `MissingFieldError` and `FormulaEvaluationError`

## Installation

```bash
pip install abs-formula-core
```

or with Poetry:

```bash
poetry add abs-formula-core
```

## Usage

```python
from motor.motor_asyncio import AsyncIOMotorDatabase
from abs_formula_core import FormulaEvaluatorService, MissingFieldError

evaluator = FormulaEvaluatorService(db=cosmos_db)

formula = [
    "MULTIPLY(IF(",
    {"is_field": True, "field": "<dropdown_field_id>"},
    " == \"Yes\", MULTIPLY(",
    {"is_field": True, "field": "<number_field_id>"},
    ", 6), 24))",
]

try:
    value = await evaluator.evaluate(formula, record, entity_id="<entity_id>")
except MissingFieldError as e:
    # Field referenced by formula has no value on the record
    print(e.field_id)
```

## Formula token shape

```
List[Union[
    str,                                                            # literal expression fragment
    {"is_field": True, "field": "<field_id>",                       # record field reference
     "reference_field": "<inner_field_id>"?},                       # optional association follow
]]
```

Strings are concatenated verbatim into the expression. Field-reference
dicts are resolved against the record:

- **dropdown / radio / checkbox** UUIDs are replaced with their option
  labels via the field definition.
- **association** values are followed when `reference_field` is set:
  the referenced record is loaded and its inner field value substituted.
- **date / datetime / time / time_range / date_range** are coerced into
  `datetime` objects for date-function compatibility.
- **file** fields return their stored metadata shape.
- **number / text / boolean** values are formatted as expression literals.

## Supported functions

56 functions across categories: Conditional (IF, SWITCH), Math (SUM,
COUNT, AVG, MIN, MAX, ROUND, ABS, DIVIDE, MULTIPLY, MINUS, MOD, POWER,
SQRT, LOG), Logical (AND, OR, NOT, XOR, EXACT), String (CONCATENATE,
UPPER, LOWER, LEN, LEFT, RIGHT, MID, TRIM, REPLACE, REPT, SEARCH,
SUBSTITUTE, TEXT, IN, STR, VALUE), Array (APPEND, EXTEND, REMOVE),
Date/Time (TODAY, NOW, DATE, DATEVALUE, DATEADD, DATEDIFF, ADD_DAYS,
SUBTRACT_DAYS, WORKDAY, WORKDAYS, DAYS, MONTH, YEAR, DAY, HOUR, MINUTE,
SECOND, HOURS_DIFF, MINUTES_DIFF, ADD_MINUTES, SUBTRACT_MINUTES,
FORMAT_DATE, IOSWEEKNUM, WEEKNUM), Constants (PI, TRUE, FALSE).

Plus SEQUENCE() preprocessing via the `counters` Mongo collection.

## Errors

- `MissingFieldError(field_id)` — raised when a record field referenced
  by the formula has no value. Callers should surface a 422 with the
  field id.
- `FormulaEvaluationError(message, expression?)` — raised when a formula
  fails to evaluate for non-missing-field reasons (malformed expression,
  division by zero, simpleeval rejection).
