Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
56baf47
Remove adapter authentication
lars-petter-hauge Aug 5, 2026
e43e375
Extract shared model contracts
lars-petter-hauge Aug 5, 2026
b5c94c5
Add broker message contracts
lars-petter-hauge Aug 5, 2026
07f71ef
Add RabbitMQ transport
lars-petter-hauge Aug 5, 2026
4ac9ee9
Add Azure Service Bus transport
lars-petter-hauge Aug 5, 2026
977ce4d
Add model worker runtime
lars-petter-hauge Aug 5, 2026
46be75e
Move adapters into their own workers folder
lars-petter-hauge Aug 5, 2026
5048fe4
Add worker heartbeat registry
lars-petter-hauge Aug 5, 2026
691d27a
Add persisted broker result processing
lars-petter-hauge Aug 5, 2026
61e8d8d
Start broker services with the API
lars-petter-hauge Aug 5, 2026
306e4ff
Dispatch simulations through the broker
lars-petter-hauge Aug 5, 2026
158806d
Single adapter specification
lars-petter-hauge Aug 5, 2026
3900f50
Expose worker availability status
lars-petter-hauge Aug 5, 2026
4444624
Report active simulations as processing
lars-petter-hauge Aug 5, 2026
cd41986
Persist failures for timed out jobs
lars-petter-hauge Aug 5, 2026
2a96b84
Package API and worker images
lars-petter-hauge Aug 5, 2026
0db0a3b
Build worker packages in CI
lars-petter-hauge Aug 5, 2026
2556420
Run broker workers with Docker Compose
lars-petter-hauge Aug 5, 2026
d91ee14
Deploy broker workers in Radix
lars-petter-hauge Aug 5, 2026
5e72516
Document broker worker architecture
lars-petter-hauge Aug 5, 2026
8db444e
Handle processing results in the Python client
lars-petter-hauge Aug 5, 2026
a3264c4
Remove model runtimes from the local API image
lars-petter-hauge Aug 5, 2026
a80fee2
Preserve passthrough concentrations in workers
lars-petter-hauge Aug 5, 2026
8e0ae36
Timeout only dispatched model jobs
lars-petter-hauge Aug 5, 2026
2fdd8cc
Report model failures in the Python client
lars-petter-hauge Aug 5, 2026
645786f
Ensure backend goes to service bus
lars-petter-hauge Aug 6, 2026
9dbd541
Add azure.servicebus to noisy loggers
lars-petter-hauge Aug 7, 2026
64e7f18
Add acidwatch-worker-runtime as dev dep
lars-petter-hauge Aug 12, 2026
d78a6af
Remove restart listener
lars-petter-hauge Aug 12, 2026
bf5fc71
Cleanup unused connectionstrings
lars-petter-hauge Aug 17, 2026
69b8d3d
Cleanup README
lars-petter-hauge Aug 12, 2026
d77cded
Send immediate heartbeat on job start
lars-petter-hauge Aug 17, 2026
22a4d52
Install arcs and run in worker
lars-petter-hauge Aug 18, 2026
a2ae0ff
Remove unused URLs in radix
lars-petter-hauge Aug 19, 2026
e92423c
Improve scaling configuration in radix
lars-petter-hauge Aug 19, 2026
8e99f49
Run alembic and uvicorn directly
lars-petter-hauge Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions .devcontainer/install-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ pip install uv
uv sync --directory ./backend --extra pg

cp ./backend/.env.example ./backend/.env
echo "ARCS_API_BASE_URI=https://api-arcs-prod.radix.equinor.com">>./backend/.env
if [ -n "$CODESPACE_NAME" ]; then
echo "FRONTEND_URI=https://$CODESPACE_NAME-5173.app.github.dev">>./backend/.env
echo "FRONTEND_URI=https://$CODESPACE_NAME-5173.app.github.dev" >>./backend/.env
fi

cp ./frontend/.env.example ./frontend/.env
if [ -n "$CODESPACE_NAME" ]; then
echo "VITE_API_URL=https://$CODESPACE_NAME-8001.app.github.dev">>./frontend/.env
echo "VITE_API_URL=https://$CODESPACE_NAME-8001.app.github.dev" >>./frontend/.env
fi

cd frontend

npm install


56 changes: 49 additions & 7 deletions .github/workflows/python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
sparse-checkout: backend

- uses: actions/setup-python@v5
with:
Expand All @@ -28,17 +26,61 @@ jobs:
- uses: astral-sh/setup-uv@v5

- name: Install backend
run: uv sync
run: uv sync --package acidwatch-api

- name: Run ruff
run: |
uv run ruff format --check
uv run ruff check
uv run --package acidwatch-api ruff format --check src tests packages ../workers
uv run --package acidwatch-api ruff check src tests packages ../workers

- name: Run mypy
run: uv run mypy --strict src
run: uv run --package acidwatch-api mypy --strict src packages/acidwatch-models/src packages/acidwatch-messaging/src packages/acidwatch-messaging/typecheck packages/acidwatch-worker-runtime/src

- name: Run pytest
run: |
cp .env.example .env
uv run pytest tests
uv run --package acidwatch-api pytest tests

worker-packages:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- worker: example
test_path: tests
- worker: tocomo
test_path: tests
- worker: arcs
test_path: ""
- worker: arcs-exp
test_path: tests
- worker: srk-vanlaar
test_path: ""
- worker: gibbs-minimization
test_path: tests
- worker: phpitz-reactive
test_path: ""
- worker: phpitz-solubility
test_path: ""

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.12'

- uses: astral-sh/setup-uv@v5

- name: Install worker
working-directory: workers/${{ matrix.worker }}
run: uv sync

- name: Build worker package
working-directory: workers/${{ matrix.worker }}
run: uv build

- name: Test worker
if: matrix.test_path != ''
working-directory: workers/${{ matrix.worker }}
run: uv run pytest ${{ matrix.test_path }}
41 changes: 24 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ The production version of AcidWatch is found at https://acidwatch.radix.equinor.
## Developing

AcidWatch uses Python in the backend and Javascript in the frontend.
Additionally, some features require a reasonably up-to-date Java version. Ensure that you have Python 3.12 or later, [uv](https://docs.astral.sh/uv/), NodeJS and Java (eg. OpenJDK 21).

### Running with Docker Compose

Expand Down Expand Up @@ -53,18 +52,13 @@ The backend is written using FastAPI and SQLAlchemy.
Using uv, install AcidWatch's backend using the following command:

```sh
uv --directory backend sync
uv sync
```

> [!NOTE]
> Here, `--directory backend` instructs `uv` to enter the `backend/` directory before
> doing anything. If you enter the `backend` directory (eg. via `cd backend`), you
> can drop writing `--directory backend` for each command.

Then, run the backend in development mode using the following command:

```sh
uv --directory backend run python -m acidwatch_api
uv run python -m acidwatch_api
```

To change the settings, first copy `backend/.env.example` to `backend/.env` and
Expand Down Expand Up @@ -101,7 +95,7 @@ First, ensure that the backend is installed with the `pg` (PostgreSQL) optional
dependency group. This installs the recommended SQLAlchemy driver:

```sh
uv --directory backend sync --extra pg
uv sync --package acidwatch-api --extra pg
```

Then, set the `ACIDWATCH_DATABASE` as described in the [SQLite section](#SQLite) to the following:
Expand All @@ -117,8 +111,7 @@ ACIDWATCH_DATABASE=postgresql:///[database]?host=[path]
ACIDWATCH_DATABASE=postgresql://postgres:password@localhost:5432/acidwatch
```

AcidWatch uses SQLAlchemy's Alembic to handle migrations. Run `uv --directory backend
run alembic upgrade head` to migrate the database to the current schema.
AcidWatch uses SQLAlchemy's Alembic to handle migrations. Run `uv run alembic upgrade head` to migrate the database to the current schema.

Don't have a postgres running? Here's a simple docker setup, that will create the necessary
database to work with the example above:
Expand All @@ -131,6 +124,23 @@ docker run -e POSTGRES_PASSWORD=password -e POSTGRES_DB=acidwatch -p 5432:5432 p

For other databases, refer to SQLAlchemy documentation on how to create

### Workers

Each worker is an independent uv project with its own lockfile and virtual
environment. From a worker directory, install and test only that worker:

```sh
cd workers/example
uv sync
uv run pytest
```

The worker projects use local path sources for the shared messaging, model, and
runtime packages. Their third-party dependencies are resolved independently, so
dependency conflicts in one worker do not affect the backend or other workers.
See the [example worker](./workers/example/README.md) for the contributor
workflow and standalone execution interface.

### Frontend

The frontend uses Vite and React. Components are provided by the official
Expand Down Expand Up @@ -175,7 +185,7 @@ If someone fancies using codespaces and wants to break out of the tedious local
Open a terminal and write following commands to run frontend.

```sh
cd /frontend
cd frontend
npm run dev
```

Expand All @@ -184,14 +194,11 @@ npm run dev
Open another terminal and run the backend with uv:

```sh
uv --directory backend sync
uv --directory backend run python -m acidwatch_api
uv sync
uv run python -m acidwatch_api
```

#### 3. Toggle port visibility

Kudos! Now frontend is running on port 5173, and backend is on 8001. Toggle the port for backend only to be public so it's accessible by frontend.

#### 4. Point to a different deployment environment

Now open source models can be run and tested. To point to a different instance of deployment (dev, prod or local) install-dependencies.sh can be updated.
3 changes: 0 additions & 3 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ FRONTEND_CLIENT_ID=49385006-e775-4109-9635-2f1a2bdc8ea8
BACKEND_CLIENT_ID=456cc109-08d7-4c11-bf2e-a7b26660f99e
BACKEND_CLIENT_SECRET=<create and insert from acidwatch_backend _dev app registration>
BACKEND_API_SCOPE=api://456cc109-08d7-4c11-bf2e-a7b26660f99e/AcidWatch.User
TOCOMO_API_BASE_URI=http://localhost:5005
ARCS_API_BASE_URI=http://localhost:8000
TENANT_ID=3aa4a235-b6e2-48d5-9195-7fcf05b459b0
#CONNECTION_STRING=<get from Azure portal acidwatch-db-dev keys>
CONNECTION_STRING=local
FRONTEND_URI=http://localhost:5173
27 changes: 14 additions & 13 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
FROM python:3.13-slim
# Install OpenJDK 21 for Java support (needed by solubilityCCS)
RUN apt-get update && \
apt-get install -y openjdk-21-jdk-headless && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

ENV JAVA_HOME=/usr/lib/jvm/default-java
FROM python:3.13-slim AS build

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
COPY . /app
WORKDIR /app

ENV UV_NO_CACHE=1

RUN uv sync --package acidwatch-api --extra pg --frozen --no-dev --no-editable

COPY .. /app
FROM python:3.13-slim

COPY --from=build /app/.venv /app/.venv
COPY backend/alembic /app/backend/alembic
COPY backend/alembic.ini backend/entrypoint /app/backend/
WORKDIR /app/backend

ENV UV_NO_CACHE=1
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

RUN uv sync --package acidwatch-api --extra pg --frozen --no-dev --no-editable

USER 1000

EXPOSE 8001
EXPOSE 8000

ENTRYPOINT ["/app/backend/entrypoint"]
8 changes: 1 addition & 7 deletions backend/Dockerfile.local
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
FROM python:3.12-slim

# Install OpenJDK 21 for Java support (needed by solubilityCCS / neqsim)
RUN apt-get update && \
apt-get install -y openjdk-21-jdk-headless && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

ENV JAVA_HOME=/usr/lib/jvm/default-java
ENV UV_NO_CACHE=1
ENV UV_DYNAMIC_VERSIONING_BYPASS=0.1.0
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

Expand Down
4 changes: 2 additions & 2 deletions backend/entrypoint
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
set -ex

uv run --no-sync alembic upgrade head
exec uv run --no-sync uvicorn acidwatch_api.app:app --host 0.0.0.0 --port 8000
alembic upgrade head
exec uvicorn acidwatch_api.app:app --host 0.0.0.0 --port 8000
14 changes: 14 additions & 0 deletions backend/packages/acidwatch-messaging/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "acidwatch-messaging"
version = "0.1.0"
description = "Broker contracts and transports for AcidWatch"
requires-python = ">=3.12, <3.14"
dependencies = [
"acidwatch-models",
"aio-pika>=10.0.1",
"azure-servicebus>=7.14.2,<8.0.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from .contracts import AdapterJob, AdapterResult, Heartbeat
from .queues import (
DEAD_LETTER_QUEUE,
HEARTBEATS_QUEUE,
RESULTS_QUEUE,
job_queue_name,
)
from .transport import (
AzureServiceBusTransport,
Message,
RabbitMQTransport,
Transport,
create_transport,
)

__all__ = [
"AdapterJob",
"AdapterResult",
"AzureServiceBusTransport",
"DEAD_LETTER_QUEUE",
"HEARTBEATS_QUEUE",
"Heartbeat",
"Message",
"RESULTS_QUEUE",
"RabbitMQTransport",
"Transport",
"create_transport",
"job_queue_name",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from datetime import datetime
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field

from acidwatch_models.datamodel import AnyPanel, Conditions, Phase


class _Message(BaseModel):
model_config = ConfigDict(extra="forbid")


class AdapterJob(_Message):
model_input_id: UUID
model_id: str
concentrations: dict[str, int | float]
parameters: dict[str, bool | float | int | str]
conditions: Conditions


class AdapterResult(_Message):
model_input_id: UUID
phases: list[Phase] = Field(default_factory=list)
panels: list[AnyPanel] = Field(default_factory=list)
error: str | None = None


class Heartbeat(_Message):
model_id: str
instance_id: str
timestamp: datetime
job_id: str | None = None
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
HEARTBEATS_QUEUE = "acidwatch.heartbeats"
RESULTS_QUEUE = "acidwatch.results"
DEAD_LETTER_QUEUE = "acidwatch.results.dead"


def job_queue_name(model_id: str) -> str:
return f"acidwatch.{model_id}"
Loading
Loading