Developer Guide
This guide explains how to extend the GRAFT. It covers the three most common extension points:
| Guide | What you will build |
|---|---|
| Adding a New Model | A new ML forecasting pipeline that integrates with the existing run, simulation, and evaluation infrastructure |
| Adding a New Tab | A new page in the NiceGUI dashboard with its own route, navbar entry, UI components, and business logic |
| Adding a Data Source | A new backend data adapter that feeds sensor readings into the dashboard |
Architecture Recap
Section titled “Architecture Recap”Before extending the dashboard, it helps to understand the three-layer pattern every feature follows:
app/models/<model>.py ← ML logic: train, predict, run_pipelineapp/services/model_manager.py ← Orchestration: fetch data, call pipeline, scheduleapp/ui/control_tab_ui.py ← UI widgets: buttons, forms, config panelsapp/ui/components/control_tab.py ← Event wiring: connect buttons to service callsapp/main.py ← Page routes: register new pages at /routeFor a new model you touch the first two layers.
For a new tab you touch the last three.
For a new data source you touch app/backend/ and app/services/.
Protocol to Follow
Section titled “Protocol to Follow”Async everywhere — all model pipelines, data fetches, and database
writes are async functions. Always call them with
asyncio.create_task() from a UI click handler, never with await
directly in a synchronous callback.
app_state dict — shared mutable state within a page session is passed
as a plain dict. Keys follow the pattern selected_for_*,
available_*, *_running. Do not use global variables.
Config-driven — every model reads its hyperparameters from a YAML file
in app/configs/. UI controls in the Configuration tab write back to
those files via save_model_config(). New models must follow this
same pattern.
Database logging — every model must call log_metrics() and
save_prediction() from app/backend/database.py after each training
and prediction run. This is what populates the Performance Metrics page
and the Evaluation tab. Models that skip this step will not appear in
the UI.
Logger — create a module-level logger at the top of every new file:
from app.backend.logger_config import setup_loggerlogger = setup_logger(__name__)