Architecture
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Python 3.10+, Flask 3.0+ |
| ORM | Flask-SQLAlchemy 3.1+ |
| Database | SQLite |
| Templates | Jinja2 (Flask built-in) |
| Frontend | HTML5, inline CSS, vanilla JS |
| Testing | pytest 8.0+, pytest-flask 1.3+, hypothesis 6.0+ |
Project Structure
Gneisswork/
├── run.py # Application entry point (auto-installs deps)
├── main.py # Android APK entry point (Kivy → Flask → WebView)
├── sedmob/
│ ├── __init__.py # Package marker
│ ├── app.py # Flask app factory, all web routes, helpers
│ ├── api.py # Read-only JSON API blueprint (/api prefix)
│ ├── models.py # SQLAlchemy ORM models
│ ├── seed.py # Reference data seeding (runs once on first launch)
│ ├── requirements.txt # Python dependencies
│ ├── static/
│ │ ├── favicon.png # Site icon
│ │ └── geolocation.js # Browser GPS capture script
│ ├── templates/
│ │ ├── base.html # Master layout (nav, flash messages, inline CSS)
│ │ ├── home.html # Profile list
│ │ ├── profile_form.html # Profile create/edit + bed list
│ │ ├── bed_form.html # Bed create/edit form
│ │ ├── reference.html # Reference data management
│ │ └── settings.html # Backup/restore settings + live logs panel
│ └── tests/
│ ├── __init__.py # Package marker
│ ├── conftest.py # Fixtures: app, client, db (in-memory SQLite)
│ └── test_*.py # Feature test modules (18 files)
├── p4a-recipes/ # Custom python-for-android recipe overrides
│ ├── flask/ # Flask 3.1.1 (overrides p4a's bundled 2.0.3)
│ ├── sqlalchemy/ # SQLAlchemy 2.0.40 (overrides p4a's bundled 1.3.3)
│ └── werkzeug/ # Werkzeug 3.1.7 (compatible with Flask 3.x)
├── .test/
│ └── test_android_startup.py # Local simulation of Android startup
├── buildozer.spec # Buildozer config for APK packaging
├── Dockerfile # Container image (Python 3.12-slim + gunicorn)
├── docker-compose.yml # Compose config (volumes for db + uploads)
├── .pwlw-sedmob/ # Legacy Cordova mobile app (archived)
├── media/ # Banner, icon, and screenshots
└── docs/ # Documentation (published to GitHub Pages)
Application Factory Pattern
Gneisswork uses Flask’s application factory pattern via create_app() in sedmob/app.py. This function:
- Creates the Flask app instance with configuration
- Initializes the SQLAlchemy database extension
- Registers the API blueprint (
/apiprefix) - Creates all database tables
- Seeds reference data if tables are empty
- Registers all web routes
def create_app(config=None):
app = Flask(__name__)
# ... config setup ...
db.init_app(app)
from sedmob.api import api
app.register_blueprint(api)
with app.app_context():
db.create_all()
seed_database()
# ... route registration ...
return app
Route Organization
Routes are organized into logical groups within app.py:
| Group | Prefix | Purpose |
|---|---|---|
| Home | / |
Profile listing |
| Profile CRUD | /profile/... |
Create, edit, delete profiles |
| Photo Upload | /profile/<id>/photo/... |
Upload, delete profile photos |
| Bed Photo Upload | /profile/<id>/bed/<id>/photo/... |
Upload, delete bed photos |
| Bed Audio Upload | /profile/<id>/bed/<id>/audio/... |
Upload, delete bed audio |
| Bed CRUD | /profile/<id>/bed/... |
Create, edit, delete, reorder beds |
| Upload Serving | /uploads/... |
Serve uploaded photos and audio |
| CSV Export | /profile/<id>/export |
Download profile as CSV |
| Bulk Export | /export/all |
Download all profiles as ZIP of CSVs |
| Reference Data | /reference/... |
Manage lithologies and structures |
| Backup/Restore | /backup, /restore, /settings |
JSON database export/import |
| Full Backup | /export/full, /restore/full |
ZIP archive with database JSON + uploaded files |
| Logs | /logs |
Live application log stream (JSON) |
| API (Blueprint) | /api/... |
Read-only JSON API |
Design Patterns
Nested Resource URLs
Beds are nested under their parent profile in the URL structure, reflecting the domain relationship:
/profile/1/bed/new
/profile/1/bed/3
/profile/1/bed/3/delete
Position-Based Ordering
Beds use an integer position field for ordering within a profile rather than relying on primary key order. This supports:
- Drag-and-drop reordering via
POST /profile/<id>/bed/reorder - Automatic position shifting when a bed is deleted
- Explicit ordering in queries:
Bed.query.order_by(Bed.position)
Reference Data by Name
Beds store lithology, structure, and grain size values as string names rather than foreign keys. This allows:
- Simpler CSV export (no joins needed)
- Custom values that don’t exist in reference tables
- Reference data can be deleted without breaking existing bed records
Shared Reference Data Helper
The _ref_data() helper function queries all reference tables at once and passes them to templates as a single ref dict, keeping template rendering consistent across forms.
Database
SQLite is used as the database engine. The database file is created automatically at Flask’s instance/gneisswork.db on first run. No migrations framework is used — db.create_all() handles schema creation.