Add Eloverblik data pipeline: fetch metering points, readings, and charges to PostgreSQL

- Add eloverblik controller with API client for metering points, time series, and charges
- Add migrations for metering_points, meter_readings, and charges tables
- Add HugSQL queries (upsert-metering-point!, insert-meter-reading!, insert-charge!)
- Add POST /eloverblik/sync route to trigger sync from browser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:25:59 +01:00
co-authored by Claude Opus 4.6
parent c54ecd2cfa
commit ca473c62bb
9 changed files with 346 additions and 1 deletions
@@ -0,0 +1 @@
DROP TABLE IF EXISTS metering_points;
@@ -0,0 +1,15 @@
CREATE TABLE metering_points (
metering_point_id VARCHAR(18) PRIMARY KEY,
type_of_mp VARCHAR(50),
balance_supplier VARCHAR(100),
street_name VARCHAR(200),
building_number VARCHAR(20),
postcode VARCHAR(10),
city_name VARCHAR(100),
has_relation BOOLEAN,
settlement_method VARCHAR(50),
reading_occurrence VARCHAR(50),
consumer_start_date DATE,
first_consumer_name VARCHAR(200),
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS meter_readings;
@@ -0,0 +1,9 @@
CREATE TABLE meter_readings (
metering_point_id VARCHAR(18) NOT NULL REFERENCES metering_points(metering_point_id),
time_dk TIMESTAMP NOT NULL,
quantity_kwh NUMERIC(10,3),
quality VARCHAR(50),
PRIMARY KEY (metering_point_id, time_dk)
);
--;;
CREATE INDEX idx_meter_readings_time_dk ON meter_readings(time_dk);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS charges;
@@ -0,0 +1,16 @@
CREATE TABLE charges (
id SERIAL,
metering_point_id VARCHAR(18) NOT NULL REFERENCES metering_points(metering_point_id),
charge_type VARCHAR(20) NOT NULL,
name VARCHAR(200) NOT NULL,
description VARCHAR(500),
owner VARCHAR(100) NOT NULL,
valid_from_date DATE NOT NULL,
valid_to_date DATE,
period_type VARCHAR(50),
price NUMERIC(12,6),
quantity INTEGER,
position INTEGER NOT NULL DEFAULT 0,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (metering_point_id, charge_type, name, owner, valid_from_date, position)
);
+36
View File
@@ -11,3 +11,39 @@ FROM day_ahead_prices
WHERE time_dk::date = :date
AND price_area = :price-area
ORDER BY time_dk;
-- :name upsert-metering-point! :! :n
-- :doc Upsert a metering point, refreshing data on re-sync
INSERT INTO metering_points (metering_point_id, type_of_mp, balance_supplier, street_name,
building_number, postcode, city_name, has_relation, settlement_method,
reading_occurrence, consumer_start_date, first_consumer_name)
VALUES (:metering-point-id, :type-of-mp, :balance-supplier, :street-name,
:building-number, :postcode, :city-name, :has-relation, :settlement-method,
:reading-occurrence, :consumer-start-date, :first-consumer-name)
ON CONFLICT (metering_point_id) DO UPDATE SET
type_of_mp = EXCLUDED.type_of_mp,
balance_supplier = EXCLUDED.balance_supplier,
street_name = EXCLUDED.street_name,
building_number = EXCLUDED.building_number,
postcode = EXCLUDED.postcode,
city_name = EXCLUDED.city_name,
has_relation = EXCLUDED.has_relation,
settlement_method = EXCLUDED.settlement_method,
reading_occurrence = EXCLUDED.reading_occurrence,
consumer_start_date = EXCLUDED.consumer_start_date,
first_consumer_name = EXCLUDED.first_consumer_name,
fetched_at = now();
-- :name insert-meter-reading! :! :n
-- :doc Insert a meter reading, skip if already exists
INSERT INTO meter_readings (metering_point_id, time_dk, quantity_kwh, quality)
VALUES (:metering-point-id, :time-dk, :quantity-kwh, :quality)
ON CONFLICT (metering_point_id, time_dk) DO NOTHING;
-- :name insert-charge! :! :n
-- :doc Insert a charge record, skip if already exists
INSERT INTO charges (metering_point_id, charge_type, name, description, owner,
valid_from_date, valid_to_date, period_type, price, quantity, position)
VALUES (:metering-point-id, :charge-type, :name, :description, :owner,
:valid-from-date, :valid-to-date, :period-type, :price, :quantity, :position)
ON CONFLICT (metering_point_id, charge_type, name, owner, valid_from_date, position) DO NOTHING;