LogExp is a radiation logging and visualization platform that integrates a MightyOhm Geiger Counter with a Flask backend, Postgres storage, and a responsive web UI. It supports real‑time polling, analytics, CSV export, diagnostics, and a production‑ready Docker deployment using Gunicorn.
pip install -r requirements.txtexport SQLALCHEMY_DATABASE_URI=postgresql://logexp:logexp@localhost:5432/logexpflask db upgradeflask runLogExp uses GitHub Actions to enforce application correctness on every push and pull request.
The CI pipeline runs:
- Python 3.11
- Dependency installation from
requirements.txt - Full pytest suite
- Import‑time validation of all modules
- Deterministic, environment‑clean test execution
Workflow file:
.github/workflows/application-ci.yml
A green CI run is required before merging into main.
The main branch is protected to ensure stability and reproducibility:
- ✔ Require pull request
- ✔ Require all status checks to pass
- ✔ Require Application CI
- ✔ Require branches to be up to date
- ✔ No direct pushes to
main
This ensures main always reflects a deployable, production‑ready state.
All development occurs on feature branches:
git checkout -b feature/my-change- CI must pass
- Code must import cleanly
- Tests updated if behavior changes
- No unpinned or implicit dependencies
- No environment‑specific assumptions
Once CI is green and the PR is approved, it can be merged.
pytestIf you add a new dependency, update:
requirements.txt
and ensure tests still pass in a clean environment.
LogExp uses a fully pinned, CI‑validated dependency set:
Flask==3.0.0
Flask-Migrate==4.1.0
Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.3
matplotlib==3.10.8
psycopg2-binary==2.9.9
pydantic==2.12.5
python-dotenv==1.0.1
pyserial==3.5
pytest==8.2.1
pytz==2025.2
If you add a new import, add the corresponding package and verify CI passes.
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pytestThis mirrors CI and prevents environment drift.
- Tests pass locally
- Dependencies updated (if needed)
- No stray debug prints or commented code
- Code follows existing structure and conventions
- CI passes on GitHub
LogExp ships with a production‑grade Docker setup:
- Multi‑stage Dockerfile (slim runtime)
- Gunicorn application server
- Automatic Alembic migrations
- Idempotent database seeding (
flask seed-data) - Poller disabled by default (
START_POLLER=False) - Healthchecks for both Postgres and the Flask app
docker compose up --builddocker compose downdocker compose build --no-cachedocker compose logs -f| Variable | Purpose | Default |
|---|---|---|
SQLALCHEMY_DATABASE_URI |
Database connection string | Required |
START_POLLER |
Enable hardware poller | False |
FLASK_ENV |
Flask environment | production |
LOCAL_TIMEZONE |
UI timezone | America/Chicago |
/readings— Web UI/api/readings.json— JSON/api/readings.csv— CSV export
/api/poller/status/api/poller/start/api/poller/stop
/api/geiger/test/api/health— Healthcheck
The Geiger poller runs as a background thread reading from a USB‑serial device.
Poller starts automatically.
Poller is disabled unless explicitly enabled:
environment:
START_POLLER: "True"flask geiger-start
flask geiger-stopflask db upgradeflask seed-datalogexp/
│
├── app/
│ ├── app_blueprints/ # Flask blueprints (UI, API, diagnostics)
│ ├── templates/ # Jinja2 templates
│ ├── static/ # CSS, JS, images
│ ├── config.py # Config + env var parsing
│ ├── extensions.py # SQLAlchemy, Migrate, LoginManager
│ ├── models.py # SQLAlchemy models
│ ├── poller.py # Geiger poller thread
│ ├── services/ # DB analytics, telemetry, helpers
│ ├── __init__.py # create_app()
│ └── wsgi.py # WSGI entrypoint for Gunicorn
│
├── analytics/
│ ├── engine.py # Pure analytics engine (deterministic, DB‑free)
│ └── types.py # ReadingSample, Batch types
│
├── seeds/
│ └── seed_data.py # Idempotent database seeding
│
├── migrations/ # Alembic migrations
│
├── docker/ # Entrypoint scripts, compose variants
│ └── entrypoint.sh
│
├── tests/ # Full pytest suite
│
├── docker-compose.yml # Default compose (Linux/macOS)
├── docker-compose.pi.yml # Raspberry Pi deployment (KEEP‑0001)
├── docker-compose.mac.yml # macOS developer stack
├── docker-compose.linux.yml # Linux host stack
│
├── Dockerfile # Multi‑stage production build
├── gunicorn.conf.py # Gunicorn configuration
├── pyproject.toml # Package metadata + editable install
├── requirements.txt # Dev/test dependencies
├── docker-requirements.txt # Runtime-only dependencies
└── README.md
┌──────────────────────────────┐
│ MightyOhm Geiger Counter │
└───────────────┬──────────────┘
│ USB-Serial
▼
┌───────────────────────────┐
│ LogExp Poller Thread │
│ (optional in Docker) │
└───────────────┬───────────┘
│
▼
┌───────────────────────────┐
│ Postgres Database │
└───────────────┬───────────┘
│
▼
┌───────────────────────────┐
│ Flask API / Gunicorn │
└───────────────┬───────────┘
│
▼
┌───────────────────────────┐
│ Web UI / Clients │
└───────────────────────────┘
flowchart TD
A[MightyOhm Geiger Counter] -->|USB-Serial| B[LogExp Poller Thread]
B --> C[(Postgres Database)]
C --> D[Flask API / Gunicorn]
D --> E[Web UI / Clients]
| Mode | Server | Poller | Database | Notes |
|---|---|---|---|---|
| Dev | Flask dev server | Enabled | Local Postgres | Hot reload |
| Prod | Gunicorn (Docker) | Disabled by default | Docker Postgres | Auto‑migrations + seeding |
curl http://localhost:5000/api/geiger/testcurl http://localhost:5000/api/poller/statuscurl http://localhost:5000/api/healthLogExp contains two analytics layers:
-
Pure Analytics Engine (
logexp.analytics.engine)- deterministic
- in‑memory
- DB‑free
- future ingestion/control‑plane foundation
-
Legacy DB Analytics (
logexp.app.services.analytics)- SQLAlchemy queries
- transitional compatibility layer
These layers must remain separate.
- DB analytics tests →
reading_factory - Pure engine tests →
make_reading,make_batch
- Do not feed DB models into the pure engine
- Do not feed
ReadingSampleinto DB analytics - Do not collapse the two layers
The pure engine is the future.
- Postgres 18 is canonical
- SQLite is test‑only
| Variable | Description |
|---|---|
SQLALCHEMY_DATABASE_URI |
Postgres connection string |
LOCAL_TIMEZONE |
Local timezone |
| Variable | Type | Purpose |
|---|---|---|
GEIGER_THRESHOLD |
float | Diagnostics threshold |
START_POLLER |
bool | Enable/disable poller |
LOGEXP_NODE_ID |
str | Node identifier |
TELEMETRY_ENABLED |
bool | Telemetry toggle |
TELEMETRY_INTERVAL_SECONDS |
int | Telemetry interval |
CI guarantees:
- Docker image builds
create_app()boots- Env vars parsed correctly
- No SQLite fallback in container
- SQLite migrations validated via pytest
- Postgres migrations validated via CI
- Alembic env exercised under Postgres 18
- Defaults
- Environment variables
- Explicit overrides
MIT License