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?

住宅用太陽光発電シミュレーターをMCP対応にした

0
Posted at

——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つ。

  1. UIのイベントバインドにapi_visibility="hidden"を付けて
    スキーマから隠す
  2. 公開したい関数だけを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)で、

  1. Pythonからmcp_tools.simulate_residential_pv()を直接呼ぶ
  2. 本番の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対応記事も書く予定だ。

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?