Count driving only and include charging losses

This commit is contained in:
TCFDONMEZ
2026-10-08 15:51:38 +03:00
parent 050fe84d6a
commit ae036b9d65
16 changed files with 104 additions and 41 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
MONGODB_URI mongodb+srv://boklukunefe:9rprDcfHTjBYza7z@cluster0.1aarkvo.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0
MONGODB_DATABASE fatihsmg4
INGEST_TOKEN 066da6fc58a0fcd8a35deccb29e43ef7a0b3bcda5a3c2b99db86e64a5b982ec6
SESSION_SECRET a89b011dfe7bec22c4f44f89e8e0ef647e6ff3289e84ca1b5b68c413ff7f4b77
DASHBOARD_PASSWORD <sen belirle, en az 8 karakter>
MONGODB_URI=mongodb+srv://username:password@cluster.example.mongodb.net/
MONGODB_DATABASE=fatihsmg4
INGEST_TOKEN=replace-with-a-long-random-write-token
SESSION_SECRET=replace-with-a-long-random-session-secret
DASHBOARD_PASSWORD=replace-with-a-strong-dashboard-password
+5 -3
View File
@@ -27,11 +27,12 @@ export default async function ChargingPage() {
return (
<Shell active="/charging" title="Şarj" subtitle={`${sessions.length} DC seans`}>
<div className="tiles">
<Tile label="Toplam alınan" value={precise(total.energyKwh)} unit="kWh" tone="gold" />
<Tile label="Bataryaya giren net" value={precise(total.energyKwh)} unit="kWh" tone="gold" />
<Tile label="Şebekeden alınan (+%10)" value={precise(total.gridEnergyKwh)} unit="kWh" tone="gold" />
<Tile label="Toplam ödenen" value={money(total.cost)} tone="gold" />
<Tile
label="Ortalama birim"
value={total.energyKwh > 0 ? money(total.cost / total.energyKwh) : "—"}
value={total.gridEnergyKwh > 0 ? money(total.cost / total.gridEnergyKwh) : "—"}
note="kWh başına"
tone="teal"
/>
@@ -78,7 +79,8 @@ export default async function ChargingPage() {
<Row label="Başlangıç" value={dateTime(session.startedAt)} />
<Row label="Bitiş" value={dateTime(session.endedAt)} />
<Row label="Ölçülen süre" value={duration(session.durationSeconds)} />
<Row label="Alınan enerji" value={`${precise(session.energyKwh)} kWh`} />
<Row label="Bataryaya giren net" value={`${precise(session.energyKwh)} kWh`} />
<Row label="Şebekeden alınan (+%10)" value={`${precise(session.gridEnergyKwh ?? session.energyKwh * 1.1)} kWh`} />
<Row label="Kazanılan doluluk" value={gained === null ? "—" : `%${num(gained)}`} />
<Row label="Tepe güç" value={peak > 0 ? `${num(peak)} kW` : "—"} />
<Row label="Ortalama güç" value={average > 0 ? `${num(average)} kW` : "—"} />
+5 -4
View File
@@ -36,8 +36,8 @@ export default async function OverviewPage() {
>
<div className="tiles">
<Tile
label="Toplam alınan"
value={precise(charge.energyKwh)}
label="Şebekeden alınan"
value={precise(charge.gridEnergyKwh)}
unit="kWh"
note={`${charge.sessions} DC şarj`}
tone="gold"
@@ -45,7 +45,7 @@ export default async function OverviewPage() {
<Tile
label="Ödenen"
value={money(charge.cost)}
note={charge.energyKwh > 0 ? `${money(charge.cost / charge.energyKwh)}/kWh ort.` : undefined}
note={charge.gridEnergyKwh > 0 ? `${money(charge.cost / charge.gridEnergyKwh)}/kWh ort.` : undefined}
tone="gold"
/>
<Tile label="Son 30 gün" value={num(recent.km)} unit="km" note={efficiency(recent.kwh, recent.km)} tone="teal" />
@@ -68,7 +68,8 @@ export default async function OverviewPage() {
<h2 className="card-title">Son şarj</h2>
<div className="rows">
<Row label="Tarih" value={dateTime(lastSession.startedAt)} />
<Row label="Alınan enerji" value={`${precise(lastSession.energyKwh)} kWh`} />
<Row label="Bataryaya giren net" value={`${precise(lastSession.energyKwh)} kWh`} />
<Row label="Şebekeden alınan (+%10)" value={`${precise(lastSession.gridEnergyKwh ?? lastSession.energyKwh * 1.1)} kWh`} />
<Row
label="Doluluk"
value={
+3 -1
View File
@@ -47,6 +47,7 @@ export async function loadOverview(dayLimit = 60, sessionLimit = 60): Promise<Ov
export interface ChargeTotals {
energyKwh: number;
gridEnergyKwh: number;
cost: number;
sessions: number;
seconds: number;
@@ -58,6 +59,7 @@ export function sumCharging(sessions: ChargingSession[]): ChargeTotals {
return sessions.reduce<ChargeTotals>(
(total, session) => ({
energyKwh: total.energyKwh + session.energyKwh,
gridEnergyKwh: total.gridEnergyKwh + (session.gridEnergyKwh ?? session.energyKwh * 1.1),
cost: total.cost + session.totalCost,
sessions: total.sessions + 1,
seconds: total.seconds + session.durationSeconds,
@@ -68,7 +70,7 @@ export function sumCharging(sessions: ChargingSession[]): ChargeTotals {
: 0),
peakKw: Math.max(total.peakKw, ...session.curve.map((point) => point.powerKw), 0),
}),
{ energyKwh: 0, cost: 0, sessions: 0, seconds: 0, socGained: 0, peakKw: 0 },
{ energyKwh: 0, gridEnergyKwh: 0, cost: 0, sessions: 0, seconds: 0, socGained: 0, peakKw: 0 },
);
}
+2
View File
@@ -41,6 +41,8 @@ export interface ChargingSession {
endedAt: Date;
durationSeconds: number;
energyKwh: number;
chargingLossPercent: number;
gridEnergyKwh: number;
pricePerKwh: number;
totalCost: number;
startSocPercent?: number;
+9 -3
View File
@@ -49,15 +49,21 @@ export function validateChargingSession(body: unknown, pathSessionId: string): C
});
const startSocPercent = record.startSocPercent;
const endSocPercent = record.endSocPercent;
const energyKwh = bounded(record.energyKwh, 0, 1000, "energyKwh");
const pricePerKwh = bounded(record.pricePerKwh, 0, 1_000_000, "pricePerKwh");
const chargingLossPercent = 10;
const gridEnergyKwh = energyKwh * 1.1;
return {
installationId: validateUuid(record.installationId, "installationId"),
sessionId,
startedAt: validateDate(record.startedAt, "startedAt"),
endedAt: validateDate(record.endedAt, "endedAt"),
durationSeconds: bounded(record.durationSeconds, 0, 604800, "durationSeconds"),
energyKwh: bounded(record.energyKwh, 0, 1000, "energyKwh"),
pricePerKwh: bounded(record.pricePerKwh, 0, 1_000_000, "pricePerKwh"),
totalCost: bounded(record.totalCost, 0, 1_000_000_000, "totalCost"),
energyKwh,
chargingLossPercent,
gridEnergyKwh,
pricePerKwh,
totalCost: gridEnergyKwh * pricePerKwh,
...(startSocPercent === undefined ? {} : { startSocPercent: bounded(startSocPercent, 0, 100, "startSocPercent") }),
...(endSocPercent === undefined ? {} : { endSocPercent: bounded(endSocPercent, 0, 100, "endSocPercent") }),
curve,
+7
View File
@@ -52,6 +52,8 @@ test("a charging session must match the id in its path", () => {
endedAt: 1_760_002_000_000,
durationSeconds: 1800,
energyKwh: 31.4,
chargingLossPercent: 10,
gridEnergyKwh: 34.54,
pricePerKwh: 7.5,
totalCost: 235.5,
startSocPercent: 18,
@@ -61,6 +63,9 @@ test("a charging session must match the id in its path", () => {
const record = validateChargingSession(body, SESSION);
assert.equal(record.curve.length, 1);
assert.equal(record.curve[0].powerKw, 84.2);
assert.equal(record.chargingLossPercent, 10);
assert.equal(record.gridEnergyKwh, 34.54);
assert.equal(record.totalCost, 259.05);
assert.throws(() => validateChargingSession(body, "11111111-2222-4333-8444-555555555556"), ValidationError);
});
@@ -79,6 +84,8 @@ test("refuses a curve longer than the car can produce", () => {
endedAt: 2,
durationSeconds: 1,
energyKwh: 1,
chargingLossPercent: 10,
gridEnergyKwh: 1.1,
pricePerKwh: 1,
totalCost: 1,
curve,