Skip to content

Commit 0d144c0

Browse files
committed
fix(forecast): incomplete = !complete; days older than sample retention keep their total (can't be judged); WAPE wording in ARCHITECTURE, skylearn and card tooltip (codex)
1 parent a6ee002 commit 0d144c0

4 files changed

Lines changed: 9 additions & 5 deletions

File tree

‎ARCHITECTURE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ PWA ดูข้อมูลระบบโซลาร์เซลล์แบ
8383
| `GET /api/device` | ค่าเครื่อง (เฟส/PV/BMS) cache 60s · รับ `?station=` |
8484
| `GET /api/weather` | TMD/Open-Meteo + UV (cache 30 นาที) |
8585
| `GET /api/history?range=day\|month\|year` | กราฟย้อนหลัง (D1 ก่อน → Deye) · รับ `?station=` |
86-
| `GET /api/forecast/accuracy?days=` | คาดพรุ่งนี้ (จาก `forecast_log`) vs `daily.gen` ของวันที่จบแล้ว — คะแนน 100−MAPE, bias, MAE + แถว |
86+
| `GET /api/forecast/accuracy?days=` | คาดพรุ่งนี้ (จาก `forecast_log`) vs `daily.gen` ของวันที่จบแล้ว — คะแนน 100−WAPE (Σ|คลาด| ÷ Σจริง), bias, MAE + แถว · วันที่ตัวอย่างช่วงกลางวัน (06–18 น.) ไม่ถึง 115/144 ไม่นับและไม่ใช้เรียนรู้ (`incomplete`) |
8787
| `GET /api/battery/health?days=` | ความจุใช้ได้จริงจากช่วงคายประจุ (samples), SOH = ÷ (Ah × V ที่วัดกลาง SOC), รอบ, DoD · cache 6 ชม. |
8888
| `GET /api/device/history?sn=&days=&keys=` | แนวโน้มค่าเครื่องจาก `device_samples` (bucket ≤240 จุด/คีย์, memo 10 นาที) |
8989
| `GET /api/export?range=day\|month\|year\|all&date=` | CSV จาก D1 ของสถานีหลัก (BOM, attachment) — สำหรับสคริปต์ · ปุ่มในแอปสร้าง CSV ฝั่ง client จากข้อมูลที่แสดงอยู่ (ทุกสถานี) |

‎src/components/ForecastAccuracy.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function ForecastAccuracyCard({ skyDays }: { skyDays: number }) {
1717
<div className={`${cardP} mt-3`}>
1818
<div className="flex items-center gap-1.5">
1919
<span className="font-bold text-[16px] text-title">ความแม่นยำพยากรณ์</span>
20-
<InfoTip text={`เทียบ "พรุ่งนี้คาดผลิต" ที่ระบบบอกไว้ตอนเย็น กับที่ผลิตได้จริงในวันนั้น ${a.n} วันล่าสุด · คะแนน = 100% − ค่าคลาดเฉลี่ย · ระบบใช้ผลนี้ปรับค่าเมฆของบ้านนี้เอง (${skyDays} วันที่เรียนรู้แล้ว)`} />
20+
<InfoTip text={`เทียบ "พรุ่งนี้คาดผลิต" ที่ระบบบอกไว้ตอนเย็น กับที่ผลิตได้จริงในวันนั้น ${a.n} วันล่าสุด · คะแนน = 100% − (หน่วยที่คลาดรวม ÷ หน่วยที่ผลิตจริงรวม) · ไม่นับวันที่ข้อมูลกลางวันขาด (เช่นตัวส่งข้อมูลหลุด) · ระบบใช้ผลนี้ปรับค่าเมฆของบ้านนี้เอง (${skyDays} วันที่เรียนรู้แล้ว)`} />
2121
</div>
2222
<div className="flex items-end gap-4 mt-2">
2323
<div><div className={`text-[34px] font-extrabold tabnum leading-none ${tone}`}>{a.score ?? "—"}<span className="text-[15px] font-bold text-body ml-0.5">%</span></div><div className="text-[12px] text-muted mt-1">แม่นยำ ({a.n} วัน)</div></div>

‎src/lib/skylearn.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function learnSky(obs: SkyObs[], prior: Record<number, number> = DEFAULT_
3636
return { sky, samples, days };
3737
}
3838

39-
// Headline accuracy over evaluated days: MAPE-style score and signed bias.
39+
// Headline accuracy over evaluated days: 100 − WAPE score, signed bias and MAE.
4040
export interface AccRow { day: string; predicted: number; actual: number; }
4141
export interface Accuracy { n: number; score: number | null; bias: number | null; mae: number | null; }
4242
export function accuracySummary(rows: AccRow[]): Accuracy {

‎src/worker/index.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ async function evaluatedForecastRows(env: Env, days: number) {
713713
// the forecast — or teaching the sky model — on that would be wrong. Such days come back with
714714
// actual = null and incomplete = true.
715715
const since = Math.floor(Date.parse(cutoff + "T00:00:00+07:00") / 1000);
716+
const retainedFrom = bkkDayOf(Date.now() - (SAMPLES_RETENTION_DAYS - 1) * 86400000);
716717
const { results } = await env.DB.prepare(
717718
`SELECT f.day, f.predicted, f.cond, f.psh, f.cap_kw, d.gen AS actual, COALESCE(c.n, 0) AS cover
718719
FROM forecast_log f
@@ -722,9 +723,12 @@ async function evaluatedForecastRows(env: Env, days: number) {
722723
GROUP BY 1) c ON c.day = f.day
723724
WHERE f.day >= ? AND f.day < ? ORDER BY f.day`).bind(since, cutoff, today).all();
724725
return ((results || []) as any[]).map((r) => {
725-
const complete = Number(r.cover) >= DAYLIGHT_COVER_MIN;
726+
// samples are pruned after SAMPLES_RETENTION_DAYS: older days can't be judged, so they keep
727+
// their recorded total (the pre-coverage behaviour) instead of silently dropping out of learning
728+
const judgeable = String(r.day) >= retainedFrom;
729+
const complete = !judgeable || Number(r.cover) >= DAYLIGHT_COVER_MIN;
726730
return { day: String(r.day), predicted: Number(r.predicted), cond: Number(r.cond), psh: Number(r.psh), cap_kw: Number(r.cap_kw),
727-
actual: complete && r.actual != null ? Number(r.actual) : null, incomplete: !complete && r.actual != null };
731+
actual: complete && r.actual != null ? Number(r.actual) : null, incomplete: !complete };
728732
});
729733
}
730734
async function forecastModel(env: Env) {

0 commit comments

Comments
 (0)