Metadata-Version: 2.4
Name: modus-py
Version: 0.1.0a7
Summary: Schema-driven, unit-aware Polars transform engine.
Project-URL: Homepage, https://github.com/12345054321/modus
Project-URL: Repository, https://github.com/12345054321/modus
Project-URL: Documentation, https://github.com/12345054321/modus/wiki
Project-URL: Changelog, https://github.com/12345054321/modus/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/12345054321/modus/issues
Author: Chloe Elvin
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: astropy,dataframe,pipeline,polars,schema,timeseries,transform,units
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: astropy<8.0.0,>=7.0.0
Requires-Dist: polars<2.0.0,>=1.34.0
Requires-Dist: pyarrow
Requires-Dist: pydantic<3.0.0,>=2.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: tzdata
Provides-Extra: dev
Requires-Dist: pandas; extra == 'dev'
Requires-Dist: playwright>=1.61; extra == 'dev'
Requires-Dist: pyright>=1.1; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-benchmark; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest-mock; extra == 'dev'
Requires-Dist: pytest-ruff; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.15.0; extra == 'dev'
Provides-Extra: gui
Requires-Dist: kaleido; extra == 'gui'
Requires-Dist: nicegui[plotly]>=2.0; extra == 'gui'
Description-Content-Type: text/markdown

<img src="src/modus/gui/moduslogo.png" alt="modus logo" width="120">

# modus

Schema-driven, unit-aware Polars transform engine.

modus pairs `ModusFrame` — a Polars DataFrame with per-column Astropy unit
metadata — with `FrameTransform`, a builder-pattern engine for expressing
timeseries transform pipelines (grouping, cleaning, deriving, filtering, and
aggregating) declaratively, while keeping unit metadata accurate throughout.

## Installation

```
pip install modus-py
```

## Quick start

```python
import astropy.units as u
import polars
import modus

frame = modus.ModusFrame(
    data=polars.DataFrame({"distance": [100.0, 250.0], "time": [4.0, 10.0]}),
    units={"distance": u.m, "time": u.s},
)

velocity = frame.col("distance") / frame.col("time")  # UnitExpr; unit propagates
frame = frame.with_columns(velocity=velocity)
frame.column_unit("velocity")  # Unit("m / s") -- derived, never declared
```

```python
import datetime

import astropy.units as u
import polars
import modus
from modus.transform.ops import aggregations, cleaners, groupers, suppressors

frame = modus.ModusFrame(
    data=polars.DataFrame(
        {
            "ts": [
                datetime.datetime(2024, 1, 1, 0, 0, 0),
                datetime.datetime(2024, 1, 1, 0, 0, 10),
                datetime.datetime(2024, 1, 1, 0, 0, 20),
                datetime.datetime(2024, 1, 1, 0, 0, 30),
                datetime.datetime(2024, 1, 1, 0, 1, 40),
                datetime.datetime(2024, 1, 1, 0, 1, 50),
                datetime.datetime(2024, 1, 1, 0, 2, 0),
                datetime.datetime(2024, 1, 1, 0, 2, 10),
            ],
            "sts": [0, 1, 1, 0, 0, 1, 1, 0],
            "pressure_kpa": [101.3, 99.8, 99.6, 98.7, 102.1, 100.5, 100.2, 101.0],
        }
    ),
    units={"pressure_kpa": u.kPa},
)

result = (
    modus.FrameTransform()
    .add_grouper(groupers.DataGap("chunk", gap_seconds=60, timestamp_column="ts"))
    .add_grouper(groupers.EventSequence("run_event", input_column="sts", value=1, within="chunk"))
    .add_cleaner(cleaners.Interpolate("pressure_kpa", within="chunk"))
    .add_suppressor(suppressors.SuppressLeadingEvent("run_event", within="chunk"))
    .add_suppressor(suppressors.SuppressTrailingEvent("run_event", within="chunk"))
    .group_by("chunk", "run_event")
    .add_aggregation(aggregations.Mean("pressure_kpa", output_name="mean_pressure_kpa"))
    .add_aggregation(aggregations.Duration(output_name="run_duration_s", timestamp_column="ts"))
    .apply(frame)
)

labelled = result.labelled      # ModusFrame -- timeseries with grouper columns added
aggregated = result.aggregated  # ModusFrame -- one row per (chunk, run_event) group
```

## Prefer a GUI?

The easiest way to get started with modus, especially if you'd rather not write the Python above by hand:

```bash
pip install "modus-py[gui]"
modus-spec-gui
```

This opens the TransformSpec builder: pick groupers, cleaners, derivations, and aggregations from a menu, fill in their fields, and watch the JSON/YAML for the resulting pipeline update live, no hand-written mapping or operation-class imports required. It can also load an existing `ModusFrame` or `TransformSpec` to build and run against real data. See [Use the GUI Apps](docs/How-to-Guides/Use-the-GUI-Apps.md).

## Documentation

See [docs/](docs/) for the full reference: `ModusFrame`/`UnitExpr`, the unit
registry, `FrameTransform`'s execution model and pre-flight validation, each
operation family (groupers, cleaners, derivations, suppressors, group masks,
event views, aggregations), the declarative `TransformSpec` schema, and JSON
serialisation.

## Custom units

modus ships an extensible unit registry, in the same spirit as Astropy's own
enabled-units model:

```python
import astropy.units as u
import modus

modus.units.registry.register("kgcm2", u.def_unit("kgcm2", 98.0665 * u.kPa))
```

## Custom derivations and aggregations

Both extension points are plugin registries keyed by name, so a schema or
manifest layer built on top of modus can construct bespoke operations from
declarative configuration without importing analytic-specific Python:

```python
import modus

@modus.transform.DerivationRegistry.register("state_inference")
class StateInferenceDerivation(modus.transform.Derivation):
    ...

@modus.transform.AggregationRegistry.register("weighted_percentile")
class WeightedPercentile(modus.transform.Aggregation):
    ...
```

See [Write a Custom Derivation](docs/How-to-Guides/Write-a-Custom-Derivation.md), [Write a Custom Aggregation](docs/How-to-Guides/Write-a-Custom-Aggregation.md), and [Register a Custom Operation in the GUI](docs/How-to-Guides/Register-a-Custom-Operation-in-the-GUI.md) for the full requirements and worked examples.

## Development

```
pip install -e .[dev]
pytest
```

## Licence

BSD-3-Clause — see [LICENSE](LICENSE).
