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?

FIP転シミュレーターをMCP対応にした

0
Last updated at Posted at 2026-09-07

FIP転シミュレーターをMCP対応にした

——AIエージェントに叩かせて見つかった、制度の読み違いによるバグ

何をしたか

FIP転+蓄電池の事業性シミュレーター(pv-sim-fip)を
MCPサーバーとして公開した。

シリーズ3本目の記事だが、MCP化としてはこれが1件目で、
ここで作った設計を住宅用・産業用に横展開している。

エンドポイントはここだ。

https://hachinai-pv-sim-fip.hf.space/gradio_api/mcp/

FIP転とは何か

FIT認定の太陽光発電所をFIP制度に移行し、
蓄電池を併設して収益を最大化するビジネスモデルだ。

FITは固定価格買取なので、いつ発電しても単価は同じ。
FIPは市場価格(JEPXスポット)+プレミアムで売るので、
高く売れる時間帯に売るという戦略が意味を持つ。
だから蓄電池が効いてくる。

FIP収益 = JEPX市場売電収入(30分コマ価格×売電量)
        + プレミアム(基準価格 − 参照価格)
        + 非化石証書売却収入
        − BG手数料

公開しているツール一覧

ツール名 機能
list_stations 対応地点一覧(NEDO METPV-20収録の50地点)
get_jepx_stats JEPXスポット価格の統計(LP計算なし・即答)
estimate_pv_generation 発電量のみの簡易試算
validate_fip_params パラメータの妥当性チェック・正規化・警告提示
simulate_fip_case_a ケースA: 新規FIP発電所+蓄電池
simulate_fip_case_b ケースB: 既存FIT→FIP転+蓄電池後付け
validate_grid_battery_params 系統用蓄電池の検証
simulate_grid_battery 系統用蓄電池(PVなし)単独の事業性

下2つの系統用蓄電池は別記事で扱う。

30秒で試す

MCPクライアントから

Claude Codeならこの1行で繋がる。

claude mcp add --scope user --transport http pv-sim-fip https://hachinai-pv-sim-fip.hf.space/gradio_api/mcp/

MCPクライアントを持っていなくても試せる

pip install gradio_client

まずはLP計算の要らないget_jepx_statsから。即答で返る。

from gradio_client import Client

client = Client("hachinai/pv-sim-fip")

stats = client.predict(
    area="九州",
    fiscal_years=[2023],
    api_name="/get_jepx_stats",
)

print("年平均価格      :", stats["annual_avg_yen_per_kwh"], "円/kWh")
print("日次スプレッド  :", stats["avg_daily_spread_yen_per_kwh"], "円/kWh")
print("0.01円近傍のコマ:", stats["near_zero_slots_ratio_pct"], "%")
年平均価格      : 9.14 円/kWh
日次スプレッド  : 12.94 円/kWh
0.01円近傍のコマ: 13.39 %

この年平均9.14円/kWhが、そのまま後述の参照価格になる。
九州は年間13.4%のコマで価格が0.01円まで落ちていて、
これが蓄電池アービトラージの原資になる。

本計算(ケースA)はLPを解くので30〜90秒かかる。

result = client.predict(
    station_no="82182",                       # 福岡
    ppeak_kw=5000,
    jepx_area="九州",
    jepx_fiscal_years=[2023],
    fip_base_price_yen_per_kwh=15,            # 基準価格(公表値をそのまま)
    battery_capacity_kwh=1000,
    annual_curtailment_rate_pct=12.4,
    api_name="/simulate_fip_case_a",
)

a = result["assumptions"]
print("参照価格      :", a["reference_price_yen_per_kwh"], "円/kWh")
print("実効プレミアム:", a["premium_effective_yen_per_kwh"], "円/kWh")
print("ゼロクリップ  :", a["premium_clipped_at_zero"])
print("IRR           :", result["kpis"]["project_irr_pct"], "%")
print("PVの捕捉率    :", result["annual"]["capture_rate_pct"], "%")
参照価格      : 9.14 円/kWh
実効プレミアム: 5.86 円/kWh
ゼロクリップ  : False
IRR           : -4.26 %
PVの捕捉率    : 66.2 %

指定しなかった引数はサーバ側のデフォルトが使われる。
jepx_area="東京" に変えて基準価格をデフォルトの9.6円に戻すと、
今度はpremium_clipped_at_zero: Trueが返る。
その意味は次節で説明する。

基準価格をプレミアムだと思い込んでいた

最初に、一番大きかった間違いの話を書く。

FIPのプレミアムは、こう決まる。

プレミアム = 基準価格 − 参照価格

基準価格は国が決める値(例: 2026年度・50kW以上地上設置で9.6円/kWh)。
参照価格は市場価格の平均から算定される値。
事業者が受け取る上乗せ分は、その差額だ。

ところが最初の実装では、ユーザーが入力した基準価格を
そのままプレミアムとして売電単価に足していた。

誤: 売電単価 = JEPX価格 + 基準価格 + 非化石証書 − BG手数料
正: 売電単価 = JEPX価格 + (基準価格 − 参照価格) + 非化石証書 − BG手数料

参照価格はJEPX年平均と同じオーダーなので、
この間違いは売電単価をおよそ2倍にする。

スモークテスト(九州・5MW+1MWh・基準価格15円/kWh)の
IRRは +7.11% → -3.03% に変わった。
黒字判定だった案件が赤字になる規模の誤りだ。

現在は、選択したエリア・年度のJEPX単純平均から
参照価格を自動算定し、差額をツール内部で計算している。
ユーザーは公表値の基準価格をそのまま入れればいい。

戻り値にも算定過程を明示するようにした。

{
  "assumptions": {
    "fip_base_price_yen_per_kwh": 15.0,
    "reference_price_yen_per_kwh": 9.14,
    "premium_effective_yen_per_kwh": 5.86,
    "premium_clipped_at_zero": false
  }
}

プレミアムがマイナスになったらどうするか

上の式をそのまま使うと、参照価格が基準価格を上回ったときに
プレミアムが負になる。

これは制度上あり得ない。FIPに「参照価格を超えた分を
事業者から徴収する」仕組みは無いからだ。
そこで max(0, 基準価格 − 参照価格) でクリップしている。

問題は、これが例外的なケースではないことだ。

デフォルトの基準価格9.6円/kWhは、2023〜2025年度平均では
全9エリアの参照価格を下回る(四国9.68円〜東京12.77円)。
つまりデフォルト設定では必ずこのクリップ経路を通る。

東京・2MWで実行すると、こう返ってくる。

{
  "premium_effective_yen_per_kwh": 0.0,
  "premium_clipped_at_zero": true
}
参照価格12.77円/kWhが基準価格9.60円/kWhを上回るため、
プレミアムを0円/kWhに制限しました(FIP制度上、参照価格
超過分を事業者から徴収する仕組みは存在しません)。
この条件ではFIPプレミアムによる収益上乗せは発生しません

黙って0にすると、エージェントは
「プレミアムが乗った上での試算結果」だと思って報告してしまう。
warningsとcaveatsの両方に出して、
「今回はプレミアムが効いていない」ことを必ず伝えるようにした。

実現単価(capture price)——蓄電池が効く場所は場所による

FIPで効いてくる指標をひとつ追加した。
PVが実際に捕捉できている市場価格だ。

太陽光は昼に発電する。ところがJEPXの昼間価格は
太陽光の大量導入で沈む。だからPVの実現単価は
市場の年平均を下回る。

実測するとこうなる。

エリア JEPX年平均 PVの実現単価 捕捉率
九州(2023年度) 9.14 円/kWh 6.04 円/kWh 66.2 %
東京(2023-25年度) 12.77 円/kWh 11.40 円/kWh 89.2 %

九州はPVが市場価格の3分の1を取りこぼしている。
昼にPVが集中して価格を潰しているからだ。
その代わり日次スプレッドが大きい(九州2023年度で平均12.94円/kWh、
価格が0.01円まで落ちるコマが年間13.4%)。
捕捉率が低い=蓄電池で時間をずらす価値が大きい。

東京は逆で、捕捉率89.2%と取りこぼしが少ない代わりに
スプレッドが小さく、蓄電池の出番が少ない。

同じ「FIP転+蓄電池」でも処方箋が真逆になる。
これを1つの数字で示せるようにした。

ケースAとケースBで計算の意味が変わる

FIP転には2つの入り口がある。

ケースA: 新規FIP発電所+蓄電池
最初からFIP認定を取る。投資額はPV+蓄電池。
プレミアム期間は20年。

ケースB: 既存FIT→FIP転+蓄電池後付け
既設のFIT発電所に蓄電池を足してFIPに移す。
投資額は蓄電池のみ(PVはサンクコスト)。
そして重要なのが、
FIPプレミアムの交付期間はFIT残存期間を引き継ぐこと。
FIT20年契約の12年目に転換すれば、
プレミアムは残り9年しか出ない。

だからケースBのIRRは、
「FIP転した場合」と「FIT継続した場合」の
増分キャッシュフローで評価している。
蓄電池の追加投資に対するリターンを見るのが目的だからだ。

戻り値でも effective_premium_years: 9 のように
実際に効くプレミアム年数を返す。

「FIT転しない方がいい」と正直に返す

ケースBを実測するとこうなる。

条件: 東京・2MW・FIT単価36円/kWh・12年目にFIP転・蓄電池1MWh

項目 値
FIT継続の20年運営CF 842,783,951 円
FIP転+蓄電池の20年運営CF 343,213,824 円
差額 −499,570,127 円
蓄電池投資を含めた損得 −567,570,127 円
増分IRR 計算不能(全期間マイナス)

FIT36円は今のJEPX価格(東京平均12.77円)の3倍近い。
プレミアムもゼロにクリップされる条件なので、
FIT期間が残っているうちに転換する意味は全く無い。

シミュレーターとしては当たり前の結果を出しているだけだが、
「自社ツールで試算したら不利と出た」を隠さず返すことは
けっこう大事だと思っている。
特にエージェント経由だと、
返した数字がそのまま人間の判断材料になる。

なお、増分IRRが計算不能なときは、
project_irr_pct に無理やり数字を入れず null を返している。
IRRは全キャッシュフローが同符号なら存在しない。
存在しないものを返さないのは当然だが、
Newton法で適当な初期値から回すと
それらしい値が出てしまうことがあるので注意がいる。

(そもそも蓄電池のCFは投資→収益→廃止措置で
符号が2回変わるため複数の解を持ちうる。
粗くスキャンして全ての符号変化区間を二分法で解き、
ゼロに最も近い根を返す実装にしている。)

ケースAの実測値

条件: 福岡・5MW+蓄電池1MWh・九州エリア・
基準価格15円/kWh・JEPX 2023年度・出力制御12.4%

項目 値
年間発電量 5,036,405 kWh
参照価格(自動算定) 9.14 円/kWh
実効プレミアム +5.86 円/kWh
PVの実現単価 / 捕捉率 6.04 円/kWh / 66.2 %
出力制御量(蓄電池なし) 624,514 kWh
出力制御量(蓄電池あり) 445,850 kWh
出力制御回避量 178,665 kWh
蓄電池の増分収益(LP理論値) 4,314,926 円/年
蓄電池の増分収益(実現率85%適用後) 3,667,687 円/年
Project IRR −4.26 %
NPV −406,475,690 円

LPの理論値をそのまま返さない

蓄電池の充放電最適化は、30分×365日=17,520コマの
線形計画問題として解いている(PuLP+CBC)。

ただしこのLPは1年分のJEPX価格を完全予見する前提だ。
実運用は前日の価格予測に基づくので、必ずこれを下回る。

そこで蓄電池の増分収益にだけ
arbitrage_realization_rate_pct(デフォルト85%)を掛けている。
ベースラインの市場直売収入には掛けない。
蓄電池が生む増分だけが予測精度の影響を受けるからだ。

透明性のため、理論値と補正後の値を両方返す。

{
  "battery_annual_increment_theoretical_yen": 4314926,
  "battery_annual_increment_realized_yen": 3667687,
  "arbitrage_realization_rate_pct": 85.0
}

85%という数字自体に強い根拠があるわけではない。
だからこそ理論値を隠さず、
利用者が自分の見立てで割り戻せるようにしている。

出力抑制には2種類ある

実装していて気づいた非対称性の話も書いておく。

出力を絞る理由は2つある。

種別 発生条件 蓄電池で回避できるか
強制出力制御 系統からの指令 ○ 充電して制御解除後に売る
経済的自主抑制 売電単価が負のコマ × 蓄電池があっても発生する

LPは元々どちらも選べたのだが、
比較対象の「蓄電池なし」ベースラインが
損失が出る価格帯でも全量売電していた。
つまり「自主抑制できること自体の価値」が
蓄電池の増分収益に混ざり込んでいた。

東京・プレミアム0の条件で増分の約2%、
プレミアムが負になる条件では約1割がこれだった。
現在は両者とも売電単価が負のコマで抑制する。

集計も分けている。
蓄電池の「出力制御回避量」は強制分だけで評価する。
自主抑制分まで足すと回避量が負値になって
意味が取れなくなるからだ。

{
  "forced_curtailed_with_battery_kwh": 445850,
  "economic_curtailed_with_battery_kwh": 0,
  "curtailment_avoided_kwh": 178665
}

三菱総研の試算と突合する——「一致しない」の中身を分解する

経産省の検討会に出ている三菱総研の試算
(2024年度 第5回 定置用蓄電システム普及拡大検討会 資料3)と
数字を突き合わせる検証を入れている。

最初は「1.4pt以内で一致」していた。
ところが一次資料を精読したら、こちらの前提が3つ間違っていた。

  1. 基準価格15円をプレミアムだと解釈していた(前述のバグ)
  2. PCSの時間率を0.25Cだと思っていたが、資料には3時間率(0.333C)とあった
  3. JEPXを3年平均で使っていたが、資料は2023年度単年だった

3つとも直した結果、乖離は-7〜-9ptに拡大した。
以前の一致は、3つの誤りが偶然打ち消し合っていただけだった。

ここで大事なのは、拡大した後の乖離は
こちらの実装ミスではない
ということだ。
残差の内訳は分解できている。

収入段階で比べると、当社75,317円/kWh/20年に対し
三菱総研123,060円/kWh/20年で、比率61.2%。
IRRの差はほぼ全部この収入差から来ている。その内訳は3つ。

1. BG手数料2.0円/kWh(残差の約11%)
三菱総研の算定諸元にこの費用は載っていない。
こちらが保守的に計上しているだけで、実装の誤りではない。
20年で約10,900円/kWh分に相当する。

2. プレミアムの粒度(影響は軽微。検証済み)
三菱総研は月別プレミアム、こちらは年間一律値を使っている。
一見効きそうなので九州2023年度を月別集計して実測した。

参照価格の取り方 参照価格 実効プレミアム
単純年平均(現行方式) 9.14 円/kWh +5.86 円/kWh
発電量加重平均(月別化した場合の近似) 8.93 円/kWh +6.07 円/kWh

月別プレミアム自体は+2.87円(11月)〜+8.98円(6月)と
3倍以上ばらつくのに、加重平均すると差は+0.20円/kWhしかない。
九州では発電量の多い4〜6月(参照価格が安くプレミアムが厚い)と
同じく発電量の多い8〜9月(参照価格が高くプレミアムが薄い)が
相殺してしまうためだ。
月別化は有力に見える論点だが、実測の結果見送りを確定した。

3. 出力制御の配分機構の違い(残差の主因)
1と2を足しても残差の約28%にしか届かない。残りがこれだ。

三菱総研は「その日の最安コマ(0.01円)で充電して
逸失プレミアムを回収する」設計なので、
蓄電池のスループットがほぼ全量プレミアム回収に直結する。

一方こちらは出力制御を月×時間帯の合成確率テーブルで配分しており、
蓄電池の充電窓と必ずしも一致しない。
実測すると、当社の蓄電池年間放電量297,679kWhのうち
出力制御回避が59.8%(178,132kWh)、残り約4割が純アービトラージだった。

つまりモデルの機構が違うだけで、
どちらかの計算が間違っているわけではない。
全CAPEX水準で乖離がほぼ一定(-7〜-9pt)だったことも、
特定条件のバグではなく一律のスケール差であることを裏付けている。

そして、この差を埋めにはいかないと決めた。
三菱総研の前提(1日1サイクル保証+最安コマ全量捕捉)は
かなり楽観的で、そこに数字を寄せるのは筋が悪い。

出力制御を合成テーブルではなく
JEPXの安値コマそのものから駆動する方式への変更は検討している。
ただしそれは**「三菱総研に数値を合わせるため」ではなく
「モデルの現実性そのものの改善」として**やるべきものだ。

公表試算と合わないとき、
慌てて自分の数字を寄せにいくのが一番まずい。
差の出どころを分解して、
どちらの前提がどう違うかまで書ければ、
「合わない」は失敗ではなく情報になる。

まとめ

  • FIP転シミュレーターをMCPサーバーとして公開した(8ツール)
  • 基準価格とプレミアムを取り違えていたバグを直した。
    IRRが+7.11%→-3.03%変わる規模の誤りだった
  • プレミアムのゼロ下限クリップは例外処理ではなく、
    デフォルト設定では必ず通る経路。だから必ず警告を返す
  • PVの実現単価(捕捉率)を出力に追加した。
    九州66.2%・東京89.2%——蓄電池の処方箋が真逆になる
  • 「FIT継続の方が有利」という結果もそのまま返す。
    増分IRRが存在しないときはnullを返す
  • LPの完全予見な理論値と、実現率85%を掛けた値を両方返す
  • 公表試算との突合で乖離が拡大したが、
    その内訳(BG手数料・プレミアム粒度・出力制御の配分機構)を
    分解し、実装ミスではなくモデル前提の違いだと確認した。
    数字を寄せにいく対応はしていない
  • MCPエンドポイント:
    https://hachinai-pv-sim-fip.hf.space/gradio_api/mcp/

このSpaceには太陽光を伴わない系統用蓄電池単独の
試算ツールも入っている。UIを持たず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?