Metadata-Version: 2.5
Name: geo-mlops-sdk
Version: 0.1.0
Summary: Edge SDK and runtime for the Geosoft MLOps Central Platform
Author-email: zer0 <osom8979@gmail.com>, Kiruas <kiruas12@gmail.com>
Maintainer-email: zer0 <osom8979@gmail.com>, Kiruas <kiruas12@gmail.com>
License-Expression: MIT
Keywords: edge,machine-learning,mlops,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: all
Requires-Dist: aiofiles>=23; extra == 'all'
Requires-Dist: aiosqlite>=0.20; extra == 'all'
Requires-Dist: fastapi>=0.110; extra == 'all'
Requires-Dist: numpy; extra == 'all'
Requires-Dist: nvidia-ml-py>=12; extra == 'all'
Requires-Dist: pillow; extra == 'all'
Requires-Dist: psutil>=5.9; extra == 'all'
Requires-Dist: pydantic-settings>=2.2; extra == 'all'
Requires-Dist: pymodbus<4,>=3.6; extra == 'all'
Requires-Dist: python-multipart>=0.0.9; extra == 'all'
Requires-Dist: pyyaml>=6; extra == 'all'
Requires-Dist: rfdetr>=1.0; extra == 'all'
Requires-Dist: ultralytics>=8.2; extra == 'all'
Requires-Dist: uvicorn>=0.29; extra == 'all'
Provides-Extra: edge
Requires-Dist: aiofiles>=23; extra == 'edge'
Requires-Dist: aiosqlite>=0.20; extra == 'edge'
Requires-Dist: fastapi>=0.110; extra == 'edge'
Requires-Dist: psutil>=5.9; extra == 'edge'
Requires-Dist: pydantic-settings>=2.2; extra == 'edge'
Requires-Dist: python-multipart>=0.0.9; extra == 'edge'
Requires-Dist: pyyaml>=6; extra == 'edge'
Requires-Dist: uvicorn>=0.29; extra == 'edge'
Provides-Extra: gpu
Requires-Dist: nvidia-ml-py>=12; extra == 'gpu'
Provides-Extra: modbus
Requires-Dist: pymodbus<4,>=3.6; extra == 'modbus'
Provides-Extra: rfdetr
Requires-Dist: numpy; extra == 'rfdetr'
Requires-Dist: pillow; extra == 'rfdetr'
Requires-Dist: rfdetr>=1.0; extra == 'rfdetr'
Provides-Extra: yolo
Requires-Dist: numpy; extra == 'yolo'
Requires-Dist: pillow; extra == 'yolo'
Requires-Dist: ultralytics>=8.2; extra == 'yolo'
Description-Content-Type: text/markdown

# Geo-MLOps-SDK

Edge SDK and runtime for the Geosoft MLOps Central Platform.

The package ships two things:

* **`geo_mlops_sdk.client`** — an async HTTP client (`httpx`) whose methods map
  1:1 onto Central's device API. Use it when you only want to call the platform
  from your own application.
* **`geo_mlops_sdk.edge`** — a standalone edge runtime: local durable queue,
  connectivity monitor, throttled uploader, collectors, model cache and
  runners, a FastAPI surface for the on-site web UI, and a service daemon.

An edge PC is assumed to be treated roughly — cables pulled, power cut,
offline for days. The runtime accumulates locally under a size/age ceiling,
watches for the link to come back, and pushes the backlog without starving the
collection that keeps running.

## Installation

```bash
pip install geo-mlops-sdk               # client only
pip install 'geo-mlops-sdk[edge]'       # + standalone runtime and daemon
pip install 'geo-mlops-sdk[edge,gpu,modbus,yolo]'
```

| Extra    | Adds |
|----------|------|
| `edge`   | runtime, FastAPI surface, daemon (`geo-mlops-edge`) |
| `gpu`    | NVIDIA utilization in the heartbeat |
| `modbus` | Modbus TCP collector |
| `yolo`   | local execution of YOLO models |
| `rfdetr` | local execution of RF-DETR models |
| `all`    | everything above |

> PyPI currently holds `0.0.2`, a placeholder published before this SDK was
> rewritten. `0.1.0` is the first release of the rewritten package; anything
> below it predates the current API and shares nothing with it but the name.

## Usage

```python
from geo_mlops_sdk.client import CentralClient
from geo_mlops_sdk.contracts.edge import HeartbeatBody

async with CentralClient("https://central.example", "device-token") as client:
    await client.register(location="yard-1")
    await client.send_heartbeat(HeartbeatBody(cpu=12.5, mem=55.0, disk=61.0))
```

```bash
geo-mlops-edge run --config /etc/geo-mlops/edge.yaml   # service entry point
geo-mlops-edge status                                  # ask a running agent
geo-mlops-edge queue --state failed                    # why nothing is arriving
```

Deployment samples live in [`deploy/`](deploy/): a systemd unit, a Dockerfile,
and annotated `edge.yaml` / Modbus register-map examples. The top-level
`docker-compose.yml` runs the agent on its own from that Dockerfile, reading
`/etc/geo-mlops/edge.yaml` and `edge.env` from the host; a push to `main` of
the geo remote deploys it onto the self-hosted runner labelled `edge`
(`.github/workflows/deploy-edge.yml`).

## Rotating a device token

A device token is required to talk to Central, and rotating one is a **site
visit**. There is no remote path back:

1. Rotate or revoke the token in Central (`POST /edge/devices/{id}/token:rotate`).
2. The agent's next call is refused, the link goes to `AUTH_FAILED`, and it
   stops -- deliberately, rather than hammering Central with rejected requests.
3. On the machine, put the new token in `/etc/geo-mlops/edge.env`
   (`GEO_EDGE_CENTRAL__TOKEN=...`) and restart the service:
   `sudo systemctl restart geo-mlops-edge`.

Step 3 cannot be done from Central. The command poller stops along with
everything else, so a `restart` command cannot reach the device either -- which
is why the fleet screen reports how many days a token has left
(`token_expires_in_days`): tokens last a year, and that is exactly long enough
for the expiry to arrive unplanned.

`geo-mlops-edge status` says the same thing in words, at the top of its output,
so an engineer already standing at the machine does not have to infer it from a
state name.

The agent never restarts itself. A `restart` command from the fleet makes it
exit with code 3 and the supervisor starts a clean process.

## Development

This project uses [uv](https://docs.astral.sh/uv/) for dependency management.

```bash
uv sync --group dev --all-extras
```

Quality tooling (wrappers run the tool from the project root):

```bash
./check.sh          # black --fix, isort --fix, flake8, mypy, pytest
./check.sh --ci     # formatters in check mode (what CI runs)
./check.sh --build  # the above plus `uv build`
```

Individual steps remain available: `./black.sh`, `./isort.sh`, `./flake8.sh`,
`./mypy.sh`, `./pytest.sh`.

Tests that need something outside the process are opt-in markers and are
excluded by default: `live` (a real Central server, see `scripts/live/`),
`weights` (real framework packages and checkpoints), `gpu` (an NVIDIA device).

Plans live in `docs/plans/`; the current one is
[`04-edge-sdk-rewrite-plan.md`](docs/plans/04-edge-sdk-rewrite-plan.md).
