Metadata-Version: 2.3
Name: abs-formula-core
Version: 0.1.0
Summary: Shared formula evaluator for GovAssist services. Synchronously evaluates string-template formulas authored in the form formula-field UI, with full field-handler parity with automation-builder-service.
License: MIT
Author: AutoBridgeSystems
Author-email: info@autobridgesystems.com
Requires-Python: >=3.11,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: abs-exception-core (>=0.3.0,<0.4.0)
Requires-Dist: motor (>=3.0.0,<4.0.0)
Requires-Dist: pymongo (>=4.0.0,<5.0.0)
Requires-Dist: simpleeval (>=1.0.3,<2.0.0)
Description-Content-Type: text/markdown

# abs-formula-core

Shared formula evaluator for GovAssist services.

Synchronously evaluates string-template formulas authored in the form
formula-field UI. Mirrors automation-builder-service's evaluation engine
function-for-function so payment formulas, record-number formulas, and any
other formula-typed field produce identical results across services.

## Install

```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.

