@mxpicture/tesla-superchargers-mate 0.1.24 → 0.1.26
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/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/schema.d.ts +13 -0
- package/dist/store/schema.d.ts.map +1 -0
- package/dist/store/schema.js +27 -0
- package/dist/store/schema.js.map +1 -0
- package/package.json +4 -3
- package/sql/reset.sql +29 -0
- package/sql/setup.sql +462 -0
package/dist/store/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
package/dist/store/index.js
CHANGED
package/dist/store/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply the idempotent `pic_*` schema (CREATE ... IF NOT EXISTS) to the
|
|
3
|
+
* configured database. Safe to re-run on every deploy — existing rows (including
|
|
4
|
+
* the accumulated price history in `pic_pricebooks`) are left untouched. The
|
|
5
|
+
* connection comes from the standard `PG*` environment variables.
|
|
6
|
+
*/
|
|
7
|
+
export declare const applySchema: () => Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* DESTRUCTIVE: drop every `pic_*` table, including the accumulated price history.
|
|
10
|
+
* Intended for a deliberate reset; pair with {@link applySchema} to recreate.
|
|
11
|
+
*/
|
|
12
|
+
export declare const resetSchema: () => Promise<void>;
|
|
13
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,QAAa,OAAO,CAAC,IAAI,CAEhD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,QAAa,OAAO,CAAC,IAAI,CAEhD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { sql } from "./db.js";
|
|
3
|
+
/**
|
|
4
|
+
* Absolute path to a bundled SQL file. The `.sql` files ship inside this package
|
|
5
|
+
* (see its `files` array) at `<pkg>/sql/*.sql`; this module compiles to
|
|
6
|
+
* `<pkg>/dist/store/schema.js`, so they sit two directories up in both `src` and
|
|
7
|
+
* `dist`. Resolving from `import.meta.url` (not `process.cwd()`) makes it work no
|
|
8
|
+
* matter where the CLI is invoked from.
|
|
9
|
+
*/
|
|
10
|
+
const sqlFile = (name) => fileURLToPath(new URL(`../../sql/${name}`, import.meta.url));
|
|
11
|
+
/**
|
|
12
|
+
* Apply the idempotent `pic_*` schema (CREATE ... IF NOT EXISTS) to the
|
|
13
|
+
* configured database. Safe to re-run on every deploy — existing rows (including
|
|
14
|
+
* the accumulated price history in `pic_pricebooks`) are left untouched. The
|
|
15
|
+
* connection comes from the standard `PG*` environment variables.
|
|
16
|
+
*/
|
|
17
|
+
export const applySchema = async () => {
|
|
18
|
+
await sql().file(sqlFile("setup.sql"));
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* DESTRUCTIVE: drop every `pic_*` table, including the accumulated price history.
|
|
22
|
+
* Intended for a deliberate reset; pair with {@link applySchema} to recreate.
|
|
23
|
+
*/
|
|
24
|
+
export const resetSchema = async () => {
|
|
25
|
+
await sql().file(sqlFile("reset.sql"));
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/store/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAE9B;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CACvC,aAAa,CAAC,IAAI,GAAG,CAAC,aAAa,IAAI,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;IACnD,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;IACnD,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC"}
|
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.26",
|
|
4
4
|
"description": "TeslaMate integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "MXPicture",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"sql"
|
|
37
38
|
],
|
|
38
39
|
"engines": {
|
|
39
40
|
"node": ">=24"
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"access": "public"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@mxpicture/tesla-superchargers-generated": "^0.1.
|
|
52
|
+
"@mxpicture/tesla-superchargers-generated": "^0.1.26",
|
|
52
53
|
"postgres": "^3.4.9",
|
|
53
54
|
"short-uuid": "^6.0.3",
|
|
54
55
|
"zod": "^4.3.6"
|
package/sql/reset.sql
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- DESTRUCTIVE reset: drops every pic_* object — INCLUDING pic_pricebooks,
|
|
2
|
+
-- the accumulated price history this project exists to build. Run it only to
|
|
3
|
+
-- start over from scratch; setup.sql alone is idempotent and safe to
|
|
4
|
+
-- (re-)apply after updates.
|
|
5
|
+
DROP VIEW IF EXISTS pic_cp_inv_v;
|
|
6
|
+
|
|
7
|
+
DROP VIEW IF EXISTS pic_cp_sc_v;
|
|
8
|
+
|
|
9
|
+
DROP VIEW IF EXISTS pic_chargers_v;
|
|
10
|
+
|
|
11
|
+
DROP VIEW IF EXISTS pic_pricebooks_tesla_ui_v;
|
|
12
|
+
|
|
13
|
+
DROP VIEW IF EXISTS pic_pricebooks_tesla_condensed_v;
|
|
14
|
+
|
|
15
|
+
DROP VIEW IF EXISTS pic_pricebooks_tesla_v;
|
|
16
|
+
|
|
17
|
+
DROP TABLE IF EXISTS pic_invoices;
|
|
18
|
+
|
|
19
|
+
DROP TABLE IF EXISTS pic_invoice_missing_states;
|
|
20
|
+
|
|
21
|
+
DROP TABLE IF EXISTS pic_invoice_files;
|
|
22
|
+
|
|
23
|
+
DROP TABLE IF EXISTS pic_pricebooks;
|
|
24
|
+
|
|
25
|
+
DROP TABLE IF EXISTS pic_chargers;
|
|
26
|
+
|
|
27
|
+
DROP TABLE IF EXISTS pic_slugs;
|
|
28
|
+
|
|
29
|
+
DROP TABLE IF EXISTS pic_charging_process_states;
|
package/sql/setup.sql
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
-- Idempotent schema setup: creates the pic_* tables next to TeslaMate's own.
|
|
2
|
+
-- Safe to (re-)apply — existing tables (and the price history in
|
|
3
|
+
-- pic_pricebooks) are left untouched. To start over, run reset.sql first
|
|
4
|
+
-- (DESTRUCTIVE: drops the accumulated history).
|
|
5
|
+
-- slugs ------------------------------------------------------------------------------
|
|
6
|
+
CREATE TABLE IF NOT EXISTS pic_slugs (
|
|
7
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
8
|
+
fetched_at TIMESTAMPTZ NOT NULL,
|
|
9
|
+
last_fetch_uuid TEXT NOT NULL,
|
|
10
|
+
failure_count SMALLINT NOT NULL,
|
|
11
|
+
--
|
|
12
|
+
latitude DOUBLE PRECISION NOT NULL, -- number
|
|
13
|
+
longitude DOUBLE PRECISION NOT NULL, -- number
|
|
14
|
+
location_types TEXT[] NOT NULL DEFAULT '{}',
|
|
15
|
+
location_url_slug TEXT NOT NULL, -- string
|
|
16
|
+
tesla_uuid TEXT UNIQUE, -- string (uuid), semantic key for syncs
|
|
17
|
+
title TEXT, -- string|null
|
|
18
|
+
access_type TEXT, -- string (supercharger_function.access_type)
|
|
19
|
+
actual_latitude TEXT, -- string (supercharger_function.actual_latitude)
|
|
20
|
+
actual_longitude TEXT, -- string (supercharger_function.actual_longitude)
|
|
21
|
+
charging_accessibility TEXT, -- string (supercharger_function.charging_accessibility)
|
|
22
|
+
coming_soon_latitude TEXT, -- string (supercharger_function.coming_soon_latitude)
|
|
23
|
+
coming_soon_longitude TEXT, -- string (supercharger_function.coming_soon_longitude)
|
|
24
|
+
coming_soon_name TEXT, -- string (supercharger_function.coming_soon_name)
|
|
25
|
+
open_to_non_tesla BOOLEAN, -- boolean (supercharger_function.open_to_non_tesla)
|
|
26
|
+
project_status TEXT, -- string (supercharger_function.project_status)
|
|
27
|
+
show_on_find_us TEXT, -- string (supercharger_function.show_on_find_us)
|
|
28
|
+
site_status TEXT, -- string (supercharger_function.site_status)
|
|
29
|
+
vote_winner_quarter TEXT, -- string (supercharger_function.vote_winner_quarter)
|
|
30
|
+
failure_notified_at TIMESTAMPTZ -- set when a failure notification covered this row; NULL = not yet notified
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX IF NOT EXISTS pic_slugs_location_types_gin ON pic_slugs USING gin (location_types);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS pic_slugs_location_slug_idx ON pic_slugs (location_url_slug);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX IF NOT EXISTS pic_slugs_fetched_at_idx ON pic_slugs (fetched_at);
|
|
38
|
+
|
|
39
|
+
CREATE INDEX IF NOT EXISTS pic_slugs_locslug_time_idx ON pic_slugs (location_url_slug, fetched_at DESC);
|
|
40
|
+
|
|
41
|
+
-- chargers ------------------------------------------------------------------------------
|
|
42
|
+
CREATE TABLE IF NOT EXISTS pic_chargers (
|
|
43
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
44
|
+
fetched_at TIMESTAMPTZ NOT NULL,
|
|
45
|
+
last_fetch_uuid TEXT NOT NULL,
|
|
46
|
+
deleted_at TIMESTAMPTZ,
|
|
47
|
+
failure_count SMALLINT NOT NULL,
|
|
48
|
+
pic_slugs_id BIGINT REFERENCES pic_slugs (id) ON DELETE SET NULL,
|
|
49
|
+
--
|
|
50
|
+
access_type TEXT NOT NULL, -- string (accessType)
|
|
51
|
+
location_guid TEXT NOT NULL UNIQUE, -- string (locationGUID), semantic key for syncs
|
|
52
|
+
max_power_kw SMALLINT NOT NULL, -- number (maxPowerKw)
|
|
53
|
+
name TEXT NOT NULL, -- string
|
|
54
|
+
public_stall_count SMALLINT NOT NULL, -- number
|
|
55
|
+
time_zone TEXT NOT NULL, -- string
|
|
56
|
+
entry_point_latitude DOUBLE PRECISION NOT NULL, -- number (entryPoint.latitude)
|
|
57
|
+
entry_point_longitude DOUBLE PRECISION NOT NULL, -- number (entryPoint.longitude)
|
|
58
|
+
centroid_latitude DOUBLE PRECISION NOT NULL, -- number (centroid.latitude)
|
|
59
|
+
centroid_longitude DOUBLE PRECISION NOT NULL, -- number (centroid.longitude)
|
|
60
|
+
brand_name TEXT, -- string|null (brandName)
|
|
61
|
+
common_site_name TEXT NOT NULL, -- string (commonSiteName)
|
|
62
|
+
city TEXT NOT NULL, -- string
|
|
63
|
+
country TEXT NOT NULL, -- string
|
|
64
|
+
country_code TEXT NOT NULL, -- string (countryCode)
|
|
65
|
+
district TEXT, -- string|null
|
|
66
|
+
nav_region TEXT NOT NULL, -- string (navRegion)
|
|
67
|
+
nearest_entrance TEXT, -- string|null (nearestEntrance)
|
|
68
|
+
postal_code TEXT NOT NULL, -- string (postalCode)
|
|
69
|
+
postal_ext TEXT, -- string|null (postalExt)
|
|
70
|
+
state TEXT, -- string|null
|
|
71
|
+
street TEXT NOT NULL, -- string
|
|
72
|
+
street_number TEXT NOT NULL, -- string (streetNumber)
|
|
73
|
+
site_id BIGINT NOT NULL, -- number (availabilityProfile.siteId.id)
|
|
74
|
+
site_type TEXT NOT NULL, -- string (availabilityProfile.siteId.siteType)
|
|
75
|
+
trt_id BIGINT NOT NULL, -- number (availabilityProfile.trtId.id)
|
|
76
|
+
trt_type TEXT NOT NULL, -- string (availabilityProfile.trtId.siteType)
|
|
77
|
+
utc_offset TEXT, -- string|null (availabilityProfile.utcOffset)
|
|
78
|
+
failure_notified_at TIMESTAMPTZ -- set when a failure notification covered this row; NULL = not yet notified
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
CREATE INDEX IF NOT EXISTS pic_chargers_location_slug_idx ON pic_chargers (pic_slugs_id);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS pic_chargers_fetched_at_idx ON pic_chargers (fetched_at);
|
|
84
|
+
|
|
85
|
+
CREATE INDEX IF NOT EXISTS pic_chargers_locslug_time_idx ON pic_chargers (pic_slugs_id, fetched_at DESC);
|
|
86
|
+
|
|
87
|
+
CREATE OR REPLACE VIEW pic_chargers_v AS
|
|
88
|
+
SELECT
|
|
89
|
+
*
|
|
90
|
+
FROM
|
|
91
|
+
pic_chargers
|
|
92
|
+
WHERE
|
|
93
|
+
deleted_at IS NULL;
|
|
94
|
+
|
|
95
|
+
-- pricebooks ------------------------------------------------------------------------------
|
|
96
|
+
-- History table: one snapshot ("group") = all rows sharing
|
|
97
|
+
-- (pic_chargers_id, last_fetch_uuid). fetched_at is the group's creation time
|
|
98
|
+
-- and never changes; an unchanged re-fetch only bumps last_fetch_uuid.
|
|
99
|
+
CREATE TABLE IF NOT EXISTS pic_pricebooks (
|
|
100
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
101
|
+
fetched_at TIMESTAMPTZ NOT NULL,
|
|
102
|
+
last_fetch_uuid TEXT NOT NULL,
|
|
103
|
+
pic_chargers_id BIGINT NOT NULL REFERENCES pic_chargers (id) ON DELETE CASCADE,
|
|
104
|
+
--
|
|
105
|
+
currency_code TEXT NOT NULL, -- string (currencyCode)
|
|
106
|
+
uom TEXT NOT NULL, -- string (ucom)
|
|
107
|
+
days SMALLINT[] NOT NULL, -- string [0,1,...]
|
|
108
|
+
start_time TIME NOT NULL, -- time (startTime)
|
|
109
|
+
end_time TIME NOT NULL, -- time (endTime)
|
|
110
|
+
vehicle_make_type TEXT NOT NULL, -- string (vehicleMakeType)
|
|
111
|
+
fee_type TEXT NOT NULL, -- string (feeType)
|
|
112
|
+
is_member_pricebook BOOLEAN NOT NULL, -- boolean (isMemberPricebook)
|
|
113
|
+
is_tou BOOLEAN NOT NULL, -- boolean (isTou)
|
|
114
|
+
rate_base NUMERIC(5, 2) NOT NULL, -- number (rateBase)
|
|
115
|
+
rate_tier_1 NUMERIC(5, 2) NOT NULL, -- number (rateTier1)
|
|
116
|
+
rate_tier_2 NUMERIC(5, 2) NOT NULL, -- number (rateTier2)
|
|
117
|
+
-- derived: 0-based dense rank within the (pic_chargers_id, last_fetch_uuid)
|
|
118
|
+
-- group, ordered by start_time (rows sharing a start_time share the value);
|
|
119
|
+
-- NULL when is_tou = false
|
|
120
|
+
sort_order SMALLINT
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
CREATE INDEX IF NOT EXISTS pic_pricebooks_loc_idx ON pic_pricebooks (pic_chargers_id);
|
|
124
|
+
|
|
125
|
+
-- group lookups: current group / group at a point in time
|
|
126
|
+
CREATE INDEX IF NOT EXISTS pic_pricebooks_charger_time_idx ON pic_pricebooks (pic_chargers_id, fetched_at DESC);
|
|
127
|
+
|
|
128
|
+
CREATE OR REPLACE VIEW pic_pricebooks_tesla_v AS
|
|
129
|
+
SELECT
|
|
130
|
+
*
|
|
131
|
+
FROM
|
|
132
|
+
pic_pricebooks AS p
|
|
133
|
+
WHERE
|
|
134
|
+
p.vehicle_make_type = 'TSLA'
|
|
135
|
+
AND p.fee_type = 'CHARGING'
|
|
136
|
+
ORDER BY
|
|
137
|
+
p.pic_chargers_id,
|
|
138
|
+
p.is_tou,
|
|
139
|
+
p.start_time;
|
|
140
|
+
|
|
141
|
+
-- // todo add currency code for ui/grafana
|
|
142
|
+
CREATE OR REPLACE VIEW pic_pricebooks_tesla_condensed_v AS
|
|
143
|
+
SELECT
|
|
144
|
+
c.*,
|
|
145
|
+
base.start_time AS base_start_time,
|
|
146
|
+
base.end_time AS base_end_time,
|
|
147
|
+
base.rate_base AS base_rate_base,
|
|
148
|
+
base.uom AS base_uom,
|
|
149
|
+
s0.start_time AS s0_start_time,
|
|
150
|
+
s0.end_time AS s0_end_time,
|
|
151
|
+
s0.rate_base AS s0_rate_base,
|
|
152
|
+
s0.uom AS s0_uom,
|
|
153
|
+
s1.start_time AS s1_start_time,
|
|
154
|
+
s1.end_time AS s1_end_time,
|
|
155
|
+
s1.rate_base AS s1_rate_base,
|
|
156
|
+
s1.uom AS s1_uom,
|
|
157
|
+
s2.start_time AS s2_start_time,
|
|
158
|
+
s2.end_time AS s2_end_time,
|
|
159
|
+
s2.rate_base AS s2_rate_base,
|
|
160
|
+
s2.uom AS s2_uom,
|
|
161
|
+
s3.start_time AS s3_start_time,
|
|
162
|
+
s3.end_time AS s3_end_time,
|
|
163
|
+
s3.rate_base AS s3_rate_base,
|
|
164
|
+
s3.uom AS s3_uom,
|
|
165
|
+
s4.start_time AS s4_start_time,
|
|
166
|
+
s4.end_time AS s4_end_time,
|
|
167
|
+
s4.rate_base AS s4_rate_base,
|
|
168
|
+
s4.uom AS s4_uom,
|
|
169
|
+
s5.start_time AS s5_start_time,
|
|
170
|
+
s5.end_time AS s5_end_time,
|
|
171
|
+
s5.rate_base AS s5_rate_base,
|
|
172
|
+
s5.uom AS s5_uom
|
|
173
|
+
FROM
|
|
174
|
+
pic_chargers_v AS c
|
|
175
|
+
LEFT JOIN pic_pricebooks_tesla_v base ON base.pic_chargers_id = c.id
|
|
176
|
+
AND base.last_fetch_uuid = c.last_fetch_uuid
|
|
177
|
+
AND base.sort_order IS NULL
|
|
178
|
+
LEFT JOIN pic_pricebooks_tesla_v s0 ON s0.pic_chargers_id = c.id
|
|
179
|
+
AND s0.last_fetch_uuid = c.last_fetch_uuid
|
|
180
|
+
AND s0.sort_order = 0
|
|
181
|
+
LEFT JOIN pic_pricebooks_tesla_v s1 ON s1.pic_chargers_id = c.id
|
|
182
|
+
AND s1.last_fetch_uuid = c.last_fetch_uuid
|
|
183
|
+
AND s1.sort_order = 1
|
|
184
|
+
LEFT JOIN pic_pricebooks_tesla_v s2 ON s2.pic_chargers_id = c.id
|
|
185
|
+
AND s2.last_fetch_uuid = c.last_fetch_uuid
|
|
186
|
+
AND s2.sort_order = 2
|
|
187
|
+
LEFT JOIN pic_pricebooks_tesla_v s3 ON s3.pic_chargers_id = c.id
|
|
188
|
+
AND s3.last_fetch_uuid = c.last_fetch_uuid
|
|
189
|
+
AND s3.sort_order = 3
|
|
190
|
+
LEFT JOIN pic_pricebooks_tesla_v s4 ON s4.pic_chargers_id = c.id
|
|
191
|
+
AND s4.last_fetch_uuid = c.last_fetch_uuid
|
|
192
|
+
AND s4.sort_order = 4
|
|
193
|
+
LEFT JOIN pic_pricebooks_tesla_v s5 ON s5.pic_chargers_id = c.id
|
|
194
|
+
AND s5.last_fetch_uuid = c.last_fetch_uuid
|
|
195
|
+
AND s5.sort_order = 5;
|
|
196
|
+
|
|
197
|
+
CREATE OR REPLACE VIEW pic_pricebooks_tesla_ui_v AS
|
|
198
|
+
SELECT
|
|
199
|
+
v.*,
|
|
200
|
+
CASE
|
|
201
|
+
WHEN v.s0_start_time IS NULL THEN CONCAT(v.base_rate_base, ' €/', v.base_uom)
|
|
202
|
+
END AS base_timeslot,
|
|
203
|
+
CASE
|
|
204
|
+
WHEN v.s0_start_time IS NOT NULL THEN CONCAT(
|
|
205
|
+
to_char(v.s0_start_time, 'HH24:mm'),
|
|
206
|
+
'-',
|
|
207
|
+
to_char(v.s0_end_time, 'HH24:mm'),
|
|
208
|
+
': ',
|
|
209
|
+
v.s0_rate_base,
|
|
210
|
+
' €/',
|
|
211
|
+
v.s0_uom
|
|
212
|
+
)
|
|
213
|
+
END AS s0_timeslot,
|
|
214
|
+
CASE
|
|
215
|
+
WHEN v.s1_start_time IS NOT NULL THEN CONCAT(
|
|
216
|
+
to_char(v.s1_start_time, 'HH24:mm'),
|
|
217
|
+
'-',
|
|
218
|
+
to_char(v.s1_end_time, 'HH24:mm'),
|
|
219
|
+
': ',
|
|
220
|
+
v.s1_rate_base,
|
|
221
|
+
' €/',
|
|
222
|
+
v.s1_uom
|
|
223
|
+
)
|
|
224
|
+
END AS s1_timeslot,
|
|
225
|
+
CASE
|
|
226
|
+
WHEN v.s2_start_time IS NOT NULL THEN CONCAT(
|
|
227
|
+
to_char(v.s2_start_time, 'HH24:mm'),
|
|
228
|
+
'-',
|
|
229
|
+
to_char(v.s2_end_time, 'HH24:mm'),
|
|
230
|
+
': ',
|
|
231
|
+
v.s2_rate_base,
|
|
232
|
+
' €/',
|
|
233
|
+
v.s2_uom
|
|
234
|
+
)
|
|
235
|
+
END AS s2_timeslot,
|
|
236
|
+
CASE
|
|
237
|
+
WHEN v.s3_start_time IS NOT NULL THEN CONCAT(
|
|
238
|
+
to_char(v.s3_start_time, 'HH24:mm'),
|
|
239
|
+
'-',
|
|
240
|
+
to_char(v.s3_end_time, 'HH24:mm'),
|
|
241
|
+
': ',
|
|
242
|
+
v.s3_rate_base,
|
|
243
|
+
' €/',
|
|
244
|
+
v.s3_uom
|
|
245
|
+
)
|
|
246
|
+
END AS s3_timeslot,
|
|
247
|
+
CASE
|
|
248
|
+
WHEN v.s4_start_time IS NOT NULL THEN CONCAT(
|
|
249
|
+
to_char(v.s4_start_time, 'HH24:mm'),
|
|
250
|
+
'-',
|
|
251
|
+
to_char(v.s4_end_time, 'HH24:mm'),
|
|
252
|
+
': ',
|
|
253
|
+
v.s4_rate_base,
|
|
254
|
+
' €/',
|
|
255
|
+
v.s4_uom
|
|
256
|
+
)
|
|
257
|
+
END AS s4_timeslot,
|
|
258
|
+
CASE
|
|
259
|
+
WHEN v.s5_start_time IS NOT NULL THEN CONCAT(
|
|
260
|
+
to_char(v.s5_start_time, 'HH24:mm'),
|
|
261
|
+
'-',
|
|
262
|
+
to_char(v.s5_end_time, 'HH24:mm'),
|
|
263
|
+
': ',
|
|
264
|
+
v.s5_rate_base,
|
|
265
|
+
' €/',
|
|
266
|
+
v.s5_uom
|
|
267
|
+
)
|
|
268
|
+
END AS s5_timeslot
|
|
269
|
+
FROM
|
|
270
|
+
pic_pricebooks_tesla_condensed_v AS v;
|
|
271
|
+
|
|
272
|
+
-- charging process states ------------------------------------------------------------------------------
|
|
273
|
+
CREATE TABLE IF NOT EXISTS pic_charging_process_states (
|
|
274
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
275
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
276
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
277
|
+
--
|
|
278
|
+
charging_processes_id INTEGER NOT NULL, -- no foreign key --> teslamate should not fail because of this extension
|
|
279
|
+
failure_count SMALLINT NOT NULL,
|
|
280
|
+
failure_notified_at TIMESTAMPTZ -- set when a failure notification covered this row; NULL = not yet notified
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
-- the failure upsert conflicts on charging_processes_id, so the index must be
|
|
284
|
+
-- unique; the old non-unique pic_charg_proc_idx is dropped on upgrade
|
|
285
|
+
DROP INDEX IF EXISTS pic_charg_proc_idx;
|
|
286
|
+
|
|
287
|
+
CREATE UNIQUE INDEX IF NOT EXISTS pic_charg_proc_uq_idx ON pic_charging_process_states (charging_processes_id);
|
|
288
|
+
|
|
289
|
+
-- invoices (from Tesla App) ------------------------------------------------------------------------------
|
|
290
|
+
CREATE TABLE IF NOT EXISTS pic_invoices (
|
|
291
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
292
|
+
fetched_at TIMESTAMPTZ NOT NULL,
|
|
293
|
+
last_fetch_uuid TEXT NOT NULL,
|
|
294
|
+
charging_processes_id INTEGER, -- no foreign key --> teslamate should not fail because of this extension
|
|
295
|
+
--
|
|
296
|
+
charge_start_date_time TIMESTAMPTZ NOT NULL,
|
|
297
|
+
name TEXT,
|
|
298
|
+
vin TEXT NOT NULL,
|
|
299
|
+
model TEXT,
|
|
300
|
+
country TEXT NOT NULL,
|
|
301
|
+
site_location_name TEXT NOT NULL,
|
|
302
|
+
description TEXT,
|
|
303
|
+
ucom TEXT NOT NULL,
|
|
304
|
+
quantity_base NUMERIC(8, 4) NOT NULL,
|
|
305
|
+
quantity_tier1 NUMERIC(8, 4),
|
|
306
|
+
quantity_tier2 NUMERIC(8, 4),
|
|
307
|
+
quantity_tier3 NUMERIC(8, 4),
|
|
308
|
+
quantity_tier4 NUMERIC(8, 4),
|
|
309
|
+
invoice_number TEXT NOT NULL,
|
|
310
|
+
unit_cost_base NUMERIC(4, 2) NOT NULL,
|
|
311
|
+
unit_cost_tier1 NUMERIC(4, 2),
|
|
312
|
+
unit_cost_tier2 NUMERIC(4, 2),
|
|
313
|
+
unit_cost_tier3 NUMERIC(4, 2),
|
|
314
|
+
unit_cost_tier4 NUMERIC(4, 2),
|
|
315
|
+
vat NUMERIC(5, 2) NOT NULL,
|
|
316
|
+
total_exc_vat NUMERIC(5, 2) NOT NULL,
|
|
317
|
+
total_inc_vat NUMERIC(5, 2) NOT NULL,
|
|
318
|
+
status TEXT,
|
|
319
|
+
invoice TEXT
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
CREATE UNIQUE INDEX IF NOT EXISTS pic_invoices_uq_cp_idx ON pic_invoices (charging_processes_id);
|
|
323
|
+
|
|
324
|
+
CREATE UNIQUE INDEX IF NOT EXISTS pic_invoices_uq_inv_idx ON pic_invoices (invoice_number);
|
|
325
|
+
|
|
326
|
+
-- invoice files (from Tesla App) ------------------------------------------------------------------------------
|
|
327
|
+
CREATE TABLE IF NOT EXISTS pic_invoice_files (
|
|
328
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
329
|
+
fetched_at TIMESTAMPTZ NOT NULL,
|
|
330
|
+
last_fetch_uuid TEXT NOT NULL,
|
|
331
|
+
--
|
|
332
|
+
name TEXT NOT NULL
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
CREATE UNIQUE INDEX IF NOT EXISTS pic_invoices_name_idx ON pic_invoice_files (name);
|
|
336
|
+
|
|
337
|
+
-- invoice missing states ------------------------------------------------------------------------------
|
|
338
|
+
-- One row per Supercharger charge already paged about a missing invoice
|
|
339
|
+
-- (notify-unmatched-invoices), so each charge pages exactly once. A row is
|
|
340
|
+
-- irrelevant once an invoice arrives (the charge stops matching the query).
|
|
341
|
+
CREATE TABLE IF NOT EXISTS pic_invoice_missing_states (
|
|
342
|
+
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
343
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
344
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
345
|
+
--
|
|
346
|
+
charging_processes_id INTEGER NOT NULL, -- no foreign key --> teslamate should not fail because of this extension
|
|
347
|
+
notified_at TIMESTAMPTZ -- set when a missing-invoice notification covered this row; NULL = not yet notified
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
-- the notified upsert conflicts on charging_processes_id, so the index must be unique
|
|
351
|
+
CREATE UNIQUE INDEX IF NOT EXISTS pic_invoice_missing_uq_idx ON pic_invoice_missing_states (charging_processes_id);
|
|
352
|
+
|
|
353
|
+
-- charging processes <-> pic_invoices ---------------------------------------------------------
|
|
354
|
+
-- Dropped instead of CREATE OR REPLACE: column types/order may change between
|
|
355
|
+
-- versions, which OR REPLACE rejects. Views hold no data, so this stays
|
|
356
|
+
-- idempotent. pic_cp_inv_v depends on pic_cp_sc_v, so it is dropped first.
|
|
357
|
+
DROP VIEW IF EXISTS pic_cp_inv_v;
|
|
358
|
+
|
|
359
|
+
DROP VIEW IF EXISTS pic_cp_inv_prep_v;
|
|
360
|
+
|
|
361
|
+
DROP VIEW IF EXISTS pic_cp_sc_v;
|
|
362
|
+
|
|
363
|
+
-- Supercharger charging processes: a process counts as Supercharger when any
|
|
364
|
+
-- of its charges rows reports fast_charger_brand = 'Tesla' (AC charging
|
|
365
|
+
-- reports '<invalid>'). Site name comes from the reverse-geocoded address.
|
|
366
|
+
CREATE VIEW pic_cp_sc_v AS
|
|
367
|
+
SELECT
|
|
368
|
+
cp.id,
|
|
369
|
+
cp.start_date AT TIME ZONE 'UTC' AS charge_start_date_time,
|
|
370
|
+
COALESCE(addr.name, addr.display_name) AS site_location_name,
|
|
371
|
+
COALESCE(cp.charge_energy_used, cp.charge_energy_added) AS quantity,
|
|
372
|
+
cp.cost
|
|
373
|
+
FROM
|
|
374
|
+
charging_processes AS cp
|
|
375
|
+
LEFT JOIN addresses AS addr ON addr.id = cp.address_id
|
|
376
|
+
WHERE
|
|
377
|
+
EXISTS (
|
|
378
|
+
SELECT
|
|
379
|
+
1
|
|
380
|
+
FROM
|
|
381
|
+
charges AS c
|
|
382
|
+
WHERE
|
|
383
|
+
c.charging_process_id = cp.id
|
|
384
|
+
AND c.fast_charger_brand = 'Tesla'
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
-- Full outer join: matched rows plus Supercharger charges without an invoice
|
|
388
|
+
-- (match_state = 'cp_only') and invoices without a matched charge
|
|
389
|
+
-- ('inv_only'). Values missing because one side is absent are 0, so sums stay
|
|
390
|
+
-- usable; deviations are 0 where undefined.
|
|
391
|
+
CREATE VIEW pic_cp_inv_prep_v AS
|
|
392
|
+
SELECT
|
|
393
|
+
COALESCE(
|
|
394
|
+
inv.charge_start_date_time,
|
|
395
|
+
cp.charge_start_date_time
|
|
396
|
+
) AS charge_start_date_time,
|
|
397
|
+
--
|
|
398
|
+
COALESCE(
|
|
399
|
+
inv.site_location_name,
|
|
400
|
+
cp.site_location_name,
|
|
401
|
+
'Unknown'
|
|
402
|
+
) AS site_location_name,
|
|
403
|
+
--
|
|
404
|
+
CASE
|
|
405
|
+
WHEN inv.id IS NULL THEN 'cp_only'
|
|
406
|
+
WHEN cp.id IS NULL THEN 'inv_only'
|
|
407
|
+
ELSE 'matched'
|
|
408
|
+
END AS match_state,
|
|
409
|
+
--
|
|
410
|
+
inv.quantity_base AS inv_quan_raw,
|
|
411
|
+
--
|
|
412
|
+
ROUND(COALESCE(inv.quantity_base, 0), 2) AS inv_quan,
|
|
413
|
+
--
|
|
414
|
+
cp.quantity AS cp_quan_raw,
|
|
415
|
+
--
|
|
416
|
+
COALESCE(cp.quantity, 0) AS cp_quan,
|
|
417
|
+
--
|
|
418
|
+
COALESCE(
|
|
419
|
+
ROUND(
|
|
420
|
+
(cp.quantity / NULLIF(inv.quantity_base, 0)) - 1,
|
|
421
|
+
4
|
|
422
|
+
),
|
|
423
|
+
0
|
|
424
|
+
) AS deviation_quan,
|
|
425
|
+
--
|
|
426
|
+
CASE
|
|
427
|
+
WHEN inv.quantity_base IS NULL
|
|
428
|
+
OR cp.quantity IS NULL THEN 0
|
|
429
|
+
ELSE ROUND(inv.quantity_base - cp.quantity, 2)
|
|
430
|
+
END AS diff_quan,
|
|
431
|
+
--
|
|
432
|
+
inv.total_inc_vat AS inv_cost_raw,
|
|
433
|
+
--
|
|
434
|
+
ROUND(COALESCE(inv.total_inc_vat, 0), 2) AS inv_cost,
|
|
435
|
+
--
|
|
436
|
+
cp.cost AS cp_cost_raw,
|
|
437
|
+
--
|
|
438
|
+
COALESCE(cp.cost, 0) AS cp_cost,
|
|
439
|
+
--
|
|
440
|
+
COALESCE(
|
|
441
|
+
ROUND((cp.cost / NULLIF(inv.total_inc_vat, 0)) - 1, 4),
|
|
442
|
+
0
|
|
443
|
+
) AS deviation_cost,
|
|
444
|
+
--
|
|
445
|
+
CASE
|
|
446
|
+
WHEN inv.total_inc_vat IS NULL
|
|
447
|
+
OR cp.cost IS NULL THEN 0
|
|
448
|
+
ELSE ROUND(inv.total_inc_vat - cp.cost, 2)
|
|
449
|
+
END AS diff_cost
|
|
450
|
+
FROM
|
|
451
|
+
pic_cp_sc_v AS cp
|
|
452
|
+
FULL OUTER JOIN pic_invoices AS inv ON inv.charging_processes_id = cp.id;
|
|
453
|
+
|
|
454
|
+
--
|
|
455
|
+
CREATE VIEW pic_cp_inv_v AS
|
|
456
|
+
SELECT
|
|
457
|
+
base.*,
|
|
458
|
+
to_char(base.charge_start_date_time, 'YYYY-MM-DD hh:MM') AS formatted_date,
|
|
459
|
+
base.inv_quan || ' kWh (' || base.inv_cost_raw || ' €)' AS inv,
|
|
460
|
+
base.cp_quan || ' kWh (' || base.cp_cost_raw || ' €)' AS cp
|
|
461
|
+
FROM
|
|
462
|
+
pic_cp_inv_prep_v AS base;
|