——LPを使わない住宅用ならではの設計と、モデルの限界をcaveatsで返す話
何をしたか
住宅用PV需給シミュレーター(pv-sim-gh)を
MCPサーバーとして公開した。
姉妹プロジェクトのpv-sim-fip(FIP転+系統用蓄電池)に続く
2件目のMCP化で、その後に産業用(pv-sim-biz)も対応済みだ。
「validateしてからsimulateする」2段階プロトコルは
fip発の設計をそのまま踏襲している。
エンドポイントはここだ。
https://hachinai-pv-sim-gh.hf.space/gradio_api/mcp/
公開しているツール一覧
接続すると以下の4ツールが使える。
| ツール名 | 機能 |
|---|---|
list_stations |
対応地点一覧(NEDO METPV-20収録の50地点)を返す |
estimate_pv_generation |
発電量のみの簡易試算 |
validate_residential_params |
入力パラメータの妥当性チェック・正規化・警告提示 |
simulate_residential_pv |
需給バランス・蓄電池・経済性の全計算 |
ツール名はSpace名で自動的に名前空間が付く
実際にMCPクライアントから見えるツール名は、
上の素の名前ではなくSpace名を冠したものになる。
本番のスキーマを叩くとこう返ってくる。
pv_sim_gh_list_stations
pv_sim_gh_estimate_pv_generation
pv_sim_gh_validate_residential_params
pv_sim_gh_simulate_residential_pv
これはGradioが勝手にやってくれる。
おかげで住宅用・産業用・FIP転の3つのMCPサーバーに
同時接続してもlist_stations同士が衝突しない。
3つのシミュレーターを束ねるハブアプリを作ろうかと
最初は考えたのだが、この仕様のおかげで
「エージェント側が3サーバーに同時接続する」だけで
統合体験が成立してしまった。ハブは作らなくてよかった。
住宅用はLPを使っていない
産業用(biz)とFIP転(fip)の蓄電池最適化は
PuLP+CBCによる線形計画法で、30分×365日=17,520コマを
一気に解いている。
住宅用は違う。app.pyにPuLPは一度も出てこない。
30分コマを頭から順に舐めるだけの貪欲法だ。
余剰電力(PV > 需要)→ 蓄電池充電 → 系統へ売電
不足電力(需要 > PV)→ 蓄電池放電 → 系統から購入
これで十分な理由は、住宅用には
最適化すべき対象がほとんど無いからだ。
産業用には基本料金(契約電力=年間最大デマンド×単価)があり、
「どのコマで放電すればピークを削れるか」を
年間通して見渡さないと決められない。だからLPが要る。
住宅用に基本料金のデマンド課金は無い。
従量料金の単価も三段階制か時間帯別くらいで、
「余ったら貯める、足りなければ出す」以上に賢い戦略がない。
LPを持ち込んでも解は変わらず、計算時間だけが伸びる。
結果として住宅用は数秒で応答が返る。
AIエージェントから対話的に何度も叩かれる用途では、
これはむしろ効いてくる。
実際に動かしてみる
MCP対応クライアントに以下を貼り付けると動く。
pv-sim-gh(住宅用太陽光発電・蓄電池シミュレーター)の
MCPサーバーに接続してテストしたい。
1. 以下のリモートMCPサーバーを追加してください:
名前: pv-sim-gh
URL: https://hachinai-pv-sim-gh.hf.space/gradio_api/mcp/
2. 接続できたら、利用可能なツール一覧を確認してください
(list_stations, estimate_pv_generation,
validate_residential_params, simulate_residential_pv
の4つ。実際のツール名には pv_sim_gh_ という
接頭辞が付いているはずです)
3. まず list_stations を呼び、東京(station_no="44132")が
一覧に含まれることを確認してください。
4. 次の条件で validate_residential_params を呼び、
正規化されたパラメータと警告(あれば)を提示してください:
- station_no: "44132"(東京)
- faces: [{"ppeak_kw": 5.0, "tilt_deg": 30.0,
"azimuth_deg": 180.0, "pcs_limit_kw": 5.5}]
- demand_preset: "gas_electric"
- battery_capacity_kwh: 5.0
5. 検証結果を私に見せて確認を求めてから、
同じ条件で simulate_residential_pv を実行してください
(validateで確認→simulateで本計算、の2段階を必ず踏むこと)。
6. 結果から以下を報告してください:
- annual.generation_kwh(年間発電量)
- annual.self_consumption_rate_pct(自家消費率)
- annual.export_kwh / import_kwh(売電量・買電量)
- annual.battery_charge_kwh / battery_discharge_kwh
- economics.gas_electric_scenario.payback_years
- economics.all_electric_scenario.payback_years
- caveats に記載されている注意事項の内容
7. 最後に battery_capacity_kwh を 0 に変えて再実行し、
蓄電池あり/なしで自家消費率と投資回収年数が
どう変わるかを比較してください。
(回収年数が悪化するはずです。その理由が caveats に
書いてあるので、そこまで込みで説明してください)
実測値
東京・南向き5kW・傾斜30°・電気+ガス併用(5,000kWh/年)・
蓄電池5kWhで実行した結果がこれだ。
| 項目 | 値 |
|---|---|
年間発電量 generation_kwh
|
5,416 kWh |
設備利用率 capacity_factor_pct
|
12.37 % |
自家消費率 self_consumption_rate_pct
|
55.4 % |
自給率 self_sufficiency_rate_pct
|
60.0 % |
売電量 export_kwh
|
2,304 kWh |
買電量 import_kwh
|
1,999 kWh |
蓄電池充電量 battery_charge_kwh
|
1,144 kWh |
蓄電池放電量 battery_discharge_kwh
|
1,032 kWh |
往復ロス battery_round_trip_loss_kwh
|
112 kWh |
| 回収年数(電気+ガス併用) | 17.1 年 |
| 回収年数(オール電化) | 14.7 年 |
戻り値の構造はこうなっている。
{
"assumptions": { "...": "正規化済みの全入力パラメータ" },
"annual": {
"generation_kwh": 5416,
"capacity_factor_pct": 12.37,
"self_consumption_rate_pct": 55.4,
"self_sufficiency_rate_pct": 60.0,
"export_kwh": 2304,
"import_kwh": 1999,
"battery_charge_kwh": 1144,
"battery_discharge_kwh": 1032
},
"economics": {
"gas_electric_scenario": { "payback_years": 17.1, "...": "" },
"all_electric_scenario": { "payback_years": 14.7, "...": "" },
"pv_initial_cost_yen": 2025000,
"battery_cost_yen": 750000
},
"caveats": ["..."]
}
蓄電池を入れたら回収年数が伸びた——これはモデルの限界だ
上の条件でbattery_capacity_kwhだけを0と5.0で
切り替えると、こうなる。
| 項目 | 蓄電池なし | 蓄電池5kWh |
|---|---|---|
| 自家消費率 | 36.3 % | 55.4 % |
| 売電量 | 3,448 kWh | 2,304 kWh |
| 買電量 | 3,031 kWh | 1,999 kWh |
| 初期投資 | 1,275,000円 | 2,025,000円 |
| 回収年数(電気+ガス併用) | 10.0 年 | 17.1 年 |
自家消費率は36.3%→55.4%に上がっているのに、
投資回収年数は10.0年→17.1年に悪化している。
これはバグではなく、既存の経済性計算エンジンの
仕様上の限界だ。蓄電池のコストは投資額に加算されるが、
節約額の側はPV単体の自家消費率で計算されており、
蓄電池による自家消費率の向上が経済性側に
反映されていない。
問題は、これがAIエージェント経由だと危ないことだ。
人間がUIを触っていれば「おかしいな」と気づくが、
エージェントは返ってきた数字をそのまま信じて
「蓄電池を入れると損です」と要約してしまう。
そこでcaveatsにこう書いて必ず返すようにした。
{
"caveats": [
"本結果は投資判断の参考情報であり、収益・自家消費率・投資回収年数を保証するものではありません",
"経済性比較(電気+ガス併用 vs オール電化)は自家消費・蓄電池シミュレーションの需要選択とは独立しており、demand_presetの選択に関わらず常に両シナリオを計算して返します",
"経済性比較は蓄電池による自家消費率向上を反映していません(battery_cost_yen_per_kwhを投資額に加算するのみで、売電収入・買電削減効果はPVのみの自家消費率で計算されます)。蓄電池ありの正確な自家消費率・売電量は annual セクションの battery_* 系フィールドを参照してください(既存app.pyの仕様。経済性エンジンとの統合は未実装)"
]
}
「どのフィールドが信用できて、どのフィールドに
どういう限界があり、代わりに何を見ればいいか」
までを構造化データとして返す。
MCP化して一番効いたのはここかもしれない。
UIなら注釈を小さく添えれば済むが、
エージェント相手だと機械が読める形で
限界を渡さないと、そのまま誤った結論が
人間に届いてしまう。
Gradioの自動公開で気をつけたこと
ここは誤解しやすいので正確に書いておく。
デプロイしただけではMCPエンドポイントは生えない。
launch()に明示的に指定する必要がある。
demo.launch(mcp_server=True)
そしてmcp_server=Trueにすると、今度は
UI上のイベントハンドラまでAPIとして公開されうる。
ボタンのクリックハンドラや入力欄のchangeイベントが
そのままツールとして外から見える状態になる。
対策は2つ。
- UIのイベントバインドに
api_visibility="hidden"を付けて
スキーマから隠す - 公開したい関数だけを
gr.api()で明示的に登録する
# UIの内部コールバックは隠す
btn.click(fn=..., inputs=..., outputs=..., api_visibility="hidden")
# 公開するツールだけを明示登録
gr.api(mcp_tools.list_stations, api_name="list_stations")
gr.api(mcp_tools.estimate_pv_generation, api_name="estimate_pv_generation")
gr.api(mcp_tools.validate_residential_params, api_name="validate_residential_params")
gr.api(mcp_tools.simulate_residential_pv, api_name="simulate_residential_pv")
mcp_server=FalseにすればMCP自体が無効になるが、
それは「公開しない」という選択であって
「一部の関数だけ隠す」手段ではない。
粒度の制御は上の2つでやる。
pv-sim-ghでは公開する4ツールだけをgr.api()で登録し、
DBへの直接アクセスや内部計算関数は公開対象外にしている。
ローカルと本番で数値が一致するか確認した
MCPは標準プロトコルなので、どこから呼んでも
同じ結果になるはずだ。実際に確かめた。
同一条件(東京・5kW・南向き・蓄電池5kWh)で、
- Pythonから
mcp_tools.simulate_residential_pv()を直接呼ぶ - 本番のHugging Face Spacesに対してHTTP経由で呼ぶ
を比較したところ、発電量・自家消費率・売電量・買電量・
蓄電池充放電量・回収年数の全項目が完全一致した。
産業用(biz)でも同じ検証を直接呼び出し・
ローカルHTTP・本番・Codex経由の4パターンでやっていて、
そちらも全項目一致している。
なぜMCP対応にしたか
通常のWebアプリはブラウザで操作する前提だ。
MCPに対応することで、シミュレーターが
AIエージェントの「道具」として機能するようになる。
ユーザー
↓(自然言語で依頼)
AIエージェント
↓(MCPでツールを呼び出す)
pv-sim-gh(JIS C 8907計算エンジン)
↓(JSON形式で結果を返す)
AIエージェント
↓(日本語で要約して回答)
ユーザー
「東京で南向き5kWのパネルを載せて
蓄電池5kWh入れたら何年で回収できますか」
という問いにAIが計算ツールを呼び出して
答えを返す、というワークフローが成立する。
しかもツール名が自動で名前空間化されるので、
住宅用・産業用・FIP転の3つを同時に繋いで
「同じ条件を住宅用と産業用の両方で試算して比較して」
といった横断的な使い方もできる。
まとめ
- 住宅用シミュレーターをMCPサーバーとして公開した(4ツール)
- 住宅用に基本料金のデマンド課金が無い以上、
蓄電池制御にLPは不要——貪欲法で十分だと判断した - 経済性エンジンが蓄電池を反映しないという既存の限界を、
caveatsで機械可読な形で明示的に返すようにした。
エージェント相手だと注釈は構造化データで渡さないと届かない -
mcp_server=Trueは明示指定が必要で、
公開粒度はapi_visibility="hidden"とgr.api()で制御する - ツール名はSpace名で自動的に名前空間化されるので、
3サーバー同時接続でも衝突しない。ハブアプリは不要だった - ローカル直接呼び出しと本番HTTPで全項目が完全一致することを確認した
- MCPエンドポイント:
https://hachinai-pv-sim-gh.hf.space/gradio_api/mcp/
FIP転(pv-sim-fip)のMCP対応記事も書く予定だ。