Fetch prices from elprisenligenu.dk

Swap the Energi Data Service datasets (DayAheadPrices + Elspotprices)
for elprisenligenu.dk's public hourly JSON API. The new source covers
both historical and post-2025 dates — previously, Elspotprices was
empty for all 2026 dates and DayAheadPrices was scoped to tomorrow
only, leaving days like 2026-04-07 uncovered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 13:31:47 +02:00
co-authored by Claude Opus 4.7
parent da20691d2b
commit 1958ac9c5a
@@ -5,8 +5,7 @@
[pmagnus.elprice.web.controllers.tariffs :as tariffs]) [pmagnus.elprice.web.controllers.tariffs :as tariffs])
(:import (:import
(java.net (java.net
URI URI)
URLEncoder)
(java.net.http (java.net.http
HttpClient HttpClient
HttpRequest HttpRequest
@@ -15,27 +14,16 @@
Timestamp) Timestamp)
(java.time (java.time
LocalDate LocalDate
LocalDateTime LocalDateTime)))
ZoneId)
(java.time.format
DateTimeFormatter)))
(def ^:private dk-zone (ZoneId/of "Europe/Copenhagen"))
(def ^:private date-fmt (DateTimeFormatter/ofPattern "yyyy-MM-dd"))
(def ^:private eur-dkk-rate 7.46) (def ^:private eur-dkk-rate 7.46)
(defn- build-url (defn- elprisen-url
[^LocalDate date] [^LocalDate date area]
(let [start (.format date date-fmt) (format "https://www.elprisenligenu.dk/api/v1/prices/%04d/%02d-%02d_%s.json"
end (.format (.plusDays date 1) date-fmt)] (.getYear date) (.getMonthValue date) (.getDayOfMonth date) area))
(str "https://api.energidataservice.dk/dataset/DayAheadPrices"
"?start=" start
"&end=" end
"&filter=" (URLEncoder/encode (json/write-str {"PriceArea" ["DK1" "DK2"]}) "UTF-8")
"&sort=TimeDK%20asc"
"&limit=200")))
(defn- fetch-json (defn- fetch-json
@@ -63,11 +51,29 @@
(when price-eur (* price-eur eur-dkk-rate)))) (when price-eur (* price-eur eur-dkk-rate))))
(defn- fetch-area
"Fetch hourly prices for one price area from elprisenligenu.dk."
[^LocalDate date area]
(let [url (elprisen-url date area)
_ (log/info "Fetching" url)
records (fetch-json url)]
(when (sequential? records)
(map (fn [r]
;; time_start is "YYYY-MM-DDTHH:MM:SS+HH:MM" already in DK local;
;; drop the 6-char offset suffix, keep the naive wall-clock stamp.
(let [ts (subs (:time_start r) 0 19)]
{:time-dk ts
:area area
:dkk (some-> (:DKK_per_kWh r) (* 1000.0)) ; kWh → MWh
:eur (some-> (:EUR_per_kWh r) (* 1000.0))}))
records))))
(defn- fetch-from-api (defn- fetch-from-api
"Fetch hourly prices for both DK1 and DK2 from elprisenligenu.dk."
[^LocalDate date] [^LocalDate date]
(let [url (build-url date)] (concat (fetch-area date "DK1")
(log/info "Fetching el prices for" (.format date date-fmt) "from" url) (fetch-area date "DK2")))
(:records (fetch-json url))))
(defn- tariff-dkk-mwh (defn- tariff-dkk-mwh
@@ -79,13 +85,13 @@
(defn- save-records! (defn- save-records!
[query-fn records] [query-fn records]
(doseq [rec records] (doseq [rec records]
(when-let [dkk (dkk-price (:DayAheadPriceDKK rec) (:DayAheadPriceEUR rec))] (when-let [dkk (dkk-price (:dkk rec) (:eur rec))]
(let [ts (parse-timestamp (:TimeDK rec)) (let [ts (parse-timestamp (:time-dk rec))
tariff (tariff-dkk-mwh (.toLocalDateTime ^Timestamp ts)) tariff (tariff-dkk-mwh (.toLocalDateTime ^Timestamp ts))
total (+ (double dkk) tariff)] total (+ (double dkk) tariff)]
(query-fn :insert-elprice! (query-fn :insert-elprice!
{:time-dk ts {:time-dk ts
:price-area (:PriceArea rec) :price-area (:area rec)
:price-dkk dkk :price-dkk dkk
:tariff-dkk tariff :tariff-dkk tariff
:total-dkk total}))))) :total-dkk total})))))