@mxpicture/tesla-superchargers-mate 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/sql/reset.sql +8 -0
- package/sql/setup.sql +160 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxpicture/tesla-superchargers-mate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "TeslaMate integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "MXPicture",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@mxpicture/tesla-superchargers-generated": "^0.1.
|
|
52
|
+
"@mxpicture/tesla-superchargers-generated": "^0.1.41",
|
|
53
53
|
"postgres": "^3.4.9",
|
|
54
54
|
"short-uuid": "^6.0.3",
|
|
55
55
|
"zod": "^4.3.6"
|
package/sql/reset.sql
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
-- the accumulated price history this project exists to build. Run it only to
|
|
3
3
|
-- start over from scratch; setup.sql alone is idempotent and safe to
|
|
4
4
|
-- (re-)apply after updates.
|
|
5
|
+
DROP VIEW IF EXISTS pic_price_history_v;
|
|
6
|
+
|
|
7
|
+
DROP VIEW IF EXISTS pic_price_history_key_v;
|
|
8
|
+
|
|
9
|
+
DROP VIEW IF EXISTS pic_price_bound_v;
|
|
10
|
+
|
|
11
|
+
DROP VIEW IF EXISTS pic_price_fetch_id_v;
|
|
12
|
+
|
|
5
13
|
DROP VIEW IF EXISTS pic_cp_inv_v;
|
|
6
14
|
|
|
7
15
|
DROP VIEW IF EXISTS pic_cp_inv_prep_v;
|
package/sql/setup.sql
CHANGED
|
@@ -492,3 +492,163 @@ SELECT
|
|
|
492
492
|
base.cp_quan || ' kWh (' || base.cp_cost_raw || ' €)' AS cp
|
|
493
493
|
FROM
|
|
494
494
|
pic_cp_inv_prep_v AS base;
|
|
495
|
+
|
|
496
|
+
-- price history ------------------------------------------------------------------------------
|
|
497
|
+
-- Dropped instead of CREATE OR REPLACE for the same reason as the views above,
|
|
498
|
+
-- and because a bare CREATE VIEW made setup.sql non-idempotent (re-applying it
|
|
499
|
+
-- failed with 42P07 "relation already exists"). Dropped in dependency order.
|
|
500
|
+
DROP VIEW IF EXISTS pic_price_history_v;
|
|
501
|
+
|
|
502
|
+
DROP VIEW IF EXISTS pic_price_history_key_v;
|
|
503
|
+
|
|
504
|
+
DROP VIEW IF EXISTS pic_price_bound_v;
|
|
505
|
+
|
|
506
|
+
DROP VIEW IF EXISTS pic_price_fetch_id_v;
|
|
507
|
+
|
|
508
|
+
-- One row per pricebook snapshot ("fetch group") per charger, numbered oldest
|
|
509
|
+
-- first so consecutive snapshots can be chained into validity intervals.
|
|
510
|
+
CREATE VIEW pic_price_fetch_id_v AS
|
|
511
|
+
SELECT
|
|
512
|
+
pic_chargers_id,
|
|
513
|
+
last_fetch_uuid,
|
|
514
|
+
min(fetched_at) AS fetched_at,
|
|
515
|
+
row_number() OVER (
|
|
516
|
+
PARTITION BY
|
|
517
|
+
pic_chargers_id
|
|
518
|
+
ORDER BY
|
|
519
|
+
(min(fetched_at))
|
|
520
|
+
) AS fetch_id,
|
|
521
|
+
bool_or(is_tou) AS is_tou
|
|
522
|
+
FROM
|
|
523
|
+
pic_pricebooks
|
|
524
|
+
GROUP BY
|
|
525
|
+
pic_chargers_id,
|
|
526
|
+
last_fetch_uuid;
|
|
527
|
+
|
|
528
|
+
-- Validity interval of each snapshot: [fetched_at, end). Dates are resolved in
|
|
529
|
+
-- the charger's own zone, because start_time/end_time are local wall clock
|
|
530
|
+
-- there; deriving them with date_trunc() instead made the whole view depend on
|
|
531
|
+
-- the *client's* session TimeZone (psql and Grafana disagreed on 60 of 109
|
|
532
|
+
-- snapshots).
|
|
533
|
+
CREATE VIEW pic_price_bound_v AS
|
|
534
|
+
SELECT
|
|
535
|
+
base.pic_chargers_id,
|
|
536
|
+
base.last_fetch_uuid AS start_uuid,
|
|
537
|
+
base.fetch_id AS start_id,
|
|
538
|
+
base.fetched_at,
|
|
539
|
+
base.is_tou,
|
|
540
|
+
c.time_zone,
|
|
541
|
+
(base.fetched_at AT TIME ZONE c.time_zone)::date AS fetched_date,
|
|
542
|
+
COALESCE(next.fetched_at, now()) AS "end",
|
|
543
|
+
-- Exclusive day bound. A superseded snapshot ends on the day its successor
|
|
544
|
+
-- was fetched (that day already belongs to the successor). The still-open
|
|
545
|
+
-- snapshot must cover today, so its bound is tomorrow — with "today" the
|
|
546
|
+
-- chart always stopped one day short of now.
|
|
547
|
+
CASE
|
|
548
|
+
WHEN next.fetched_at IS NULL THEN (now() AT TIME ZONE c.time_zone)::date + 1
|
|
549
|
+
ELSE (next.fetched_at AT TIME ZONE c.time_zone)::date
|
|
550
|
+
END AS end_date
|
|
551
|
+
FROM
|
|
552
|
+
pic_price_fetch_id_v base
|
|
553
|
+
JOIN pic_chargers c ON c.id = base.pic_chargers_id
|
|
554
|
+
-- The successor is simply the charger's next snapshot. Matching on is_tou as
|
|
555
|
+
-- well (as this once did) left the interval open whenever a site switched
|
|
556
|
+
-- between flat and time-of-use pricing, so the superseded price ran to now()
|
|
557
|
+
-- and overlapped its replacement.
|
|
558
|
+
LEFT JOIN pic_price_fetch_id_v next ON next.pic_chargers_id = base.pic_chargers_id
|
|
559
|
+
AND next.fetch_id = (base.fetch_id + 1);
|
|
560
|
+
|
|
561
|
+
-- Explodes each validity interval into the calendar days it covers. The day
|
|
562
|
+
-- grid is built from plain dates (not timestamptz) so it cannot gain or lose a
|
|
563
|
+
-- day across a DST boundary.
|
|
564
|
+
CREATE VIEW pic_price_history_key_v AS
|
|
565
|
+
WITH
|
|
566
|
+
span AS (
|
|
567
|
+
SELECT
|
|
568
|
+
min(fetched_date) AS min_date,
|
|
569
|
+
max(end_date) AS max_date
|
|
570
|
+
FROM
|
|
571
|
+
pic_price_bound_v
|
|
572
|
+
),
|
|
573
|
+
dates AS (
|
|
574
|
+
SELECT
|
|
575
|
+
dd::date AS date
|
|
576
|
+
FROM
|
|
577
|
+
span s,
|
|
578
|
+
generate_series(
|
|
579
|
+
s.min_date::TIMESTAMP,
|
|
580
|
+
s.max_date::TIMESTAMP,
|
|
581
|
+
'1 day'::INTERVAL
|
|
582
|
+
) dd
|
|
583
|
+
)
|
|
584
|
+
SELECT
|
|
585
|
+
d.date,
|
|
586
|
+
h.pic_chargers_id,
|
|
587
|
+
h.start_uuid AS last_fetch_uuid,
|
|
588
|
+
h.is_tou,
|
|
589
|
+
h.time_zone,
|
|
590
|
+
EXTRACT(
|
|
591
|
+
DOW
|
|
592
|
+
FROM
|
|
593
|
+
d.date
|
|
594
|
+
)::SMALLINT AS day_of_week
|
|
595
|
+
FROM
|
|
596
|
+
dates d
|
|
597
|
+
-- inner join: a date no snapshot covers carried only NULLs and was discarded
|
|
598
|
+
-- downstream anyway
|
|
599
|
+
JOIN pic_price_bound_v h ON h.fetched_date <= d.date
|
|
600
|
+
AND h.end_date > d.date;
|
|
601
|
+
|
|
602
|
+
-- One row per tariff slot per covered day: the point in time a price starts
|
|
603
|
+
-- being charged. A flat-rate charger therefore emits a single point per day (at
|
|
604
|
+
-- 00:00 local) and a time-of-use charger one per slot, so panels must plot this
|
|
605
|
+
-- as a step series and carry the last known value into the window — see
|
|
606
|
+
-- test.sql. timestamp is timestamptz: emitting a naive local wall clock made
|
|
607
|
+
-- Grafana read it as UTC and shift every price change by the charger's offset.
|
|
608
|
+
CREATE VIEW pic_price_history_v AS
|
|
609
|
+
SELECT
|
|
610
|
+
((k.date + b.start_time) AT TIME ZONE k.time_zone) AS TIMESTAMP,
|
|
611
|
+
k.pic_chargers_id,
|
|
612
|
+
c.location_url_slug,
|
|
613
|
+
k.last_fetch_uuid,
|
|
614
|
+
concat(c.name, ' - ', c.common_site_name) AS "name",
|
|
615
|
+
c.favourite,
|
|
616
|
+
b.uom,
|
|
617
|
+
b.days,
|
|
618
|
+
b.start_time,
|
|
619
|
+
b.end_time,
|
|
620
|
+
b.vehicle_make_type,
|
|
621
|
+
b.fee_type,
|
|
622
|
+
b.is_member_pricebook,
|
|
623
|
+
b.is_tou,
|
|
624
|
+
b.currency_code AS currency_code_original,
|
|
625
|
+
-- only claim EUR when a converted rate actually exists; rate_base_eur is
|
|
626
|
+
-- unpopulated today, so hardcoding 'EUR' would relabel a foreign-currency
|
|
627
|
+
-- rate as euros the moment a non-EUR site is tracked
|
|
628
|
+
CASE
|
|
629
|
+
WHEN b.rate_base_eur IS NOT NULL THEN 'EUR'
|
|
630
|
+
ELSE b.currency_code
|
|
631
|
+
END AS currency_code,
|
|
632
|
+
COALESCE(b.rate_base_eur, b.rate_base) AS rate_base,
|
|
633
|
+
COALESCE(b.rate_tier_1_eur, b.rate_tier_1) AS rate_tier_1,
|
|
634
|
+
COALESCE(b.rate_tier_2_eur, b.rate_tier_2) AS rate_tier_2
|
|
635
|
+
FROM
|
|
636
|
+
pic_price_history_key_v AS k
|
|
637
|
+
INNER JOIN pic_pricebooks AS b ON b.pic_chargers_id = k.pic_chargers_id
|
|
638
|
+
AND b.last_fetch_uuid = k.last_fetch_uuid
|
|
639
|
+
AND b.is_tou = k.is_tou
|
|
640
|
+
INNER JOIN pic_chargers AS c ON c.id = k.pic_chargers_id
|
|
641
|
+
LEFT JOIN unnest(b.days) AS d ON d = k.day_of_week
|
|
642
|
+
WHERE
|
|
643
|
+
b.vehicle_make_type = 'TSLA' -- todo remove and do it in grafan directly
|
|
644
|
+
AND b.fee_type = 'CHARGING' -- todo remove and do it in grafan directly
|
|
645
|
+
AND (
|
|
646
|
+
(
|
|
647
|
+
b.is_tou = TRUE
|
|
648
|
+
AND d IS NOT NULL
|
|
649
|
+
)
|
|
650
|
+
OR (
|
|
651
|
+
b.is_tou = FALSE
|
|
652
|
+
AND d IS NULL
|
|
653
|
+
)
|
|
654
|
+
);
|