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 |
Project Structure
Gneisswork/
├── run.py # Application entry point
├── sedmob/
│ ├── __init__.py # Package marker
│ ├── app.py # Flask app factory, routes, and helpers
│ ├── api.py # REST API blueprint
│ ├── models.py # SQLAlchemy ORM models
│ ├── seed.py # Reference data seeding
│ ├── requirements.txt # Python dependencies
│ ├── static/
│ │ └── favicon.png # Site icon
│ ├── templates/
│ │ ├── base.html # Master layout template
│ │ ├── home.html # Profile list (home page)
│ │ ├── profile_form.html # Profile create/edit + bed list
│ │ ├── bed_form.html # Bed create/edit form
│ │ └── reference.html # Reference data management
│ └── tests/
│ ├── conftest.py # Fixtures: app, client, db (in-memory SQLite)
│ └── test_*.py # Feature test modules
├── .pwlw-sedmob/ # Legacy Cordova mobile app (archived)
├── media/ # Banner, icon, and screenshots
└── docs/ # This documentation
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 |
| Bed CRUD | /profile/<id>/bed/... |
Create, edit, delete, reorder beds |
| 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 |
| 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.