feat: Add new API endpoints and HTML pages for ML model management
- Implemented HTML pages for datasets, models, training, testing, and results. - Created API endpoints for managing repositories, results, tests, and training sessions. - Added functionality for streaming training progress via Server-Sent Events (SSE). - Introduced a Dockerfile for the ML runner with necessary dependencies. - Developed an SDK for user code execution within the runner container. - Enhanced CSS styles for improved UI layout and navigation. - Established a layout template for consistent HTML structure across pages. - Added JavaScript for dynamic interactions on the models page. - Implemented WebSocket handling for real-time communication with kiosk devices and controllers. - Implemented model registration and management API at /api/models - Added Gitea proxy API for repository interactions at /api/repos - Created results API for listing and comparing training results at /api/results - Developed training management API for enqueueing and retrieving training jobs at /api/trainings - Introduced SSE endpoint for live training progress updates - Added HTML pages for models, datasets, and training management - Created a Dockerfile for the ML runner with necessary dependencies - Developed SDK for user code execution within the runner container - Enhanced CSS styles for improved UI/UX - Implemented WebSocket communication for real-time device and controller interactions in the kiosk system
This commit is contained in:
124
api/src/migrations/008_rulesets.sql
Normal file
124
api/src/migrations/008_rulesets.sql
Normal file
@@ -0,0 +1,124 @@
|
||||
-- ============================================================
|
||||
-- Rulesets schema (DB: rules)
|
||||
-- ------------------------------------------------------------
|
||||
-- Un ruleset e' una collezione versionata di "items" (paths
|
||||
-- Signal K per i logs, codici openmeteo per i forecasts, ...).
|
||||
-- Modello:
|
||||
-- * 5 tipi fissi: logs | forecast_current | forecast_hourly
|
||||
-- | marine_current | marine_hourly
|
||||
-- * Un solo ruleset puo' essere "active" per ciascun tipo.
|
||||
-- * Le versioni sono triple di interi 1..100 (major.build.patch).
|
||||
-- * Gli items sono JSONB per massima flessibilita'.
|
||||
-- * Ogni item ha un "ref" stabile scelto dall'utente: e' la
|
||||
-- chiave logica che garantisce continuita' su InfluxDB anche
|
||||
-- se il path del sensore cambia.
|
||||
-- * Le deployments tracciano quale ruleset-version e' stato
|
||||
-- pushato ad ogni sensore.
|
||||
-- ============================================================
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- RULESETS
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS rulesets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type TEXT NOT NULL
|
||||
CHECK (type IN ('logs','forecast_current','forecast_hourly','marine_current','marine_hourly')),
|
||||
version_major SMALLINT NOT NULL DEFAULT 1 CHECK (version_major BETWEEN 1 AND 100),
|
||||
version_build SMALLINT NOT NULL DEFAULT 0 CHECK (version_build BETWEEN 0 AND 100),
|
||||
version_patch SMALLINT NOT NULL DEFAULT 0 CHECK (version_patch BETWEEN 0 AND 100),
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
-- items: [{ ref, path, enabled, meta: {...} }, ...]
|
||||
-- ref: identificatore logico stabile (chiave su Influx)
|
||||
-- path: SK path (logs) | codice openmeteo (forecast/marine)
|
||||
-- meta: { name, unit, measurement, sk_path, group_name, category, ... }
|
||||
items JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
active BOOLEAN NOT NULL DEFAULT false,
|
||||
archived BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE (type, version_major, version_build, version_patch)
|
||||
);
|
||||
|
||||
-- solo UN ruleset active per tipo (archiviati esclusi)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS rulesets_one_active_per_type
|
||||
ON rulesets (type)
|
||||
WHERE active = true AND archived = false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS rulesets_type_idx ON rulesets (type);
|
||||
CREATE INDEX IF NOT EXISTS rulesets_active_idx ON rulesets (type) WHERE active = true;
|
||||
CREATE INDEX IF NOT EXISTS rulesets_archived_idx ON rulesets (archived);
|
||||
CREATE INDEX IF NOT EXISTS rulesets_items_gin_idx ON rulesets USING GIN (items);
|
||||
|
||||
-- Validazione items: array di oggetti con almeno ref+path
|
||||
CREATE OR REPLACE FUNCTION rulesets_validate_items() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
refs TEXT[];
|
||||
BEGIN
|
||||
IF jsonb_typeof(NEW.items) <> 'array' THEN
|
||||
RAISE EXCEPTION 'items must be a JSON array';
|
||||
END IF;
|
||||
|
||||
-- tutti gli item devono avere ref non vuoto e path (anche vuoto ammesso)
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements(NEW.items) it
|
||||
WHERE jsonb_typeof(it) <> 'object'
|
||||
OR NULLIF(it->>'ref','') IS NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'every item must be an object with a non-empty "ref"';
|
||||
END IF;
|
||||
|
||||
-- unicita' ref all'interno dello stesso ruleset
|
||||
SELECT array_agg(it->>'ref') INTO refs
|
||||
FROM jsonb_array_elements(NEW.items) it;
|
||||
IF (SELECT count(DISTINCT x) FROM unnest(refs) x) <> COALESCE(array_length(refs,1),0) THEN
|
||||
RAISE EXCEPTION 'item refs must be unique within the ruleset';
|
||||
END IF;
|
||||
|
||||
NEW.updated_at := NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS rulesets_validate_trigger ON rulesets;
|
||||
CREATE TRIGGER rulesets_validate_trigger
|
||||
BEFORE INSERT OR UPDATE ON rulesets
|
||||
FOR EACH ROW EXECUTE FUNCTION rulesets_validate_items();
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- DEPLOYMENTS
|
||||
-- Traccia quale ruleset-version e' stato pushato ad ogni
|
||||
-- sensore (per tipo). Un solo ruleset per (sensor,type).
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS ruleset_deployments (
|
||||
sensor_name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
ruleset_id UUID NOT NULL REFERENCES rulesets(id) ON DELETE CASCADE,
|
||||
deployed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
acked_at TIMESTAMPTZ,
|
||||
PRIMARY KEY (sensor_name, type)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ruleset_deployments_ruleset_idx
|
||||
ON ruleset_deployments (ruleset_id);
|
||||
|
||||
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
-- AUDIT LOG (opzionale ma utile)
|
||||
-- ─────────────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS ruleset_changes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ruleset_id UUID,
|
||||
type TEXT,
|
||||
action TEXT NOT NULL, -- created | updated | activated | archived | deleted | deployed
|
||||
user_id TEXT,
|
||||
payload JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ruleset_changes_ruleset_idx ON ruleset_changes (ruleset_id);
|
||||
CREATE INDEX IF NOT EXISTS ruleset_changes_created_idx ON ruleset_changes (created_at DESC);
|
||||
Reference in New Issue
Block a user