@mxpicture/tesla-superchargers-mate 0.1.56 → 0.1.58
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/db/schema.sql +127 -0
- package/package.json +2 -2
- package/sql/reset.sql +9 -0
- package/sql/test/test.sql +26 -0
- package/sql/views-drop.sql +14 -0
- package/sql/views.sql +320 -19
- package/sql/test/drive_position3.sql +0 -243
package/db/schema.sql
CHANGED
|
@@ -2386,6 +2386,133 @@ CREATE VIEW public.pic_refs_base_v AS
|
|
|
2386
2386
|
FROM row_no;
|
|
2387
2387
|
|
|
2388
2388
|
|
|
2389
|
+
--
|
|
2390
|
+
-- Name: pic_refs_hvac_v; Type: VIEW; Schema: public; Owner: -
|
|
2391
|
+
--
|
|
2392
|
+
|
|
2393
|
+
CREATE VIEW public.pic_refs_hvac_v AS
|
|
2394
|
+
WITH samples AS (
|
|
2395
|
+
SELECT r.car_id,
|
|
2396
|
+
r.ref_type,
|
|
2397
|
+
r.ref_id,
|
|
2398
|
+
r.start_date AS ref_start_date,
|
|
2399
|
+
r.end_date AS ref_end_date,
|
|
2400
|
+
p.id AS pos_id,
|
|
2401
|
+
p.date,
|
|
2402
|
+
p.is_climate_on,
|
|
2403
|
+
p.is_rear_defroster_on,
|
|
2404
|
+
p.is_front_defroster_on,
|
|
2405
|
+
p.battery_heater
|
|
2406
|
+
FROM (public.pic_refs r
|
|
2407
|
+
JOIN public.positions p ON (((p.car_id = r.car_id) AND (p.drive_id = r.ref_id) AND (p.date >= r.start_date) AND (p.date <= r.end_date) AND (p.is_climate_on IS NOT NULL) AND (p.is_rear_defroster_on IS NOT NULL) AND (p.is_front_defroster_on IS NOT NULL) AND (p.battery_heater IS NOT NULL))))
|
|
2408
|
+
WHERE ((r.ref_type = 'drive'::public.pic_ref_type) AND (r.ref_id > 0) AND (COALESCE(r.prev_id, 0) >= 0))
|
|
2409
|
+
UNION ALL
|
|
2410
|
+
SELECT r.car_id,
|
|
2411
|
+
r.ref_type,
|
|
2412
|
+
r.ref_id,
|
|
2413
|
+
r.start_date AS ref_start_date,
|
|
2414
|
+
r.end_date AS ref_end_date,
|
|
2415
|
+
p.id AS pos_id,
|
|
2416
|
+
p.date,
|
|
2417
|
+
p.is_climate_on,
|
|
2418
|
+
p.is_rear_defroster_on,
|
|
2419
|
+
p.is_front_defroster_on,
|
|
2420
|
+
p.battery_heater
|
|
2421
|
+
FROM (public.pic_refs r
|
|
2422
|
+
JOIN public.positions p ON (((p.car_id = r.car_id) AND (p.drive_id IS NULL) AND (p.date >= r.start_date) AND (p.date <= r.end_date) AND (p.is_climate_on IS NOT NULL) AND (p.is_rear_defroster_on IS NOT NULL) AND (p.is_front_defroster_on IS NOT NULL) AND (p.battery_heater IS NOT NULL))))
|
|
2423
|
+
WHERE ((r.ref_type = ANY (ARRAY['idle'::public.pic_ref_type, 'charge'::public.pic_ref_type])) AND (r.ref_id > 0) AND (COALESCE(r.prev_id, 0) >= 0))
|
|
2424
|
+
), spans AS (
|
|
2425
|
+
SELECT s_1.car_id,
|
|
2426
|
+
s_1.ref_type,
|
|
2427
|
+
s_1.ref_id,
|
|
2428
|
+
s_1.ref_start_date,
|
|
2429
|
+
s_1.ref_end_date,
|
|
2430
|
+
s_1.pos_id,
|
|
2431
|
+
s_1.date,
|
|
2432
|
+
s_1.is_climate_on,
|
|
2433
|
+
s_1.is_rear_defroster_on,
|
|
2434
|
+
s_1.is_front_defroster_on,
|
|
2435
|
+
s_1.battery_heater,
|
|
2436
|
+
EXTRACT(epoch FROM (COALESCE(lead(s_1.date) OVER w, s_1.ref_end_date) -
|
|
2437
|
+
CASE
|
|
2438
|
+
WHEN (row_number() OVER w = 1) THEN s_1.ref_start_date
|
|
2439
|
+
ELSE s_1.date
|
|
2440
|
+
END)) AS duration_sec
|
|
2441
|
+
FROM samples s_1
|
|
2442
|
+
WINDOW w AS (PARTITION BY s_1.car_id, s_1.ref_type, s_1.ref_id ORDER BY s_1.date, s_1.pos_id)
|
|
2443
|
+
)
|
|
2444
|
+
SELECT car_id,
|
|
2445
|
+
ref_type,
|
|
2446
|
+
ref_id,
|
|
2447
|
+
ref_start_date,
|
|
2448
|
+
ref_end_date,
|
|
2449
|
+
EXTRACT(epoch FROM (ref_end_date - ref_start_date)) AS ref_duration_sec,
|
|
2450
|
+
sum(
|
|
2451
|
+
CASE
|
|
2452
|
+
WHEN is_climate_on THEN duration_sec
|
|
2453
|
+
ELSE (0)::numeric
|
|
2454
|
+
END) AS climate_on_sec,
|
|
2455
|
+
sum(
|
|
2456
|
+
CASE
|
|
2457
|
+
WHEN is_rear_defroster_on THEN duration_sec
|
|
2458
|
+
ELSE (0)::numeric
|
|
2459
|
+
END) AS rear_defroster_sec,
|
|
2460
|
+
sum(
|
|
2461
|
+
CASE
|
|
2462
|
+
WHEN is_front_defroster_on THEN duration_sec
|
|
2463
|
+
ELSE (0)::numeric
|
|
2464
|
+
END) AS front_defroster_sec,
|
|
2465
|
+
sum(
|
|
2466
|
+
CASE
|
|
2467
|
+
WHEN battery_heater THEN duration_sec
|
|
2468
|
+
ELSE (0)::numeric
|
|
2469
|
+
END) AS battery_heater_sec,
|
|
2470
|
+
sum(duration_sec) AS pos_total_sec,
|
|
2471
|
+
count(*) AS pos_count,
|
|
2472
|
+
min(date) AS pos_first_date,
|
|
2473
|
+
max(duration_sec) AS max_gap_sec
|
|
2474
|
+
FROM spans s
|
|
2475
|
+
GROUP BY car_id, ref_type, ref_id, ref_start_date, ref_end_date;
|
|
2476
|
+
|
|
2477
|
+
|
|
2478
|
+
--
|
|
2479
|
+
-- Name: pic_refs_hvac_ratio_v; Type: VIEW; Schema: public; Owner: -
|
|
2480
|
+
--
|
|
2481
|
+
|
|
2482
|
+
CREATE VIEW public.pic_refs_hvac_ratio_v AS
|
|
2483
|
+
SELECT car_id,
|
|
2484
|
+
ref_type,
|
|
2485
|
+
ref_id,
|
|
2486
|
+
ref_start_date,
|
|
2487
|
+
ref_end_date,
|
|
2488
|
+
ref_duration_sec,
|
|
2489
|
+
climate_on_sec,
|
|
2490
|
+
rear_defroster_sec,
|
|
2491
|
+
front_defroster_sec,
|
|
2492
|
+
battery_heater_sec,
|
|
2493
|
+
pos_total_sec,
|
|
2494
|
+
pos_count,
|
|
2495
|
+
pos_first_date,
|
|
2496
|
+
max_gap_sec,
|
|
2497
|
+
CASE
|
|
2498
|
+
WHEN (pos_total_sec > (0)::numeric) THEN round(LEAST((climate_on_sec / pos_total_sec), 1.0), 6)
|
|
2499
|
+
ELSE NULL::numeric
|
|
2500
|
+
END AS climate_on_ratio,
|
|
2501
|
+
CASE
|
|
2502
|
+
WHEN (pos_total_sec > (0)::numeric) THEN round(LEAST((rear_defroster_sec / pos_total_sec), 1.0), 6)
|
|
2503
|
+
ELSE NULL::numeric
|
|
2504
|
+
END AS rear_defroster_ratio,
|
|
2505
|
+
CASE
|
|
2506
|
+
WHEN (pos_total_sec > (0)::numeric) THEN round(LEAST((front_defroster_sec / pos_total_sec), 1.0), 6)
|
|
2507
|
+
ELSE NULL::numeric
|
|
2508
|
+
END AS front_defroster_ratio,
|
|
2509
|
+
CASE
|
|
2510
|
+
WHEN (pos_total_sec > (0)::numeric) THEN round(LEAST((battery_heater_sec / pos_total_sec), 1.0), 6)
|
|
2511
|
+
ELSE NULL::numeric
|
|
2512
|
+
END AS battery_heater_ratio
|
|
2513
|
+
FROM public.pic_refs_hvac_v b;
|
|
2514
|
+
|
|
2515
|
+
|
|
2389
2516
|
--
|
|
2390
2517
|
-- Name: pic_refs_prep_v; Type: VIEW; Schema: public; Owner: -
|
|
2391
2518
|
--
|
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.58",
|
|
4
4
|
"description": "TeslaMate integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "MXPicture",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@mxpicture/tesla-superchargers-generated": "^0.1.
|
|
53
|
+
"@mxpicture/tesla-superchargers-generated": "^0.1.58",
|
|
54
54
|
"dbmate": "2.35.1",
|
|
55
55
|
"postgres": "^3.4.9",
|
|
56
56
|
"short-uuid": "^6.0.3",
|
package/sql/reset.sql
CHANGED
|
@@ -31,6 +31,15 @@ DROP VIEW IF EXISTS pic_refs_prep_v;
|
|
|
31
31
|
|
|
32
32
|
DROP VIEW IF EXISTS pic_refs_base_v;
|
|
33
33
|
|
|
34
|
+
DROP VIEW IF EXISTS pic_refs_hvac_ratio_v;
|
|
35
|
+
|
|
36
|
+
DROP VIEW IF EXISTS pic_refs_hvac_v;
|
|
37
|
+
|
|
38
|
+
-- the pre-2026-09-06 names of the two above, for a database that still has them
|
|
39
|
+
DROP VIEW IF EXISTS pic_refs_ratio_v;
|
|
40
|
+
|
|
41
|
+
DROP VIEW IF EXISTS pic_refs_duration_v;
|
|
42
|
+
|
|
34
43
|
DROP VIEW IF EXISTS pic_refs_v;
|
|
35
44
|
|
|
36
45
|
DROP VIEW IF EXISTS pic_cp_chain_v;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- SELECT
|
|
2
|
+
-- *
|
|
3
|
+
-- FROM
|
|
4
|
+
-- pic_refs_v
|
|
5
|
+
-- ORDER BY
|
|
6
|
+
-- start_date DESC
|
|
7
|
+
-- LIMIT
|
|
8
|
+
-- 10;
|
|
9
|
+
SELECT
|
|
10
|
+
*
|
|
11
|
+
FROM
|
|
12
|
+
positions p
|
|
13
|
+
WHERE
|
|
14
|
+
p.date >= to_timestamp(
|
|
15
|
+
'2026-09-06 10:10:17.521',
|
|
16
|
+
'YYYY-MM-DD HH24:MI:SS'
|
|
17
|
+
)
|
|
18
|
+
AND p.date <= to_timestamp(
|
|
19
|
+
'2026-09-06 10:33:06.469',
|
|
20
|
+
'YYYY-MM-DD HH24:MI:SS'
|
|
21
|
+
)
|
|
22
|
+
ORDER BY
|
|
23
|
+
p.date,
|
|
24
|
+
p.id
|
|
25
|
+
LIMIT
|
|
26
|
+
10;
|
package/sql/views-drop.sql
CHANGED
|
@@ -36,6 +36,20 @@ DROP VIEW IF EXISTS pic_refs_prep_v;
|
|
|
36
36
|
|
|
37
37
|
DROP VIEW IF EXISTS pic_refs_base_v;
|
|
38
38
|
|
|
39
|
+
DROP VIEW IF EXISTS pic_refs_hvac_ratio_v;
|
|
40
|
+
|
|
41
|
+
DROP VIEW IF EXISTS pic_refs_hvac_v;
|
|
42
|
+
|
|
43
|
+
-- RETIRED by rename, same rule as the group above: these are the names the two
|
|
44
|
+
-- HVAC views carried before 2026-09-06 (pic_refs_duration_v read as "the
|
|
45
|
+
-- duration of refs", which is pic_refs.hours -- a different thing). An existing
|
|
46
|
+
-- database sheds them here; a database that never had them shrugs at the IF
|
|
47
|
+
-- EXISTS. Delete these two lines once no database this code can reach still has
|
|
48
|
+
-- them -- production carries them until the deploy that lands this rename.
|
|
49
|
+
DROP VIEW IF EXISTS pic_refs_ratio_v;
|
|
50
|
+
|
|
51
|
+
DROP VIEW IF EXISTS pic_refs_duration_v;
|
|
52
|
+
|
|
39
53
|
DROP VIEW IF EXISTS pic_refs_v;
|
|
40
54
|
|
|
41
55
|
DROP VIEW IF EXISTS pic_addresses_v;
|
package/sql/views.sql
CHANGED
|
@@ -1093,6 +1093,239 @@ WHERE
|
|
|
1093
1093
|
-- opening balance anchored.
|
|
1094
1094
|
AND COALESCE(r.prev_id, 0) >= 0;
|
|
1095
1095
|
|
|
1096
|
+
-- Per-ref HVAC duty, measured from the position samples inside the interval.
|
|
1097
|
+
--
|
|
1098
|
+
-- TeslaMate records is_climate_on, the two defrosters and battery_heater on
|
|
1099
|
+
-- every `positions` row as a state, not a duration: the sample says what was
|
|
1100
|
+
-- on at that instant and nothing about how long. So each sample is charged
|
|
1101
|
+
-- with the time until the next sample inside the same ref, and the last one
|
|
1102
|
+
-- runs to the ref's end_date -- a lead() chain of the same shape as
|
|
1103
|
+
-- pic_positions_chain_v, partitioned per ref instead of running over the whole
|
|
1104
|
+
-- table.
|
|
1105
|
+
--
|
|
1106
|
+
-- The same hold is applied at the head: the FIRST sample's span starts at the
|
|
1107
|
+
-- ref's start_date, not at the sample, so its state carries backwards over the
|
|
1108
|
+
-- lead-in exactly as the last sample's carries forwards over the tail. That
|
|
1109
|
+
-- makes pos_total_sec equal to ref_duration_sec by construction, so the
|
|
1110
|
+
-- seconds of one ref sum to its whole duration -- which is what lets them be
|
|
1111
|
+
-- summed across refs at all. Both columns stay, and
|
|
1112
|
+
-- `pos_total_sec <> ref_duration_sec` is then an assertion that this view is
|
|
1113
|
+
-- broken rather than a fact about the data.
|
|
1114
|
+
--
|
|
1115
|
+
-- lead() returns the next sample's date directly rather than its id, and the
|
|
1116
|
+
-- window orders by (date, id) rather than by id alone. Both matter. Ordering
|
|
1117
|
+
-- by id assumes positions.id order matches date order; the table already holds
|
|
1118
|
+
-- rows where it does not, and the failure would be silent -- negative spans
|
|
1119
|
+
-- inside otherwise plausible sums. Taking the date from lead() is what keeps
|
|
1120
|
+
-- the CTE referenced once: a second reference makes Postgres materialise it,
|
|
1121
|
+
-- and a materialised CTE cannot take a predicate, so every lookup of a single
|
|
1122
|
+
-- ref would first join the whole of positions against the whole of pic_refs.
|
|
1123
|
+
--
|
|
1124
|
+
-- The two ref types find their samples differently, which is why this is a
|
|
1125
|
+
-- UNION of two nearly identical halves rather than one join: a drive's
|
|
1126
|
+
-- positions carry its drive_id, a parked one carries none, and folding that
|
|
1127
|
+
-- into a CASE would cost each half its index. 'charge' rides along with 'idle'
|
|
1128
|
+
-- in the second half -- a charging process owns no position rows, but the car
|
|
1129
|
+
-- is parked and still polled inside the window, and those samples are where
|
|
1130
|
+
-- preconditioning and cabin pre-heat show up.
|
|
1131
|
+
--
|
|
1132
|
+
-- A sample missing any of the four flags is skipped rather than read as off:
|
|
1133
|
+
-- TeslaMate leaves them NULL when the car reported nothing, and counting NULL
|
|
1134
|
+
-- as off would silently deflate every share. The skip is a hole in the
|
|
1135
|
+
-- sampling, not in the timeline -- the previous usable sample's state carries
|
|
1136
|
+
-- straight across it. A ref with no usable sample at all does not appear here.
|
|
1137
|
+
--
|
|
1138
|
+
-- Because the hold is applied at both ends, pos_total_sec carries no
|
|
1139
|
+
-- information any more -- it is ref_duration_sec. What the row does not say on
|
|
1140
|
+
-- its own is how much of that duration was actually observed, and a share of a
|
|
1141
|
+
-- ref sampled twice reads exactly like a share of a ref sampled forty times.
|
|
1142
|
+
-- pos_count, pos_first_date and max_gap_sec are that missing signal, and cost
|
|
1143
|
+
-- nothing: they come out of the aggregate that is already running. max_gap_sec
|
|
1144
|
+
-- is the one to filter on -- the longest stretch a single sample was made to
|
|
1145
|
+
-- speak for. On this history it is minutes on a drive and up to two days on an
|
|
1146
|
+
-- idle gap the car slept through.
|
|
1147
|
+
--
|
|
1148
|
+
-- ref_id > 0 AND COALESCE(prev_id, 0) >= 0 is the pair of guards pic_refs_v
|
|
1149
|
+
-- applies, and it takes both: they catch different rows. The per-car seed is
|
|
1150
|
+
-- ref_id = -1 with a NULL prev_id, so only the first one sees it; the idle gap
|
|
1151
|
+
-- trailing the seed carries a positive sequence ref_id and prev_id = -1, so
|
|
1152
|
+
-- only the second one does. Both intervals are constructed -- endpoints
|
|
1153
|
+
-- derived rather than observed -- so a sample inside either would be charged
|
|
1154
|
+
-- with a span that never happened. The seed used to escape this view by being
|
|
1155
|
+
-- ref_type = 'charge'; matching charges (above) is what makes the explicit
|
|
1156
|
+
-- guard necessary, and production had exactly one usable sample sitting in
|
|
1157
|
+
-- that constructed hour.
|
|
1158
|
+
CREATE OR REPLACE VIEW pic_refs_hvac_v AS
|
|
1159
|
+
WITH
|
|
1160
|
+
samples AS (
|
|
1161
|
+
SELECT
|
|
1162
|
+
r.car_id,
|
|
1163
|
+
r.ref_type,
|
|
1164
|
+
r.ref_id,
|
|
1165
|
+
r.start_date AS ref_start_date,
|
|
1166
|
+
r.end_date AS ref_end_date,
|
|
1167
|
+
p.id AS pos_id,
|
|
1168
|
+
p.date,
|
|
1169
|
+
p.is_climate_on,
|
|
1170
|
+
p.is_rear_defroster_on,
|
|
1171
|
+
p.is_front_defroster_on,
|
|
1172
|
+
p.battery_heater
|
|
1173
|
+
FROM
|
|
1174
|
+
pic_refs r
|
|
1175
|
+
INNER JOIN positions p ON p.car_id = r.car_id
|
|
1176
|
+
AND p.drive_id = r.ref_id
|
|
1177
|
+
AND p.date >= r.start_date
|
|
1178
|
+
AND p.date <= r.end_date
|
|
1179
|
+
AND p.is_climate_on IS NOT NULL
|
|
1180
|
+
AND p.is_rear_defroster_on IS NOT NULL
|
|
1181
|
+
AND p.is_front_defroster_on IS NOT NULL
|
|
1182
|
+
AND p.battery_heater IS NOT NULL
|
|
1183
|
+
WHERE
|
|
1184
|
+
r.ref_type = 'drive'
|
|
1185
|
+
AND r.ref_id > 0
|
|
1186
|
+
AND COALESCE(r.prev_id, 0) >= 0
|
|
1187
|
+
UNION ALL
|
|
1188
|
+
SELECT
|
|
1189
|
+
r.car_id,
|
|
1190
|
+
r.ref_type,
|
|
1191
|
+
r.ref_id,
|
|
1192
|
+
r.start_date AS ref_start_date,
|
|
1193
|
+
r.end_date AS ref_end_date,
|
|
1194
|
+
p.id AS pos_id,
|
|
1195
|
+
p.date,
|
|
1196
|
+
p.is_climate_on,
|
|
1197
|
+
p.is_rear_defroster_on,
|
|
1198
|
+
p.is_front_defroster_on,
|
|
1199
|
+
p.battery_heater
|
|
1200
|
+
FROM
|
|
1201
|
+
pic_refs r
|
|
1202
|
+
INNER JOIN positions p ON p.car_id = r.car_id
|
|
1203
|
+
AND p.drive_id IS NULL
|
|
1204
|
+
AND p.date >= r.start_date
|
|
1205
|
+
AND p.date <= r.end_date
|
|
1206
|
+
AND p.is_climate_on IS NOT NULL
|
|
1207
|
+
AND p.is_rear_defroster_on IS NOT NULL
|
|
1208
|
+
AND p.is_front_defroster_on IS NOT NULL
|
|
1209
|
+
AND p.battery_heater IS NOT NULL
|
|
1210
|
+
WHERE
|
|
1211
|
+
r.ref_type IN ('idle', 'charge')
|
|
1212
|
+
AND r.ref_id > 0
|
|
1213
|
+
AND COALESCE(r.prev_id, 0) >= 0
|
|
1214
|
+
),
|
|
1215
|
+
spans AS (
|
|
1216
|
+
SELECT
|
|
1217
|
+
s.*,
|
|
1218
|
+
EXTRACT(
|
|
1219
|
+
EPOCH
|
|
1220
|
+
FROM
|
|
1221
|
+
COALESCE(lead(s.date) OVER w, s.ref_end_date) - CASE
|
|
1222
|
+
WHEN row_number() OVER w = 1 THEN s.ref_start_date
|
|
1223
|
+
ELSE s.date
|
|
1224
|
+
END
|
|
1225
|
+
) AS duration_sec
|
|
1226
|
+
FROM
|
|
1227
|
+
samples s
|
|
1228
|
+
WINDOW
|
|
1229
|
+
w AS (
|
|
1230
|
+
PARTITION BY
|
|
1231
|
+
s.car_id,
|
|
1232
|
+
s.ref_type,
|
|
1233
|
+
s.ref_id
|
|
1234
|
+
ORDER BY
|
|
1235
|
+
s.date,
|
|
1236
|
+
s.pos_id
|
|
1237
|
+
)
|
|
1238
|
+
)
|
|
1239
|
+
SELECT
|
|
1240
|
+
s.car_id,
|
|
1241
|
+
s.ref_type,
|
|
1242
|
+
s.ref_id,
|
|
1243
|
+
s.ref_start_date,
|
|
1244
|
+
s.ref_end_date,
|
|
1245
|
+
EXTRACT(
|
|
1246
|
+
EPOCH
|
|
1247
|
+
FROM
|
|
1248
|
+
s.ref_end_date - s.ref_start_date
|
|
1249
|
+
) AS ref_duration_sec,
|
|
1250
|
+
sum(
|
|
1251
|
+
CASE
|
|
1252
|
+
WHEN s.is_climate_on THEN s.duration_sec
|
|
1253
|
+
ELSE 0
|
|
1254
|
+
END
|
|
1255
|
+
) AS climate_on_sec,
|
|
1256
|
+
sum(
|
|
1257
|
+
CASE
|
|
1258
|
+
WHEN s.is_rear_defroster_on THEN s.duration_sec
|
|
1259
|
+
ELSE 0
|
|
1260
|
+
END
|
|
1261
|
+
) AS rear_defroster_sec,
|
|
1262
|
+
sum(
|
|
1263
|
+
CASE
|
|
1264
|
+
WHEN s.is_front_defroster_on THEN s.duration_sec
|
|
1265
|
+
ELSE 0
|
|
1266
|
+
END
|
|
1267
|
+
) AS front_defroster_sec,
|
|
1268
|
+
sum(
|
|
1269
|
+
CASE
|
|
1270
|
+
WHEN s.battery_heater THEN s.duration_sec
|
|
1271
|
+
ELSE 0
|
|
1272
|
+
END
|
|
1273
|
+
) AS battery_heater_sec,
|
|
1274
|
+
sum(s.duration_sec) AS pos_total_sec,
|
|
1275
|
+
count(*) AS pos_count,
|
|
1276
|
+
min(s.date) AS pos_first_date,
|
|
1277
|
+
max(s.duration_sec) AS max_gap_sec
|
|
1278
|
+
FROM
|
|
1279
|
+
spans s
|
|
1280
|
+
GROUP BY
|
|
1281
|
+
s.car_id,
|
|
1282
|
+
s.ref_type,
|
|
1283
|
+
s.ref_id,
|
|
1284
|
+
s.ref_start_date,
|
|
1285
|
+
s.ref_end_date;
|
|
1286
|
+
|
|
1287
|
+
-- The same seconds as shares of the ref, one row for one row.
|
|
1288
|
+
--
|
|
1289
|
+
-- The guard is on the denominator, not inside LEAST. Postgres LEAST and
|
|
1290
|
+
-- GREATEST IGNORE their NULL arguments -- unlike Oracle and MySQL, the result
|
|
1291
|
+
-- is NULL only when every argument is -- so the obvious-looking
|
|
1292
|
+
-- LEAST(x / NULLIF(d, 0), 1.0) returns 1.0 for a zero denominator rather than
|
|
1293
|
+
-- NULL, and the row reports climate, both defrosters and the battery heater at
|
|
1294
|
+
-- 100 %. A ratio above 1 in a dashboard reads as data rather than as the
|
|
1295
|
+
-- arithmetic slip it is, and that one reads as a fully-loaded interval.
|
|
1296
|
+
--
|
|
1297
|
+
-- Since pos_total_sec is the ref's whole duration, a zero denominator now
|
|
1298
|
+
-- means a zero-length ref and nothing else. LEAST stays as the cap it was
|
|
1299
|
+
-- meant to be: with the spans tiling the ref exactly, no share can exceed 1
|
|
1300
|
+
-- by construction, so it is there to make a broken sum visible as a clamp
|
|
1301
|
+
-- rather than as a plausible number.
|
|
1302
|
+
CREATE OR REPLACE VIEW pic_refs_hvac_ratio_v AS
|
|
1303
|
+
SELECT
|
|
1304
|
+
b.*,
|
|
1305
|
+
CASE
|
|
1306
|
+
WHEN b.pos_total_sec > 0 THEN round(LEAST(b.climate_on_sec / b.pos_total_sec, 1.0), 6)
|
|
1307
|
+
END AS climate_on_ratio,
|
|
1308
|
+
CASE
|
|
1309
|
+
WHEN b.pos_total_sec > 0 THEN round(
|
|
1310
|
+
LEAST(b.rear_defroster_sec / b.pos_total_sec, 1.0),
|
|
1311
|
+
6
|
|
1312
|
+
)
|
|
1313
|
+
END AS rear_defroster_ratio,
|
|
1314
|
+
CASE
|
|
1315
|
+
WHEN b.pos_total_sec > 0 THEN round(
|
|
1316
|
+
LEAST(b.front_defroster_sec / b.pos_total_sec, 1.0),
|
|
1317
|
+
6
|
|
1318
|
+
)
|
|
1319
|
+
END AS front_defroster_ratio,
|
|
1320
|
+
CASE
|
|
1321
|
+
WHEN b.pos_total_sec > 0 THEN round(
|
|
1322
|
+
LEAST(b.battery_heater_sec / b.pos_total_sec, 1.0),
|
|
1323
|
+
6
|
|
1324
|
+
)
|
|
1325
|
+
END AS battery_heater_ratio
|
|
1326
|
+
FROM
|
|
1327
|
+
pic_refs_hvac_v b;
|
|
1328
|
+
|
|
1096
1329
|
CREATE OR REPLACE VIEW pic_refs_base_v AS
|
|
1097
1330
|
WITH
|
|
1098
1331
|
cp AS (
|
|
@@ -1325,7 +1558,38 @@ WITH
|
|
|
1325
1558
|
),
|
|
1326
1559
|
idle AS (
|
|
1327
1560
|
SELECT
|
|
1328
|
-
|
|
1561
|
+
*,
|
|
1562
|
+
-- Rated range that ROSE across a SHORT gap ending in a charge is the
|
|
1563
|
+
-- charging process opening late, not the car standing still, and the
|
|
1564
|
+
-- range belongs to the charge. Measured read-only against production on
|
|
1565
|
+
-- 2026-09-06: range rises after 7 of the 9 drives a charge follows and
|
|
1566
|
+
-- after 1 of the 315 a drive follows, so it is not a post-drive rebound;
|
|
1567
|
+
-- and the real Tesla invoice (pic_invoices.quantity_base) bills energy
|
|
1568
|
+
-- TeslaMate never logged, in the amount of the gap -- adopting it pulls
|
|
1569
|
+
-- billed/logged from as high as 1.264 back into the 1.055-1.089 band
|
|
1570
|
+
-- every gapless charge sits in. See
|
|
1571
|
+
-- docs/plans/2026-09-06_pre-charge-idle-adoption.md.
|
|
1572
|
+
--
|
|
1573
|
+
-- Such a gap is absorbed into the charge by `collection` below rather
|
|
1574
|
+
-- than kept as an idle ref. The bound is what separates it from a
|
|
1575
|
+
-- charging session TeslaMate missed ENTIRELY -- 218.76 km over 47.9 h at
|
|
1576
|
+
-- home -- which pic_build_costs_car prices at the fallback rate off its
|
|
1577
|
+
-- _rebound_km branch and which must never be handed to the next charge's
|
|
1578
|
+
-- invoice: that charge billed 1.70 EUR, and adopting 218.76 km into it
|
|
1579
|
+
-- would price the energy at roughly nothing and under-price every drive
|
|
1580
|
+
-- that drains it. The two populations are three orders of magnitude
|
|
1581
|
+
-- apart -- the longest gap adopted here ran 2.8 min, the nearest one
|
|
1582
|
+
-- excluded ran 47.9 h, and production holds nothing in between -- so the
|
|
1583
|
+
-- bound is a duration, which separates them cleanly, rather than a
|
|
1584
|
+
-- distance, which would have to guess at how fast a charger ramps.
|
|
1585
|
+
--
|
|
1586
|
+
-- The seed charge is out of reach by construction: it is every car's
|
|
1587
|
+
-- first row, so no gap precedes it.
|
|
1588
|
+
(
|
|
1589
|
+
next_type = 'charge'::pic_ref_type
|
|
1590
|
+
AND end_range_km > start_range_km
|
|
1591
|
+
AND sec_to_next <= 600
|
|
1592
|
+
) AS adopted
|
|
1329
1593
|
FROM
|
|
1330
1594
|
chain
|
|
1331
1595
|
WHERE
|
|
@@ -1346,28 +1610,54 @@ WITH
|
|
|
1346
1610
|
-- row drops out here: an interval that has not ended yet is not priced.
|
|
1347
1611
|
sec_to_next > 60
|
|
1348
1612
|
),
|
|
1349
|
-
|
|
1613
|
+
-- The gaps a charge takes over, keyed by the charge that takes them.
|
|
1614
|
+
adoption AS (
|
|
1350
1615
|
SELECT
|
|
1351
|
-
t AS ref_type,
|
|
1352
|
-
id::INTEGER AS ref_id,
|
|
1353
1616
|
car_id,
|
|
1617
|
+
next_id,
|
|
1354
1618
|
start_date,
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1619
|
+
start_range_km
|
|
1620
|
+
FROM
|
|
1621
|
+
idle
|
|
1622
|
+
WHERE
|
|
1623
|
+
adopted
|
|
1624
|
+
),
|
|
1625
|
+
collection AS (
|
|
1626
|
+
SELECT
|
|
1627
|
+
b.t AS ref_type,
|
|
1628
|
+
b.id::INTEGER AS ref_id,
|
|
1629
|
+
b.car_id,
|
|
1630
|
+
-- An adopted charge begins where the car parked rather than where
|
|
1631
|
+
-- TeslaMate noticed it. A DELIBERATE divergence: for these rows
|
|
1632
|
+
-- start_date and start_range_km no longer echo charging_processes'
|
|
1633
|
+
-- start_date and start_rated_range_km. Both move together on purpose --
|
|
1634
|
+
-- the range the charge gained has to be spread over the time it really
|
|
1635
|
+
-- took, or mean_draw_kw reads high -- and because the absorbed idle no
|
|
1636
|
+
-- longer sits between this row and the drive before it, the two now meet
|
|
1637
|
+
-- exactly: the charge starts at the instant the drive ended, leaving
|
|
1638
|
+
-- neither a gap for the queue to anchor across nor an overlap for
|
|
1639
|
+
-- pic_refs_hvac_v to count a position twice in.
|
|
1640
|
+
COALESCE(a.start_date, b.start_date) AS start_date,
|
|
1641
|
+
b.end_date,
|
|
1642
|
+
COALESCE(a.start_range_km, b.start_range_km) AS start_range_km,
|
|
1643
|
+
b.end_range_km,
|
|
1358
1644
|
NULL::pic_ref_type AS prev_type,
|
|
1359
1645
|
NULL::INTEGER AS prev_id,
|
|
1360
1646
|
NULL::pic_ref_type AS next_type,
|
|
1361
1647
|
NULL::INTEGER AS next_id,
|
|
1362
|
-
start_position_id,
|
|
1363
|
-
end_position_id,
|
|
1364
|
-
start_address_id,
|
|
1365
|
-
end_address_id,
|
|
1366
|
-
start_geofence_id,
|
|
1367
|
-
end_geofence_id,
|
|
1368
|
-
real_km
|
|
1648
|
+
b.start_position_id,
|
|
1649
|
+
b.end_position_id,
|
|
1650
|
+
b.start_address_id,
|
|
1651
|
+
b.end_address_id,
|
|
1652
|
+
b.start_geofence_id,
|
|
1653
|
+
b.end_geofence_id,
|
|
1654
|
+
b.real_km
|
|
1369
1655
|
FROM
|
|
1370
|
-
pic_refs_base_v
|
|
1656
|
+
pic_refs_base_v b
|
|
1657
|
+
-- The type is part of the join: a drive and a charge can share an id.
|
|
1658
|
+
LEFT JOIN adoption a ON a.car_id = b.car_id
|
|
1659
|
+
AND a.next_id = b.id
|
|
1660
|
+
AND b.t = 'charge'::pic_ref_type
|
|
1371
1661
|
UNION ALL
|
|
1372
1662
|
SELECT
|
|
1373
1663
|
'idle'::pic_ref_type AS ref_type,
|
|
@@ -1390,6 +1680,8 @@ WITH
|
|
|
1390
1680
|
NULL::NUMERIC AS real_km
|
|
1391
1681
|
FROM
|
|
1392
1682
|
idle
|
|
1683
|
+
WHERE
|
|
1684
|
+
NOT adopted
|
|
1393
1685
|
)
|
|
1394
1686
|
SELECT
|
|
1395
1687
|
c.*,
|
|
@@ -1715,10 +2007,19 @@ DECLARE
|
|
|
1715
2007
|
-- pic_energy_ledger_v's cfg used the same constant for a car that had never
|
|
1716
2008
|
-- charged.
|
|
1717
2009
|
_base_price CONSTANT NUMERIC := 0.40;
|
|
1718
|
-
-- Rated range
|
|
1719
|
-
--
|
|
1720
|
-
--
|
|
1721
|
-
--
|
|
2010
|
+
-- Rated range rebounds a little after a drive. An idle gain below this is
|
|
2011
|
+
-- that artefact; a gain above it is energy that really arrived and TeslaMate
|
|
2012
|
+
-- never logged. Same threshold the retired pic_energy_ledger_v used for its
|
|
2013
|
+
-- rebound_km.
|
|
2014
|
+
--
|
|
2015
|
+
-- The +16.72 km within 1.3 min this constant was once justified by was NOT a
|
|
2016
|
+
-- rebound: it was cp 18's pre-charge gap, which pic_refs_prep_v now adopts
|
|
2017
|
+
-- into the charge, and measuring the rest of production put the true
|
|
2018
|
+
-- post-drive rebound at 1.29 km or less. So the threshold is uncalibrated
|
|
2019
|
+
-- between roughly 1.3 km and 20 km -- production holds no observation in
|
|
2020
|
+
-- that band. It still earns its keep at the top of the range, where it
|
|
2021
|
+
-- catches the 218.76 km home session TeslaMate slept through, and lowering
|
|
2022
|
+
-- it would only start guessing in the empty middle.
|
|
1722
2023
|
_rebound_km CONSTANT NUMERIC := 20.0;
|
|
1723
2024
|
BEGIN
|
|
1724
2025
|
-- Same lock as pic_build_refs_car: this procedure rewrites cost and
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
WITH
|
|
2
|
-
base AS (
|
|
3
|
-
SELECT
|
|
4
|
-
p.car_id,
|
|
5
|
-
r.ref_type,
|
|
6
|
-
r.ref_id,
|
|
7
|
-
r.start_date AS ref_start_date,
|
|
8
|
-
r.end_date AS ref_end_date,
|
|
9
|
-
p.id AS pos_id,
|
|
10
|
-
p.drive_id,
|
|
11
|
-
p.is_climate_on,
|
|
12
|
-
p.is_rear_defroster_on,
|
|
13
|
-
p.is_front_defroster_on,
|
|
14
|
-
p.battery_heater
|
|
15
|
-
FROM
|
|
16
|
-
pic_refs r
|
|
17
|
-
LEFT JOIN positions p ON p.car_id = r.car_id
|
|
18
|
-
AND p.drive_id = r.ref_id
|
|
19
|
-
AND p.date >= r.start_date
|
|
20
|
-
AND p.date <= r.end_date
|
|
21
|
-
AND p.is_climate_on IS NOT NULL
|
|
22
|
-
AND p.is_rear_defroster_on IS NOT NULL
|
|
23
|
-
AND p.is_front_defroster_on IS NOT NULL
|
|
24
|
-
AND p.battery_heater IS NOT NULL
|
|
25
|
-
WHERE
|
|
26
|
-
r.ref_type = 'drive'
|
|
27
|
-
UNION ALL
|
|
28
|
-
SELECT
|
|
29
|
-
p.car_id,
|
|
30
|
-
r.ref_type,
|
|
31
|
-
r.ref_id,
|
|
32
|
-
r.start_date AS ref_start_date,
|
|
33
|
-
r.end_date AS ref_end_date,
|
|
34
|
-
p.id AS pos_id,
|
|
35
|
-
p.drive_id,
|
|
36
|
-
p.is_climate_on,
|
|
37
|
-
p.is_rear_defroster_on,
|
|
38
|
-
p.is_front_defroster_on,
|
|
39
|
-
p.battery_heater
|
|
40
|
-
FROM
|
|
41
|
-
pic_refs r
|
|
42
|
-
LEFT JOIN positions p ON p.car_id = r.car_id
|
|
43
|
-
AND p.drive_id IS NULL
|
|
44
|
-
AND p.date >= r.start_date
|
|
45
|
-
AND p.date <= r.end_date
|
|
46
|
-
AND p.is_climate_on IS NOT NULL
|
|
47
|
-
AND p.is_rear_defroster_on IS NOT NULL
|
|
48
|
-
AND p.is_front_defroster_on IS NOT NULL
|
|
49
|
-
AND p.battery_heater IS NOT NULL
|
|
50
|
-
WHERE
|
|
51
|
-
r.ref_type = 'idle'
|
|
52
|
-
ORDER BY
|
|
53
|
-
car_id,
|
|
54
|
-
ref_type,
|
|
55
|
-
ref_id,
|
|
56
|
-
ref_start_date,
|
|
57
|
-
ref_end_date
|
|
58
|
-
),
|
|
59
|
-
base_chain_ids AS (
|
|
60
|
-
SELECT
|
|
61
|
-
car_id,
|
|
62
|
-
ref_type,
|
|
63
|
-
ref_id,
|
|
64
|
-
ref_start_date,
|
|
65
|
-
ref_end_date,
|
|
66
|
-
pos_id,
|
|
67
|
-
lead(pos_id) OVER (
|
|
68
|
-
PARTITION BY
|
|
69
|
-
car_id,
|
|
70
|
-
ref_type,
|
|
71
|
-
ref_id
|
|
72
|
-
ORDER BY
|
|
73
|
-
car_id,
|
|
74
|
-
ref_type,
|
|
75
|
-
ref_id,
|
|
76
|
-
ref_start_date,
|
|
77
|
-
ref_end_date,
|
|
78
|
-
pos_id
|
|
79
|
-
) AS next_pos_id
|
|
80
|
-
FROM
|
|
81
|
-
base
|
|
82
|
-
ORDER BY
|
|
83
|
-
car_id,
|
|
84
|
-
ref_type,
|
|
85
|
-
ref_id,
|
|
86
|
-
ref_start_date,
|
|
87
|
-
ref_end_date
|
|
88
|
-
),
|
|
89
|
-
base_pos AS (
|
|
90
|
-
SELECT
|
|
91
|
-
b.car_id,
|
|
92
|
-
b.ref_type,
|
|
93
|
-
b.ref_id,
|
|
94
|
-
b.ref_start_date,
|
|
95
|
-
b.ref_end_date,
|
|
96
|
-
b.pos_id,
|
|
97
|
-
b.next_pos_id,
|
|
98
|
-
p.date,
|
|
99
|
-
p.is_climate_on,
|
|
100
|
-
p.is_rear_defroster_on,
|
|
101
|
-
p.is_front_defroster_on,
|
|
102
|
-
p.drive_id,
|
|
103
|
-
p.battery_heater
|
|
104
|
-
FROM
|
|
105
|
-
base_chain_ids b
|
|
106
|
-
INNER JOIN positions p ON p.id = b.pos_id
|
|
107
|
-
),
|
|
108
|
-
base_duration AS (
|
|
109
|
-
SELECT
|
|
110
|
-
b.*,
|
|
111
|
-
n.date AS next_date,
|
|
112
|
-
EXTRACT(
|
|
113
|
-
EPOCH
|
|
114
|
-
FROM
|
|
115
|
-
n.date
|
|
116
|
-
) - EXTRACT(
|
|
117
|
-
EPOCH
|
|
118
|
-
FROM
|
|
119
|
-
b.date
|
|
120
|
-
) AS duration_sec
|
|
121
|
-
FROM
|
|
122
|
-
base_pos b
|
|
123
|
-
LEFT JOIN base_pos n ON n.car_id = b.car_id
|
|
124
|
-
AND n.ref_type = b.ref_type
|
|
125
|
-
AND n.ref_id = b.ref_id
|
|
126
|
-
AND n.pos_id = b.next_pos_id
|
|
127
|
-
ORDER BY
|
|
128
|
-
b.car_id,
|
|
129
|
-
b.ref_type,
|
|
130
|
-
b.ref_id,
|
|
131
|
-
b.ref_start_date,
|
|
132
|
-
b.ref_end_date,
|
|
133
|
-
b.date
|
|
134
|
-
),
|
|
135
|
-
condensed AS (
|
|
136
|
-
SELECT
|
|
137
|
-
b.car_id,
|
|
138
|
-
b.ref_type,
|
|
139
|
-
b.ref_id,
|
|
140
|
-
min(b.ref_start_date) AS ref_start_date,
|
|
141
|
-
max(b.ref_end_date) AS ref_end_date,
|
|
142
|
-
EXTRACT(
|
|
143
|
-
EPOCH
|
|
144
|
-
FROM
|
|
145
|
-
max(b.ref_end_date)
|
|
146
|
-
) - EXTRACT(
|
|
147
|
-
EPOCH
|
|
148
|
-
FROM
|
|
149
|
-
min(b.ref_start_date)
|
|
150
|
-
) AS ref_duration_sec,
|
|
151
|
-
sum(
|
|
152
|
-
CASE
|
|
153
|
-
WHEN b.is_climate_on THEN b.duration_sec
|
|
154
|
-
ELSE 0
|
|
155
|
-
END
|
|
156
|
-
) AS climate_on_sec,
|
|
157
|
-
sum(
|
|
158
|
-
CASE
|
|
159
|
-
WHEN b.is_rear_defroster_on THEN b.duration_sec
|
|
160
|
-
ELSE 0
|
|
161
|
-
END
|
|
162
|
-
) AS rear_defroster_sec,
|
|
163
|
-
sum(
|
|
164
|
-
CASE
|
|
165
|
-
WHEN b.is_front_defroster_on THEN b.duration_sec
|
|
166
|
-
ELSE 0
|
|
167
|
-
END
|
|
168
|
-
) AS front_defroster_sec,
|
|
169
|
-
sum(
|
|
170
|
-
CASE
|
|
171
|
-
WHEN b.battery_heater THEN b.duration_sec
|
|
172
|
-
ELSE 0
|
|
173
|
-
END
|
|
174
|
-
) AS battery_heater_sec,
|
|
175
|
-
sum(b.duration_sec) AS pos_total_sec
|
|
176
|
-
FROM
|
|
177
|
-
base_duration b
|
|
178
|
-
GROUP BY
|
|
179
|
-
b.car_id,
|
|
180
|
-
b.ref_type,
|
|
181
|
-
b.ref_id
|
|
182
|
-
),
|
|
183
|
-
factored AS (
|
|
184
|
-
SELECT
|
|
185
|
-
b.*,
|
|
186
|
-
round(
|
|
187
|
-
LEAST(climate_on_sec / NULLIF(pos_total_sec, 0), 1.0),
|
|
188
|
-
6
|
|
189
|
-
) AS climate_on_factor,
|
|
190
|
-
round(
|
|
191
|
-
LEAST(
|
|
192
|
-
rear_defroster_sec / NULLIF(pos_total_sec, 0),
|
|
193
|
-
1.0
|
|
194
|
-
),
|
|
195
|
-
6
|
|
196
|
-
) AS rear_defroster_factor,
|
|
197
|
-
round(
|
|
198
|
-
LEAST(
|
|
199
|
-
front_defroster_sec / NULLIF(pos_total_sec, 0),
|
|
200
|
-
1.0
|
|
201
|
-
),
|
|
202
|
-
6
|
|
203
|
-
) AS front_defroster_factor,
|
|
204
|
-
round(
|
|
205
|
-
LEAST(
|
|
206
|
-
battery_heater_sec / NULLIF(pos_total_sec, 0),
|
|
207
|
-
1.0
|
|
208
|
-
),
|
|
209
|
-
6
|
|
210
|
-
) AS battery_heater_factor
|
|
211
|
-
FROM
|
|
212
|
-
condensed b
|
|
213
|
-
)
|
|
214
|
-
SELECT
|
|
215
|
-
b.*
|
|
216
|
-
-- min(b.pos_total_sec - b.ref_duration_sec) AS min_error_sec
|
|
217
|
-
-- max(b.pos_total_sec - b.ref_duration_sec) AS max_error_sec
|
|
218
|
-
FROM
|
|
219
|
-
factored b
|
|
220
|
-
-- SELECT
|
|
221
|
-
-- b.car_id,
|
|
222
|
-
-- b.ref_type,
|
|
223
|
-
-- b.ref_id,
|
|
224
|
-
-- round(
|
|
225
|
-
-- b.climate_on_count::NUMERIC / b.total_count::NUMERIC,
|
|
226
|
-
-- 4
|
|
227
|
-
-- ) AS climate_on_ratio,
|
|
228
|
-
-- round(
|
|
229
|
-
-- b.rear_defroster_count::NUMERIC / b.total_count::NUMERIC,
|
|
230
|
-
-- 4
|
|
231
|
-
-- ) AS rear_defroster_ratio,
|
|
232
|
-
-- round(
|
|
233
|
-
-- b.front_defroster_count::NUMERIC / b.total_count::NUMERIC,
|
|
234
|
-
-- 4
|
|
235
|
-
-- ) AS front_defroster_ratio,
|
|
236
|
-
-- round(
|
|
237
|
-
-- b.battery_heater_count::NUMERIC / b.total_count::NUMERIC,
|
|
238
|
-
-- 4
|
|
239
|
-
-- ) AS battery_heater_ratio
|
|
240
|
-
-- FROM
|
|
241
|
-
-- condensed b
|
|
242
|
-
-- LIMIT
|
|
243
|
-
-- 2000;
|