0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

国の省エネ計算(8760時間)を静的サイトのブラウザで回す設計——事前計算とTypeScript移植の切り分け

0
Last updated at Posted at 2026-09-27

はじめに

暮らしのお金を試算するサイト「いくらかかるの研究所」を個人で作っています。エアコンの電気代、給湯器、太陽光発電の回収年数などを、国の住宅省エネ基準の計算方法(建築研究所)で試算するサイトです。

計算方法には、建築研究所ほかが公開している Python 実装 pyhees(MIT ライセンス)を使っています。
ただしサイトは Astro の静的出力+Cloudflare Workers の静的アセット だけで動いていて、サーバーで Python は動かしていません。
1年8760時間の計算を、どうやってブラウザで回しているのかをまとめます。

前に書いた「正解値テスト」の記事の続きで、今回は 設計と、データの持ち方 の話です。

方針:重い計算は事前計算、軽い計算はTypeScriptに移植

計算 どこで 理由
部屋ごとの暖冷房負荷(第三章) Python で事前計算 → JSON 住宅全体の熱収支で重い。入力(地域×断熱等級)の組み合わせが少ない
給湯負荷・給湯器(第七章) Python で事前計算 → JSON 入力の組み合わせ(地域×人数×機種×節湯)が有限
ルームエアコン(第四章第三節) TypeScript に移植 機種の能力・効率が連続値で、組み合わせを事前に作れない
太陽光発電(第九章第一節) TypeScript に移植 容量・方位・傾き・パワコン効率が連続値。式も少ない
料金・回収年数・利回り TypeScript 単価や費用は利用者が入力する

判断の基準は単純で、入力が「選択肢」なら事前計算、「数値」なら移植 です。
事前計算したものは、ブラウザで単価を掛けたり、時間帯で分けたりするだけにしています。

データの持ち方

事前計算の JSON は public/data/ に置き、選んだ地域の分だけ fetch します。主なファイルの大きさです(6地域の例)。

ファイル 中身 大きさ gzip後
aircon/climate-r6.json 外気温・湿度 8760時間 102KB 37KB
aircon/loads-r6-g4.json 部屋ごとの暖冷房負荷(床面積1m²あたり) 76KB 30KB
pv/sol/r6-a3.json 日射量・太陽位置 8760時間 130KB 38KB
pv/house.json 標準世帯の家電・照明・換気 8760時間×4人分 314KB 3.4KB
ecocute/r6.json エコキュートの消費電力(月×平日休日×時刻) 167KB 28KB

工夫は3つです。

1. 0 が続く時系列は「開始時刻と値の列」で持つ

暖房負荷は夏にずっと0、冷房負荷は冬にずっと0です。そこで、0でない区間だけを [開始時刻, [値, 値, ...]] で持っています。

"LH": [[7, [0.944613, 0.192716]], [16, [0.096697, 0.153677, 0.118471]], ...]
export function decodeSparse(runs: [number, number[]][], scale = 1): Float64Array {
  const out = new Float64Array(8760);
  for (const [start, values] of runs) {
    for (let k = 0; k < values.length; k++) out[start + k] = values[k] * scale;
  }
  return out;
}

負荷は 床面積1m²あたり で持ち、ブラウザで部屋の広さを掛けます(国の計算方法でも負荷は床面積に比例します)。これで「6畳」「14畳」ごとのファイルが要りません。

2. 元のCSVの桁のまま整数にする

太陽光の日射量データは、pyhees に入っている CSV(MJ/m²・h、小数2桁)を100倍した整数で持ち、ブラウザで単位を変換しています。

iDN[t] = ((f.DN[t] / 100) / 3.6) * 1000; // MJ/(m2・h) → W/m2

2 / 100 は IEEE 754 で 0.02 と同じ値になる(正しく丸められる)ので、CSV の文字列から読んだ場合と同じ double になります。変換の順番も pyhees と同じにしているので、後述のテストで pyhees と 1e-9 の精度で一致します。

3. 繰り返しの多いデータは gzip に任せる

家電・照明・換気の消費電力は、国の標準の生活スケジュール(平日・休日のパターン)の繰り返しなので、314KB が gzip で 3.4KB になりました。独自の圧縮はしていません。

初期表示はビルド時に計算して、HTMLに入れる

Astro のページ(.astro)はビルド時に Node で動くので、初期条件の結果をビルド時に計算して、React の島に props で渡しています。

---
const defaults = defaultState(d, zone0);
const initialResult = runModel(defaults, constants, loadedFor(defaults)); // ビルド時に Node で計算
---
<Simulator client:load defaults={defaults} initialResult={initialResult} />
  • 検索エンジンや、JavaScript を読み込む前の画面にも結果が出る
  • 本文の「この試算でわかること」も同じ関数で計算して文章にする(前提を変えると本文も自動で変わる)

計算の関数は、ブラウザとビルドで同じものを使っています。データの読み込みだけを分けています(ビルドは fs、ブラウザは fetch)。

8760時間のループは十分速い

太陽光の「家で使う割合」では、1時間ごとの発電量(第九章第一節の移植)と、家の電気(家電・照明・換気+エアコン2台+エコキュート)を重ねて、8760時間それぞれで小さいほうを足しています。

let self = 0;
for (let t = 0; t < 8760; t++) self += Math.min(pv[t], load[t]);

エアコン2台分の第四章第三節の計算を含めても、Node で1回あたり数ms〜数十msでした。入力を変えるたびに計算し直しても、体感の遅れはありません。
Float64Array を使い、途中で配列を作り直さないようにしているくらいで、特別な最適化はしていません。

状態は URL に入れる(保存しない)

入力条件は history.replaceState で URL のクエリに入れています。サーバーにもブラウザのストレージにも保存しません。

  • 共有・ブックマークは URL でできる
  • 「入力した内容は保存しません」とサイトの約束に書ける
  • 既定値と同じパラメータは書かない(URL を短く保つ)

TypeScript移植の検証

移植した部分は、pyhees で直接計算した値を JSON(fixture)に書き出し、Vitest で 相対誤差 1e-9 以内 の一致を確認しています。

for (const c of golden.cases) {
  it(`${c.region}地域 A${c.a} 方位${c.azimuth}° 傾斜${c.tilt}°`, () => {
    const e = pvHourly(sol(c.region, c.a), { kw: 1, azimuthDeg: c.azimuth, tiltDeg: c.tilt, etaInR: c.eta, setup: c.setup });
    expect(Math.abs(sum(e) / c.annual - 1)).toBeLessThan(1e-9);
  });
}

さらに、事前計算の月別データ(Python)と、移植した関数で計算した合計(TypeScript)が一致することもテストしています。2つの経路で同じ答えになることを確かめておくと、どちらかを直したときの取り違えに気づけます。

まとめ

  • 入力が「選択肢」なら Python で事前計算、「数値」なら TypeScript に移植
  • 時系列は「0の区間を省く」「床面積1m²あたりで持つ」「元の桁の整数で持つ」で小さくし、あとは gzip に任せる
  • 初期表示はビルド時に同じ関数で計算して HTML に入れる。本文の数字もそこから作る
  • 移植は pyhees と 1e-9、事前計算とは合計で、2つの経路を突き合わせてテストする

実際のツールはこちらです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?