@mxpicture/tesla-superchargers-mate 0.1.63 → 0.1.65

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.
@@ -0,0 +1,61 @@
1
+ -- pic_additional_costs: costs TeslaMate never sees -- service, repairs, tyres,
2
+ -- insurance, tax, subsidies -- entered by hand and attributed to a car and a day.
3
+ --
4
+ -- The first pic_* table whose rows are not derivable: everything else here is a
5
+ -- cache of a feed or of an algorithm and can be rebuilt, these can only be
6
+ -- retyped. No upsert path, no sync, no soft delete.
7
+ --
8
+ -- A recurring cost is ONE row: recurrence = 'monthly' with date as the first
9
+ -- occurrence, end_date as the last (NULL = still running) and cost as the amount
10
+ -- PER MONTH. The future Grafana view expands it with generate_series; nothing
11
+ -- materializes the occurrences.
12
+ -- migrate:up
13
+ CREATE TYPE pic_cost_category AS ENUM(
14
+ 'service',
15
+ 'repair',
16
+ 'tyres',
17
+ 'insurance',
18
+ 'tax',
19
+ 'funding', -- subsidies and quota credits; normally a NEGATIVE cost
20
+ 'other' -- escape hatch, so an unforeseen cost never needs a migration
21
+ );
22
+
23
+ CREATE TYPE pic_cost_recurrence AS ENUM('once', 'monthly');
24
+
25
+ CREATE TABLE IF NOT EXISTS pic_additional_costs (
26
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
27
+ -- SMALLINT and no FK, like pic_refs.car_id: a REFERENCES cars (id) would put
28
+ -- constraint triggers on a TeslaMate table, which this project never writes to.
29
+ car_id SMALLINT NOT NULL,
30
+ category pic_cost_category NOT NULL,
31
+ -- A calendar day, not an instant; for a recurring row, the first occurrence.
32
+ -- Read and written as YYYY-MM-DD text by the store -- see the plan for why.
33
+ date DATE NOT NULL,
34
+ description TEXT NOT NULL,
35
+ -- EUR, like every stored cost here. Negative is allowed: a refund or a
36
+ -- 'funding' credit. On a recurring row this is the amount per occurrence.
37
+ cost NUMERIC(10, 2) NOT NULL,
38
+ recurrence pic_cost_recurrence NOT NULL DEFAULT 'once',
39
+ -- last occurrence of a monthly row; NULL = still running
40
+ end_date DATE,
41
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
42
+ CONSTRAINT pic_additional_costs_description_ck CHECK (btrim(description) <> ''),
43
+ CONSTRAINT pic_additional_costs_end_date_ck CHECK (
44
+ end_date IS NULL
45
+ OR (
46
+ recurrence = 'monthly'
47
+ AND end_date >= date
48
+ )
49
+ )
50
+ );
51
+
52
+ -- The predicate a per-car cost-over-time panel will filter and order on.
53
+ -- NOT unique: several entries per car and day are explicitly allowed.
54
+ CREATE INDEX IF NOT EXISTS pic_additional_costs_car_date_idx ON pic_additional_costs (car_id, date);
55
+
56
+ -- migrate:down
57
+ DROP TABLE IF EXISTS pic_additional_costs;
58
+
59
+ DROP TYPE IF EXISTS pic_cost_recurrence;
60
+
61
+ DROP TYPE IF EXISTS pic_cost_category;
package/db/schema.sql CHANGED
@@ -11,6 +11,10 @@
11
11
 
12
12
  CREATE TYPE public.pic_address_type AS ENUM ('geofence', 'cluster', 'address', 'position');
13
13
 
14
+ CREATE TYPE public.pic_cost_category AS ENUM ('service', 'repair', 'tyres', 'insurance', 'tax', 'funding', 'other');
15
+
16
+ CREATE TYPE public.pic_cost_recurrence AS ENUM ('once', 'monthly');
17
+
14
18
  CREATE TYPE public.pic_ref_type AS ENUM ('drive', 'charge', 'idle');
15
19
 
16
20
  --
@@ -35,6 +39,39 @@ SET default_tablespace = '';
35
39
 
36
40
  SET default_table_access_method = heap;
37
41
 
42
+ --
43
+ -- Name: pic_additional_costs; Type: TABLE; Schema: public; Owner: -
44
+ --
45
+
46
+ CREATE TABLE public.pic_additional_costs (
47
+ id bigint NOT NULL,
48
+ car_id smallint NOT NULL,
49
+ category public.pic_cost_category NOT NULL,
50
+ date date NOT NULL,
51
+ description text NOT NULL,
52
+ cost numeric(10,2) NOT NULL,
53
+ recurrence public.pic_cost_recurrence DEFAULT 'once'::public.pic_cost_recurrence NOT NULL,
54
+ end_date date,
55
+ created_at timestamp with time zone DEFAULT now() NOT NULL,
56
+ CONSTRAINT pic_additional_costs_description_ck CHECK ((btrim(description) <> ''::text)),
57
+ CONSTRAINT pic_additional_costs_end_date_ck CHECK (((end_date IS NULL) OR ((recurrence = 'monthly'::public.pic_cost_recurrence) AND (end_date >= date))))
58
+ );
59
+
60
+
61
+ --
62
+ -- Name: pic_additional_costs_id_seq; Type: SEQUENCE; Schema: public; Owner: -
63
+ --
64
+
65
+ ALTER TABLE public.pic_additional_costs ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
66
+ SEQUENCE NAME public.pic_additional_costs_id_seq
67
+ START WITH 1
68
+ INCREMENT BY 1
69
+ NO MINVALUE
70
+ NO MAXVALUE
71
+ CACHE 1
72
+ );
73
+
74
+
38
75
  --
39
76
  -- Name: pic_zip_country; Type: TABLE; Schema: public; Owner: -
40
77
  --
@@ -536,6 +573,14 @@ CREATE UNLOGGED TABLE public.pic_zip_import (
536
573
  );
537
574
 
538
575
 
576
+ --
577
+ -- Name: pic_additional_costs pic_additional_costs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
578
+ --
579
+
580
+ ALTER TABLE ONLY public.pic_additional_costs
581
+ ADD CONSTRAINT pic_additional_costs_pkey PRIMARY KEY (id);
582
+
583
+
539
584
  --
540
585
  -- Name: pic_chargers pic_chargers_location_guid_key; Type: CONSTRAINT; Schema: public; Owner: -
541
586
  --
@@ -680,6 +725,13 @@ ALTER TABLE ONLY public.pic_zip_country
680
725
  ADD CONSTRAINT pic_zip_country_pkey PRIMARY KEY (country_code);
681
726
 
682
727
 
728
+ --
729
+ -- Name: pic_additional_costs_car_date_idx; Type: INDEX; Schema: public; Owner: -
730
+ --
731
+
732
+ CREATE INDEX pic_additional_costs_car_date_idx ON public.pic_additional_costs USING btree (car_id, date);
733
+
734
+
683
735
  --
684
736
  -- Name: pic_charg_proc_link_uq_idx; Type: INDEX; Schema: public; Owner: -
685
737
  --
@@ -0,0 +1,20 @@
1
+ import { type Cars, type CarsId, type CarsInitializer, type CarsMutator } from "@mxpicture/tesla-superchargers-generated/public";
2
+ import { IMateStore } from "./IMateStore.js";
3
+ import { type SqlTx } from "./db.js";
4
+ /** A car as a picker renders it; `name` is nullable in TeslaMate's own table. */
5
+ export interface CarOption {
6
+ id: CarsId;
7
+ name: string | null;
8
+ }
9
+ /**
10
+ * Read access to TeslaMate's `cars` table. Only {@link CarsStore.queryNames}
11
+ * today: the additional-costs form needs a car dropdown, and with no FK on
12
+ * `pic_additional_costs.car_id` a free-text number would book a cost against a
13
+ * car that may not exist. Read-only, like every TeslaMate table here.
14
+ */
15
+ export declare class CarsStore extends IMateStore<CarsId, Cars, CarsInitializer, CarsMutator> {
16
+ constructor();
17
+ queryNames(tx?: SqlTx): Promise<CarOption[]>;
18
+ }
19
+ export declare const carsStore: () => CarsStore;
20
+ //# sourceMappingURL=CarsStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CarsStore.d.ts","sourceRoot":"","sources":["../../src/store/CarsStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,IAAI,EACT,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,KAAK,WAAW,EACjB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAO,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAE1C,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,UAAU,CACvC,MAAM,EACN,IAAI,EACJ,eAAe,EACf,WAAW,CACZ;;IAKc,UAAU,CAAC,EAAE,GAAE,KAAa,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAGjE;AAGD,eAAO,MAAM,SAAS,iBAAqC,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { carsInitializer, carsMutator, } from "@mxpicture/tesla-superchargers-generated/public";
2
+ import { IMateStore } from "./IMateStore.js";
3
+ import { sql } from "./db.js";
4
+ /**
5
+ * Read access to TeslaMate's `cars` table. Only {@link CarsStore.queryNames}
6
+ * today: the additional-costs form needs a car dropdown, and with no FK on
7
+ * `pic_additional_costs.car_id` a free-text number would book a cost against a
8
+ * car that may not exist. Read-only, like every TeslaMate table here.
9
+ */
10
+ export class CarsStore extends IMateStore {
11
+ constructor() {
12
+ super("cars", carsInitializer, carsMutator);
13
+ }
14
+ async queryNames(tx = sql()) {
15
+ return await tx `SELECT id, name FROM cars ORDER BY id`;
16
+ }
17
+ }
18
+ let _store = null;
19
+ export const carsStore = () => (_store ??= new CarsStore());
20
+ //# sourceMappingURL=CarsStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CarsStore.js","sourceRoot":"","sources":["../../src/store/CarsStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,WAAW,GAKZ,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,GAAG,EAAc,MAAM,SAAS,CAAC;AAQ1C;;;;;GAKG;AACH,MAAM,OAAO,SAAU,SAAQ,UAK9B;IACC;QACE,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;IAC9C,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,KAAY,GAAG,EAAE;QACvC,OAAO,MAAM,EAAE,CAAa,uCAAuC,CAAC;IACtE,CAAC;CACF;AAED,IAAI,MAAM,GAAqB,IAAI,CAAC;AACpC,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC,CAAC"}
@@ -0,0 +1,60 @@
1
+ import type { PicAdditionalCostsId, PicCostCategory, PicCostRecurrence } from "@mxpicture/tesla-superchargers-generated/public";
2
+ import { type SqlTx } from "./db.js";
3
+ /**
4
+ * One stored entry. Both dates are `YYYY-MM-DD` and `cost` a NUMERIC string,
5
+ * exactly as they leave Postgres.
6
+ */
7
+ export interface AdditionalCost {
8
+ id: PicAdditionalCostsId;
9
+ car_id: number;
10
+ category: PicCostCategory;
11
+ date: string;
12
+ description: string;
13
+ cost: string;
14
+ recurrence: PicCostRecurrence;
15
+ end_date: string | null;
16
+ created_at: Date;
17
+ }
18
+ /** A full row as the UI edits it; `id` and `created_at` are the store's. */
19
+ export interface AdditionalCostInput {
20
+ car_id: number;
21
+ category: PicCostCategory;
22
+ /** YYYY-MM-DD */
23
+ date: string;
24
+ description: string;
25
+ cost: string;
26
+ recurrence: PicCostRecurrence;
27
+ /** YYYY-MM-DD, or null on a one-off row and an open-ended monthly one */
28
+ end_date: string | null;
29
+ }
30
+ /**
31
+ * `pic_additional_costs` — the costs TeslaMate never sees, typed in through
32
+ * the web UI. Full CRUD, because nothing else creates these rows: they are the
33
+ * only `pic_*` rows that cannot be rebuilt from a feed or an algorithm.
34
+ *
35
+ * Deliberately NOT an {@link IMateStore}, for the same reason
36
+ * {@link PicZipCodesStore} is not: the base class reads with `SELECT *` and
37
+ * writes through the generated zod schemas, and both put a JS `Date` on the
38
+ * path of a `DATE` column. postgres.js parses `DATE` (OID 1082) into a `Date`
39
+ * at *local* midnight, which `JSON.stringify` then emits as the previous day
40
+ * anywhere east of UTC; writing one back casts a timestamptz to `date` in the
41
+ * *session* zone, which is not the process's. So this store is the boundary
42
+ * and it speaks `YYYY-MM-DD` strings: every read projects `date::TEXT`, every
43
+ * write binds `${…}::DATE`, and no JS `Date` ever exists. Overriding all four
44
+ * inherited methods to do that would leave nothing inherited.
45
+ */
46
+ export declare class PicAdditionalCostsStore {
47
+ readonly table = "pic_additional_costs";
48
+ /** Newest day first; ties broken by insertion order, newest first. */
49
+ query(tx?: SqlTx): Promise<AdditionalCost[]>;
50
+ insert(input: AdditionalCostInput, tx?: SqlTx): Promise<AdditionalCost>;
51
+ /**
52
+ * Replace every editable column of one row — the UI edits a row as a whole,
53
+ * so there is no patch path. Null when no row has that id.
54
+ */
55
+ update(id: PicAdditionalCostsId, input: AdditionalCostInput, tx?: SqlTx): Promise<AdditionalCost | null>;
56
+ /** False when the row was already gone — deleting twice is not an error. */
57
+ remove(id: PicAdditionalCostsId, tx?: SqlTx): Promise<boolean>;
58
+ }
59
+ export declare const picAdditionalCostsStore: () => PicAdditionalCostsStore;
60
+ //# sourceMappingURL=PicAdditionalCostsStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PicAdditionalCostsStore.d.ts","sourceRoot":"","sources":["../../src/store/PicAdditionalCostsStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EAClB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAAO,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,oBAAoB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,4EAA4E;AAC5E,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,iBAAiB,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,uBAAuB;IAClC,SAAgB,KAAK,0BAA0B;IAE/C,sEAAsE;IACzD,KAAK,CAAC,EAAE,GAAE,KAAa,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IASnD,MAAM,CACjB,KAAK,EAAE,mBAAmB,EAC1B,EAAE,GAAE,KAAa,GAChB,OAAO,CAAC,cAAc,CAAC;IAe1B;;;OAGG;IACU,MAAM,CACjB,EAAE,EAAE,oBAAoB,EACxB,KAAK,EAAE,mBAAmB,EAC1B,EAAE,GAAE,KAAa,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAiBjC,4EAA4E;IAC/D,MAAM,CACjB,EAAE,EAAE,oBAAoB,EACxB,EAAE,GAAE,KAAa,GAChB,OAAO,CAAC,OAAO,CAAC;CAIpB;AAGD,eAAO,MAAM,uBAAuB,+BACQ,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { sql } from "./db.js";
2
+ /**
3
+ * `pic_additional_costs` — the costs TeslaMate never sees, typed in through
4
+ * the web UI. Full CRUD, because nothing else creates these rows: they are the
5
+ * only `pic_*` rows that cannot be rebuilt from a feed or an algorithm.
6
+ *
7
+ * Deliberately NOT an {@link IMateStore}, for the same reason
8
+ * {@link PicZipCodesStore} is not: the base class reads with `SELECT *` and
9
+ * writes through the generated zod schemas, and both put a JS `Date` on the
10
+ * path of a `DATE` column. postgres.js parses `DATE` (OID 1082) into a `Date`
11
+ * at *local* midnight, which `JSON.stringify` then emits as the previous day
12
+ * anywhere east of UTC; writing one back casts a timestamptz to `date` in the
13
+ * *session* zone, which is not the process's. So this store is the boundary
14
+ * and it speaks `YYYY-MM-DD` strings: every read projects `date::TEXT`, every
15
+ * write binds `${…}::DATE`, and no JS `Date` ever exists. Overriding all four
16
+ * inherited methods to do that would leave nothing inherited.
17
+ */
18
+ export class PicAdditionalCostsStore {
19
+ table = "pic_additional_costs";
20
+ /** Newest day first; ties broken by insertion order, newest first. */
21
+ async query(tx = sql()) {
22
+ return await tx `
23
+ SELECT
24
+ id, car_id, category, date::TEXT AS date, description, cost,
25
+ recurrence, end_date::TEXT AS end_date, created_at
26
+ FROM pic_additional_costs
27
+ ORDER BY date DESC, id DESC`;
28
+ }
29
+ async insert(input, tx = sql()) {
30
+ const [row] = await tx `
31
+ INSERT INTO pic_additional_costs (
32
+ car_id, category, date, description, cost, recurrence, end_date
33
+ ) VALUES (
34
+ ${input.car_id}, ${input.category}, ${input.date}::DATE,
35
+ ${input.description}, ${input.cost}, ${input.recurrence},
36
+ ${input.end_date}::DATE
37
+ )
38
+ RETURNING
39
+ id, car_id, category, date::TEXT AS date, description, cost,
40
+ recurrence, end_date::TEXT AS end_date, created_at`;
41
+ return row;
42
+ }
43
+ /**
44
+ * Replace every editable column of one row — the UI edits a row as a whole,
45
+ * so there is no patch path. Null when no row has that id.
46
+ */
47
+ async update(id, input, tx = sql()) {
48
+ const rows = await tx `
49
+ UPDATE pic_additional_costs SET
50
+ car_id = ${input.car_id},
51
+ category = ${input.category},
52
+ date = ${input.date}::DATE,
53
+ description = ${input.description},
54
+ cost = ${input.cost},
55
+ recurrence = ${input.recurrence},
56
+ end_date = ${input.end_date}::DATE
57
+ WHERE id = ${id}
58
+ RETURNING
59
+ id, car_id, category, date::TEXT AS date, description, cost,
60
+ recurrence, end_date::TEXT AS end_date, created_at`;
61
+ return rows[0] ?? null;
62
+ }
63
+ /** False when the row was already gone — deleting twice is not an error. */
64
+ async remove(id, tx = sql()) {
65
+ const result = await tx `DELETE FROM pic_additional_costs WHERE id = ${id}`;
66
+ return result.count > 0;
67
+ }
68
+ }
69
+ let _store = null;
70
+ export const picAdditionalCostsStore = () => (_store ??= new PicAdditionalCostsStore());
71
+ //# sourceMappingURL=PicAdditionalCostsStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PicAdditionalCostsStore.js","sourceRoot":"","sources":["../../src/store/PicAdditionalCostsStore.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,GAAG,EAAc,MAAM,SAAS,CAAC;AA+B1C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,uBAAuB;IAClB,KAAK,GAAG,sBAAsB,CAAC;IAE/C,sEAAsE;IAC/D,KAAK,CAAC,KAAK,CAAC,KAAY,GAAG,EAAE;QAClC,OAAO,MAAM,EAAE,CAAkB;;;;;kCAKH,CAAC;IACjC,CAAC;IAEM,KAAK,CAAC,MAAM,CACjB,KAA0B,EAC1B,KAAY,GAAG,EAAE;QAEjB,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,CAAkB;;;;UAIlC,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,IAAI;UAC9C,KAAK,CAAC,WAAW,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,UAAU;UACrD,KAAK,CAAC,QAAQ;;;;2DAImC,CAAC;QACxD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,MAAM,CACjB,EAAwB,EACxB,KAA0B,EAC1B,KAAY,GAAG,EAAE;QAEjB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;;mBAExB,KAAK,CAAC,MAAM;qBACV,KAAK,CAAC,QAAQ;iBAClB,KAAK,CAAC,IAAI;wBACH,KAAK,CAAC,WAAW;iBACxB,KAAK,CAAC,IAAI;uBACJ,KAAK,CAAC,UAAU;qBAClB,KAAK,CAAC,QAAQ;mBAChB,EAAE;;;2DAGsC,CAAC;QACxD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACzB,CAAC;IAED,4EAA4E;IACrE,KAAK,CAAC,MAAM,CACjB,EAAwB,EACxB,KAAY,GAAG,EAAE;QAEjB,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA,+CAA+C,EAAE,EAAE,CAAC;QAC3E,OAAO,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;IAC1B,CAAC;CACF;AAED,IAAI,MAAM,GAAmC,IAAI,CAAC;AAClD,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,EAAE,CAC1C,CAAC,MAAM,KAAK,IAAI,uBAAuB,EAAE,CAAC,CAAC"}
@@ -1,7 +1,9 @@
1
+ export * from "./CarsStore.js";
1
2
  export * from "./ChargingProcessesStore.js";
2
3
  export * from "./GeofencesStore.js";
3
4
  export * from "./IFailableStore.js";
4
5
  export * from "./IMateStore.js";
6
+ export * from "./PicAdditionalCostsStore.js";
5
7
  export * from "./PicCPLinksStore.js";
6
8
  export * from "./PicChargersStore.js";
7
9
  export * from "./PicInvoiceFilesStore.js";
@@ -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,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -1,8 +1,10 @@
1
1
  // This file is auto-generated by build-cli. Do not edit manually.
2
+ export * from "./CarsStore.js";
2
3
  export * from "./ChargingProcessesStore.js";
3
4
  export * from "./GeofencesStore.js";
4
5
  export * from "./IFailableStore.js";
5
6
  export * from "./IMateStore.js";
7
+ export * from "./PicAdditionalCostsStore.js";
6
8
  export * from "./PicCPLinksStore.js";
7
9
  export * from "./PicChargersStore.js";
8
10
  export * from "./PicInvoiceFilesStore.js";
@@ -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,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/store/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxpicture/tesla-superchargers-mate",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
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.63",
53
+ "@mxpicture/tesla-superchargers-generated": "^0.1.65",
54
54
  "dbmate": "2.35.1",
55
55
  "postgres": "^3.4.9",
56
56
  "short-uuid": "^6.0.3",
package/sql/reset.sql CHANGED
@@ -115,6 +115,13 @@ DROP PROCEDURE IF EXISTS pic_build_refs_car (SMALLINT);
115
115
 
116
116
  DROP PROCEDURE IF EXISTS pic_build_costs_car (SMALLINT);
117
117
 
118
+ DROP TABLE IF EXISTS pic_additional_costs;
119
+
120
+ -- After the table: both enums are only ever used by its columns.
121
+ DROP TYPE IF EXISTS pic_cost_recurrence;
122
+
123
+ DROP TYPE IF EXISTS pic_cost_category;
124
+
118
125
  DROP TABLE IF EXISTS pic_refs;
119
126
 
120
127
  DROP SEQUENCE IF EXISTS pic_refs_idle_id;
@@ -0,0 +1,55 @@
1
+ WITH
2
+ base AS (
3
+ SELECT
4
+ r.car_id,
5
+ r.ref_type,
6
+ sum(
7
+ EXTRACT(
8
+ EPOCH
9
+ FROM
10
+ r.end_date - r.start_date
11
+ )
12
+ ) AS duration_sec,
13
+ sum(r.real_km) AS real_km,
14
+ sum(r.start_range_km - r.end_range_km) AS dropped_km,
15
+ sum(r.cost) AS cost
16
+ FROM
17
+ pic_refs r
18
+ WHERE
19
+ r.car_id = 1
20
+ -- AND $__timeFilter(r.start_date)
21
+ GROUP BY
22
+ r.car_id,
23
+ r.ref_type
24
+ )
25
+ SELECT
26
+ r.*,
27
+ (
28
+ round(
29
+ (r.dropped_km * c.efficiency)::NUMERIC / NULLIF(r.duration_sec / 3600.0, (0)::NUMERIC),
30
+ 3
31
+ )
32
+ ) AS mean_draw_kw
33
+ -- kwh_per_real_1km
34
+ -- cost_per_real_100km
35
+ -- cost_per_kwh
36
+ -- floor(
37
+ -- extract(
38
+ -- epoch
39
+ -- FROM
40
+ -- r.start_date
41
+ -- )
42
+ -- ) * 1000 AS start_date_ts,
43
+ -- ceil(
44
+ -- extract(
45
+ -- epoch
46
+ -- FROM
47
+ -- r.end_date
48
+ -- )
49
+ -- ) * 1000 AS end_date_ts
50
+ FROM
51
+ base r
52
+ INNER JOIN cars c ON c.id = r.car_id;
53
+
54
+ -- ORDER BY
55
+ -- r.start_date DESC;
@@ -0,0 +1,181 @@
1
+ WITH
2
+ base AS (
3
+ SELECT
4
+ *
5
+ FROM
6
+ pic_refs_v
7
+ WHERE
8
+ car_id = 1
9
+ -- car_id = $car_id
10
+ -- AND $__timeFilter(start_date)
11
+ ),
12
+ latest_base AS (
13
+ SELECT
14
+ *
15
+ FROM
16
+ pic_refs_v b
17
+ WHERE
18
+ car_id = 1
19
+ -- car_id = $car_id
20
+ ORDER BY
21
+ start_date DESC,
22
+ end_date DESC
23
+ LIMIT
24
+ 1
25
+ ),
26
+ latest_selected AS (
27
+ SELECT
28
+ car_id,
29
+ ref_id,
30
+ ref_type
31
+ FROM
32
+ pic_refs_v b
33
+ WHERE
34
+ car_id = 1
35
+ -- car_id = $car_id
36
+ -- AND $__timeFilter(start_date)
37
+ AND ref_type = 'drive'
38
+ ORDER BY
39
+ start_date DESC,
40
+ end_date DESC
41
+ LIMIT
42
+ 1
43
+ ),
44
+ latest AS (
45
+ SELECT
46
+ b.car_id,
47
+ 'idle'::pic_ref_type AS ref_type,
48
+ (-100)::INTEGER AS ref_id,
49
+ end_date AS start_date,
50
+ (
51
+ SELECT
52
+ now()
53
+ ) AS end_date,
54
+ b.end_address AS start_address,
55
+ b.end_address,
56
+ NULL:NUMERIC AS real_km,
57
+ b.dropped_kwh,
58
+ b.gross_dropped_kwh,
59
+ b.dropped_cost,
60
+ b.gross_dropped_cost,
61
+ b.cost_per_real_100km,
62
+ b.gross_cost_per_real_100km,
63
+ b.kwh_per_real_1km,
64
+ b.gross_kwh_per_real_1km,
65
+ b.cost_per_kwh,
66
+ b.gross_cost_per_kwh
67
+ FROM
68
+ latest_base b
69
+ INNER JOIN latest_selected s ON s.car_id = b.car_id
70
+ AND s.ref_type = b.ref_type
71
+ AND s.ref_id = b.ref_id
72
+ LIMIT
73
+ 1
74
+ ),
75
+ res AS (
76
+ SELECT
77
+ rb.start_date,
78
+ rb.ref_type,
79
+ rb.end_date,
80
+ rb.start_address,
81
+ rb.end_address,
82
+ rb.real_km,
83
+ rb.dropped_kwh,
84
+ rb.gross_dropped_kwh,
85
+ rb.dropped_cost,
86
+ rb.gross_dropped_cost,
87
+ rb.cost_per_real_100km,
88
+ rb.gross_cost_per_real_100km,
89
+ rb.kwh_per_real_1km,
90
+ rb.gross_kwh_per_real_1km,
91
+ rb.cost_per_kwh,
92
+ rb.gross_cost_per_kwh,
93
+ rb.car_id,
94
+ rb.ref_id
95
+ FROM
96
+ base rb
97
+ UNION ALL
98
+ SELECT
99
+ rl.start_date,
100
+ rl.ref_type,
101
+ rl.end_date,
102
+ rl.start_address,
103
+ rl.end_address,
104
+ rl.real_km,
105
+ rl.dropped_kwh,
106
+ rl.gross_dropped_kwh,
107
+ rl.dropped_cost,
108
+ rl.gross_dropped_cost,
109
+ rl.cost_per_real_100km,
110
+ rl.gross_cost_per_real_100km,
111
+ rl.kwh_per_real_1km,
112
+ rl.gross_kwh_per_real_1km,
113
+ rl.cost_per_kwh,
114
+ rl.gross_cost_per_kwh,
115
+ rl.car_id,
116
+ rl.ref_id
117
+ FROM
118
+ latest rl
119
+ )
120
+ SELECT
121
+ r.start_date AS "Start",
122
+ CASE
123
+ WHEN r.ref_type = 'drive' THEN '🚗 Driving'
124
+ WHEN r.ref_type = 'idle' THEN '🅿️ Parking'
125
+ WHEN r.ref_type = 'charge' THEN '🔋 Charging'
126
+ ELSE NULL
127
+ END AS "Action",
128
+ EXTRACT(
129
+ EPOCH
130
+ FROM
131
+ r.end_date - r.start_date
132
+ ) AS "Duration",
133
+ r.start_address AS "From",
134
+ r.end_address AS "To",
135
+ r.real_km AS "Distance",
136
+ r.dropped_kwh AS "Energy (net)",
137
+ CASE
138
+ WHEN r.ref_type = 'drive' THEN r.gross_dropped_kwh
139
+ END AS "Energy (gross)",
140
+ r.dropped_cost AS "Cost (net)",
141
+ CASE
142
+ WHEN r.ref_type = 'drive' THEN r.gross_dropped_cost
143
+ END AS "Cost (gross)",
144
+ r.cost_per_real_100km AS "Cost / 100 km (net)",
145
+ CASE
146
+ WHEN r.ref_type = 'drive' THEN r.gross_cost_per_real_100km
147
+ END AS "Cost / 100 km (gross)",
148
+ r.kwh_per_real_1km AS "Energy / km (net)",
149
+ CASE
150
+ WHEN r.ref_type = 'drive' THEN r.gross_kwh_per_real_1km
151
+ END AS "Energy / km (gross)",
152
+ r.cost_per_kwh AS "Cost / kWh (net)",
153
+ CASE
154
+ WHEN r.ref_type = 'drive' THEN r.gross_cost_per_kwh
155
+ END AS "Cost / kWh (gross)",
156
+ floor(
157
+ extract(
158
+ epoch
159
+ FROM
160
+ r.start_date
161
+ )
162
+ ) * 1000 AS start_date_ts,
163
+ ceil(
164
+ extract(
165
+ epoch
166
+ FROM
167
+ r.end_date
168
+ )
169
+ ) * 1000 AS end_date_ts,
170
+ r.car_id,
171
+ r.ref_id AS drive_id,
172
+ -- h.climate_on_ratio,
173
+ -- CONCAT('F: ', round(h.front_defroster_ratio * 100.0, 0), ' %, R: ', round(h.rear_defroster_ratio * 100.0, 0), ' %') AS defroster_ratio,
174
+ h.battery_heater_ratio AS "Battery Heater"
175
+ FROM
176
+ res r
177
+ LEFT JOIN pic_refs_hvac_ratio_v h ON h.car_id = r.car_id
178
+ AND h.ref_type = r.ref_type
179
+ AND h.ref_id = r.ref_id
180
+ ORDER BY
181
+ r.start_date DESC;