料金計算エンジン 設計壁打ち

2026-08-12 ・ 第4版(全16件 決定済み・実装仕様として確定)

チャーター料金の計算を 自前の料金エンジンとして作り直す ための設計資料。作るものは ①パラメータ設定ページ ②料金シミュレーションページ の2つで、置き場所は当面 mcj-companyapps/pricing。将来は刷新後のHP・予約管理・代理店ポータル・オーナーポータルから叩かれる 社内外まとめて1本の料金API にする。

第1版で挙げた論点10件はすべて方針が決まった。いちばん大きいのは「HPとの金額一致を条件にしない」こと。料金改定を前提にするので、エンジンは HPの再現装置ではなく、これからの料金の正 になる。飛行時間も 空中ETEを5分切り上げ という自前の定義で持つ。

細部6件も決着し、未決はゼロ。端数はHPの実挙動を実測して確認したうえで 1,000円未満切り捨て・丸めてから貸切料金を充当 に決めた(4章)。この版をそのまま実装仕様として使える状態。次は12章 Phase1。

0. 決定サマリ
  • 課金する飛行時間=空中ETE(風補正・空港ファクター込み)を LEGごとに5分切り上げ。ブロックタイムは使わない。往復で時間が違うのはそのまま出す。
  • テクランは料金計算に持ち込まない。燃料を度外視して直行したときの空中ETEで計算する。運航上テクランが要るかどうかは別の判断(配置エンジンの仕事)。
  • 連続5日以上も計算する。HPの4日上限が外れる。むしろ長期の受注を伸ばしたいので、そこで見積が出ないのは機会損失。
  • エンジンは定価のみを返す。代理店割引・キックバックは代理店ポータル側で管理する。
  • 通貨は当面JPYのみ。USDは将来やるので、データモデルには通貨の軸だけ入れておく。
  • 遠域ゾーンは空港ごとに選ぶ。海外運航が始まり空港もゾーンも増えていく前提で、地理条件のハードコードにはしない。
  • 版(プライスブック)+有効化フラグを持つ。見積時点の額がそのまま請求額なので、見積を出した瞬間の版と明細を焼き付ける。
  • 編集権限は最初は不要。ただし後から挟むので、誰がいつ何を変えたかの記録は最初から取る。
  • 置き場所=いまは companyapps、本格運用時に切り離す。パラメータのD1は mcj-master に相乗り。(9章)
  • HPとの金額一致は条件にしない。companyapps 内で十分検証してから公開へ進む。現行料金からの変更を予定している。
  • 飛行料金は1,000円未満を切り捨て、その丸め後の値で貸切料金を充当する(HPの実挙動と同じ順序)。
  • 型式ごとに「社内で使える/公開してよい」の2フラグを持つ。事業機化が未定の機体を公開しないための栓。
  • 最初の投入は現行のままのパラメータ。改定は次の版で。
1. 作るもの

① パラメータ設定ページ /pricing/params

  • 型式ごとに 基本料金・貸切料金/日・飛行単価①②・海外運航料金・早朝/深夜料金 を設定
  • 遠域ゾーンを自由に追加。ゾーンごと × 型式ごとに 遠域料金・施設料
  • 空港ごとに 空港内送迎料金・所属遠域ゾーン を割当(マスタの空港一覧から検索)
  • 税率・丸め規則・版の作成と有効化
  • 変更は 保存の前に差分プレビュー、保存後は版として残る

② 料金シミュレーションページ /pricing/sim

  • 型式・日程・区間(複数日・複数LEG)・早朝深夜を入れると内訳つきで金額が出る
  • 飛行時間は flight-calc.js が算出(直行・空中ETE・5分切り上げ)
  • 内訳の各行が どのパラメータから来たかを表示(値付けを説明できる状態にする)
  • 版どうしの比較モード — 現行料金と改定案で代表ケースがいくら変わるかを一覧(13章)
この2ページは本番影響ゼロ。既存の予約管理・代理店ポータルの見積経路には一切つながない。つなぐのは第4フェーズ。
2. いまの料金の出所 — 全部HPをスクレイプしている
消費者(5系統) 代理店ポータル 見積 mcj-sales → /api/agency/charter-quote 社内見積(予約管理) /api/internal/charter-quote オーナーポータル 見積 /api/owner/charter-quote LINE秘書 見積 mcj-shift 内で直接 空港マスタ同期 mcj-master/tools/hp-airports/ Service Binding mcj-shift hp-calculator.ts フォームPOST → HTML パース 毎回 scrape mcjet.jp /rc/jpy/ さくらVPS・nginx・CGI Git管理外/FTPで直更新 ここが止まると社内業務が同時に止まる 既知の制約 ・ご利用日数は最大4日(5日以上は計算不能) ・パース失敗時に例外を出さず合計0を返す ・タイムアウト/リトライなし
図1:いまの料金の出所。5系統すべてが1本のスクレイプに乗っている。HP刷新で /rc/ を触ると全部止まる。

自前化の一番の理由は、HP刷新でこの依存が切れること。HP側は同僚が担当する予定で、/rc/ を残すか引っ越すかも未決。料金エンジンを自前で持てば、HPは「料金APIを叩くだけの画面」になり、/rc/ のCGIごと廃止できる。

副次的に、4日上限が外れる(連続5日以上を営業部へ回している運用が不要になる)・失敗が静かに0円にならない料金改定をこちらの都合でできるようになる。

なお旧LINE見積Bot mcj-quote/workers/src/pricing.ts にも自前実装があるが、C510定数のみ・遠域を全LEGの発着地で加算しており料金表とズレている。流用しない。新エンジン完成時に撤去対象。
3. 料金体系(現行HPの写経 / 2026-08-12 時点)

これは 移行の出発点であって、目標ではない。現行から料金を変更する予定なので、この値は「最初に投入する版(2026-04)」として入れておき、改定は新しい版で行う。

項目Mustang(C510)CJ4(C25C)課金単位適用条件
基本料金700,0001,050,000円 / 件1件につき1回
チャーター(貸切)料金600,000750,000円 / 日飛行料金に充当される(4章)
飛行単価①400,000600,000円 / 時間国内離発着
飛行単価②500,000700,000円 / 時間出発地または到着地が海外
海外運航料金250,000250,000円 / 件国際運航時(HPの表は両型式で結合セル=同額。2026-08-13 確認)
早朝出発料金280,000280,000円 / 件初日の出発が 8:59 まで
深夜到着料金280,000280,000円 / 件最終日の到着が 21:00 以降
遠域発着料金(対象地域)C510CJ4
北海道120,000200,000
奄美以南200,000320,000
那覇以南350,000520,000
台湾500,000750,000
韓国250,000350,000
グアム・サイパン750,000
香港・マカオ1,300,000
遠域発着料金は実質フェリー料金。適用は一連の 発地と最終着陸地 のみで、途中で降りた遠域では発生しない。同じ空港・同じゾーンでも重複排除しないので、遠域が起終点なら往復ぶん2回乗る。海外運航の開始でここが増えていく
空港内送迎料金金額
千歳・成田・羽田・関西・福岡・鹿児島・那覇120,000
中部150,000
海外空港施設利用料金・送迎料金金額
台湾650,000
韓国500,000
グアム・サイパン500,000
香港1,500,000
マカオ550,000
消費税 10%。表示はすべて税抜、最後に一括で課税。
HP表記の「Mustang / CJ4」は、社内マスタの型式コードでは C510 / C25C。エンジンのキーは マスタ側の type_code に統一し、表示名だけHP表記に寄せる。G550(N679MS)・HA420 は料金表にないので当面 料金未設定=見積不可(→ 11章の確認5)。
4. 計算式
// 1件(=連続して飛ぶ日の塊 ひとかたまり)あたり・税抜
合計税抜 =
    基本料金                                       // 件に1回
  + Σ [ 飛行料金(その日) + max(0, 貸切料金/日 − 飛行料金(その日)) ]   // ← 明細の「貸切料金」
  + Σ使用空港(重複排除) 空港内送迎料金
  + 遠域発着料金(発地) + 遠域発着料金(最終着陸地)        // 実質フェリー料金。同じ空港でも重複排除しない
  + 海外運航料金(国際運航なら件に1回)
  + Σ海外空港(重複排除) 海外空港施設利用料金
  + 早朝出発料金 + 深夜到着料金

// その日の飛行料金(★丸めてから貸切に充当する。順序が金額に効く)
飛行料金(日) = floorN( Σその日のLEG ( 課金時間(LEG)[分] ÷ 60 × 飛行単価 ) )
             飛行単価 = 出発地か到着地が海外なら hourly_intl(単価2) / それ以外は hourly_domestic(単価1)
                        // 判定はLEG単位(羽田→那覇は単価1・那覇→台北は単価2)
             floorN   = 1,000円未満切り捨て(HPの実挙動に合わせる。単位はパラメータで変更可)

// 課金時間の定義(第2版で確定)
課金時間(LEG) = ceil5( 空中ETE(直行・風補正込み) )
    空中ETE = 大圏距離 ÷ 巡航速度 × 60 + 固定分 + 風補正
              固定分   = 20分(羽田・成田の発着は30分)
              風補正   = α(季節,型式) × 東西成分 ÷ 100     // 東向きは短縮・西向きは延長
    ceil5(x) = 5分単位で切り上げ                            // 1:02 → 1:05 / 0:58 → 1:00
    // taxi は含めない(ブロックタイムは使わない)
    // テクランは考慮しない(燃料度外視で直行したときの値)

// 早朝・深夜は時刻が入力されているときだけ判定する(未入力なら加算しない)
// 風補正の季節αは各LEGの運航日の月。運航日未定の問い合わせでは当月を使う

合計税込 = 合計税抜 × (1 + 税率)

いちばん間違えやすいのは貸切料金の充当。「総額に対して」ではなく「日ごとに」充当する。飛ぶ日は飛行料金で吸収され、吸収しきれない差額だけ貸切料金が乗る。飛ばない中日は満額が乗る。

そしてもう一段、順序の罠がある。HPは飛行料金を丸めてからその値で貸切料金を充当している(下表)。先に充当してから丸めると金額が変わるので、この順序を守る。

HPの端数処理(2026-08-12 実測)
区間(C510・単価400,000/時)HPの飛行時間理論値(丸め前)HPの飛行料金HPの貸切料金検算
羽田 → 大島0:25166,666.67166,000434,000600,000 − 166,000
羽田 → 松本0:35233,333.33233,000367,000600,000 − 233,000
羽田 → 八丈島0:45300,000.00300,000300,000端数なし
羽田 → 南紀白浜1:05433,333.33433,000167,000600,000 − 433,000
名古屋 → 仙台1:10466,666.67466,000134,000600,000 − 466,000
羽田 → 稚内2:25966,666.67966,0000飛行料金が60万超
羽田 → 石垣4:101,666,666.671,666,0000同上
読み取れること2つ。①HPは1,000円未満を切り捨て、その丸め後の値で貸切を充当している。②HP側の飛行時間もすでに5分刻み(0:25 / 0:35 / 1:05 / 2:25 / 4:10)で、5分切り上げという決定はHPの見せ方とも整合する。
なお CJ4 は単価600,000/時=1分あたり10,000円ちょうどなので、そもそも端数が出ない。端数が問題になるのは C510(単価①400,000=6,666.67/分)と海外発着の単価②(500,000=8,333.33/分)。
小計は各項目が1,000円単位に揃うので、消費税は必ず100円単位になる(例 1,906,000 × 0.1 = 190,600)。
貸切料金/日 = 600,000(C510) 1日目 羽田→大阪 1:05 飛行料金 433,000 貸切 167,000 ← 差額だけ乗る 2日目 飛行なし 貸切 600,000(満額) ← 中日は丸ごと乗る 別の日 羽田→福岡 2:00 飛行料金 800,000 ← 60万を超えるので 貸切 0円
図2:貸切料金は「その日の飛行料金で埋まらなかったぶん」だけ乗る。
  • 「件」の定義=連続して飛ぶ日の塊。連続する日は機材を通しで確保する=1件なので割らない。日ごとに割ると基本料金が件数ぶん乗る。
  • エンジンの責務は「1件を正しく計算すること」まで。塊が複数あるときの「まとめるか分けるか」の最適化は、エンジンを何度か呼ぶ上位レイヤの仕事として分離する。
  • 遠域は 発地と最終着陸地のみ=実質フェリー料金。羽田→下地島→羽田は起終点が羽田なので該当なし、下地島→羽田→下地島は起終点が下地島なので下地島の遠域料金×2。羽田→新千歳の片道は着地ぶんの1回で、往復を1件にまとめると0回・片道2件に割ると各1回。この非線形があるので、まとめ/分けの損得は静的ルールで決め打ちせず必ず両方計算して比べる。
  • 5日以上の制約はなくす。式はそのまま日数ぶん延びる(貸切料金/日が積み上がる)。長期案件で見積が出せるようになる。
5. 飛行時間の引き方

使うもの = FlightCalc.ete() 一本

  • 入力は 発地・着地・型式・運航月 だけ。返る eteMin(空中ETE)を5分切り上げて使う
  • block(taxi込)は使わない
  • 諸元は cfgFromMaster()、空港は airportsFromMaster()mcj-master が単一情報源
  • 座標さえマスタにあれば、新規空港も海外空港もそのまま計算できる

使わないもの = assessItinerary()

  • テクラン計画・燃料・滑走路長・代替空港の判定は料金には持ち込まない
  • おかげでエンジンの入力が激減し、燃料モデルの精度が料金に影響しなくなる
  • 「運航できるか」は配置エンジン、「いくらか」は料金エンジン、と責務が割れる
  • 結果として、テクランが要る区間でも料金は直行相当=差分は会社が飲む

コピーを増やさないこと。flight-calc.js はいまリポジトリ内に中身が完全に同一のコピーが3つある(mcj-reservation/frontend/=原本・Workerが直接 import、mcj-sales/frontend/mcj-companyapps/frontend/flight-calc/)。

1
仮フェーズ(companyapps)=同一Pages内で読む。<script src="/flight-calc/flight-calc.js"> で既存ファイルをそのまま参照する。コピーは増えないし、今日から動く。
2
本体化フェーズ=共有コアにする。原本を shared/flight-calc/ に1つ置き、各プロジェクトはそこを import。そのPRで既存3コピーもまとめて解消する。
×
予約管理の GET /api/feasibility を実行時に叩く案は採らない。将来は逆に予約管理が料金エンジンを呼ぶので相互依存になる。
往復で飛行時間が変わる(東向きは追い風で短く、西向きは向かい風で長い)点はそのまま出す。羽田→那覇 2:50 / 那覇→羽田 2:35 のように往路と復路で飛行料金が違う。実態に忠実な値なので、見積書の見せ方(LEGごとに時間を出すか、合計だけ出すか)で吸収する。
6. パラメータのデータモデル

設計の芯は 「遠域ゾーン」を実体として持ち、空港をそこに割り当てること。海外運航が始まって空港もゾーンも増えていく前提なので、地理条件のハードコードにはしない。ゾーンを足す → 空港を割り当てる の2操作だけで対応できる状態にする。

もうひとつは 版(プライスブック)+有効化フラグ。見積時点の額がそのまま請求額になるので、見積を作った瞬間に「どの版で出したか」と明細を焼き付ける。改定は「新しい版を作る → 内容を詰める → 有効化する」の3手。

テーブル役割
price_booksid / name / currency / effective_from / is_active / status(draft·active·archived) / note / created_by / created_atcurrency は当面 JPY 固定だが列は持つ(将来USD)
price_type_ratesbook_id / type_code / base_fee / charter_per_day / hourly_domestic / hourly_intl / overseas_ops_fee / early_fee / late_fee / is_internal / is_public型式ごとの単価。型式を足す=1行足す。2つの公開フラグで「社内では見積れるが外には出さない」を作れる
price_zonesbook_id / zone_code / name / sort遠域ゾーンの定義。ここを増やすのが「適宜追加」の実体
price_zone_ratesbook_id / zone_code / type_code / remote_fee / facility_feeゾーン × 型式の金額。CJ4のみ設定(C510は「—」)も表現できる
price_airport_feesbook_id / icao / transfer_fee / zone_code空港ごとの送迎料金所属ゾーン。未登録の空港は 0円・ゾーンなしで落ちる(=警告対象)
price_rulesbook_id / tax_rate / time_basis / rounding_unit_min / rounding_scope / rounding_mode / amount_round_unit / amount_round_mode / early_before / late_after税率・課金時間・金額の丸め。確定値は air / 5 / leg / ceil + 金額は floor / 単位 1000
price_auditid / book_id / actor / action / diff_json / at誰がいつ何を変えたか。権限は後回しでも記録は最初から

仮フェーズは D1 を作らず KV の JSON 1本で始める

  • companyapps には既に KV バインド(COMPARISON_CACHE)があり、航空路マップの手動経路が同じパターンで既に乗っている/api/routes-local)。同じ形で /api/pricing-params を足せば、新しいリソースを一切作らずにページから編集・保存ができる。
  • JSON の構造は上の7テーブルと1対1にしておく。本体化のとき流し込むだけになる。
// KV: key = pricing-params  (本体化後は上のテーブルに1:1で写る)
{
  "book": { "id": "2026-04", "name": "2026年度 標準", "currency": "JPY",
            "effective_from": "2026-04-01", "is_active": true, "status": "active" },
  "rules": { "tax_rate": 0.10,
             "time_basis": "air",          // 空中ETE(ブロックではない)
             "rounding_unit_min": 5,        // 5分
             "rounding_scope": "leg",       // LEGごと
             "rounding_mode": "ceil",       // 切り上げ
             "techran": "ignore",           // 燃料度外視の直行で計算
             "amount_round_unit": 1000,     // 飛行料金の丸め単位=1,000円(HP準拠)
             "amount_round_mode": "floor",  // 切り捨て。丸めてから貸切に充当する
             "early_before": "08:59", "late_after": "21:00" },
  "types": {
    "C510": { "label": "Mustang", "base_fee": 700000, "charter_per_day": 600000,
              "hourly_domestic": 400000, "hourly_intl": 500000,
              "overseas_ops_fee": 250000, "early_fee": 280000, "late_fee": 280000,
              "is_internal": true, "is_public": true },
    "C25C": { "label": "CJ4", "base_fee": 1050000, "charter_per_day": 750000,
              "hourly_domestic": 600000, "hourly_intl": 700000,
              "overseas_ops_fee": null, "early_fee": 280000, "late_fee": 280000,
              "is_internal": true, "is_public": true }
    // 事業機化が未定の型式を足すときは is_internal:true / is_public:false で入れる
  },
  "zones": [
    { "code": "HKD",  "name": "北海道",         "rates": { "C510": {"remote":120000}, "C25C": {"remote":200000} } },
    { "code": "AMAMI","name": "奄美以南",       "rates": { "C510": {"remote":200000}, "C25C": {"remote":320000} } },
    { "code": "OKA_S","name": "那覇以南",       "rates": { "C510": {"remote":350000}, "C25C": {"remote":520000} } },
    { "code": "TW",   "name": "台湾",           "rates": { "C510": {"remote":500000,"facility":650000},
                                                          "C25C": {"remote":750000,"facility":650000} } },
    { "code": "KR",   "name": "韓国",           "rates": { "C510": {"remote":250000,"facility":500000},
                                                          "C25C": {"remote":350000,"facility":500000} } },
    { "code": "GUM",  "name": "グアム・サイパン","rates": { "C25C": {"remote":750000,"facility":500000} } },
    { "code": "HKG",  "name": "香港",           "rates": { "C25C": {"remote":1300000,"facility":1500000} } },
    { "code": "MFM",  "name": "マカオ",         "rates": { "C25C": {"remote":1300000,"facility":550000} } }
  ],
  "airports": {
    "RJCC": { "transfer": 120000, "zone": "HKD" },
    "RJTT": { "transfer": 120000 },
    "RJGG": { "transfer": 150000 },
    "ROAH": { "transfer": 120000, "zone": "OKA_S" }
  }
}
香港とマカオは 遠域は同額(1,300,000)だが施設料が違う(1,500,000 / 550,000)ため、HP表記では1行でもゾーンは分ける。この手の「表記は1行だが実体は2つ」がこれから増えるので、ゾーンは細かく持って表示側でまとめる方針にする。

ゾーン割当の漏れ対策。空港が増えるほど「ゾーンを割り当て忘れて静かに安くなる」事故が起きやすい。対策を2つ入れる。
① 設定ページに 「マスタにあるがゾーン未割当の空港」一覧 を常設する。
② 見積時、国内でない空港(country_code <> 'JP')にゾーンが無い場合は警告を返す。海外は必ずどこかのゾーンに属するはずなので、これで取りこぼしを拾える。

型式ごとの公開フラグ(2つ持つ)

  • is_internal … 社内見積・予約管理・各ポータルで計算してよいか
  • is_public … HPの公開APIに出してよいか
  • 新しい型式は internal のみ ON で投入し、値付けを検証してから public を立てる。事業機化が未定の機体を外に出さない栓になる
  • 版とは独立。改定の版を切っても公開状態はその版の設定に従う

master の「事実」とは分ける

  • 就航開始日 aircraft.service_start、機体の有効 aircraft.is_active、国際運航の認可 type_intl_rulesmaster が持つ事実。料金側では持たない(二重管理になる)
  • 料金側のフラグは 「料金を出してよいか」という意思だけを表す
  • 見積が通る条件は master の事実 AND 料金側のフラグ。どちらで弾かれたかを理由として返す
7. 画面
① パラメータ設定 /pricing/params
上部バー(固定)
版:2026年度 標準有効中新しい版を作る差分を見る保存
タブ
型式別単価遠域ゾーン空港共通ルール版の管理
型式別単価(行=型式・列=項目の編集グリッド)
C510 Mustang基本 700,000貸切/日 600,000①400,000②500,000社内◯ 公開◯
C25C CJ4基本 1,050,000貸切/日 750,000①600,000②700,000社内◯ 公開◯
G550(例)未設定社内◯ 公開✕
遠域ゾーン(+ゾーンを追加)
北海道奄美以南那覇以南台湾韓国グアム・サイパン香港マカオ+ 追加
選んだゾーン → 型式ごとの 遠域料金 / 施設料 を編集 + 所属空港を割当
空港(マスタの空港一覧を検索して割当)
検索:那覇ROAH 那覇送迎 120,000ゾーン:那覇以南
⚠ ゾーン未割当の海外空港 3件
版の管理
2026-04 標準有効2026-10 改定案下書き2025-04アーカイブ
② 料金シミュレーション /pricing/sim
入力
型式 C510版 2026-04日程 8/20〜8/22
8/20 羽田 → 大阪 09:00発
8/21 (飛行なし)
8/22 大阪 → 羽田 17:00着+ LEGを追加
飛行時間(flight-calc / 直行・空中ETE・5分切り上げ)
羽田→大阪 1:02 → 1:05大阪→羽田 0:58 → 1:00
内訳(各行に出所バッジ)
基本料金 700,000飛行料金 833,000貸切料金 967,000
空港内送迎 120,000遠域 0早朝深夜 0
小計 2,620,000消費税 262,000合計 2,882,000
検証
版どうしを比較代表ケース一括実行JSONで出力
内訳の各行には「どのパラメータのどの値から来たか」を出す。値付けを説明できる状態=営業が使える状態、という判断。金額が合わないときの調査も速い。
8. API 契約
// 見積(本体。将来HP・予約管理・各ポータルが叩く)
POST /api/pricing/quote
{
  "type_code": "C510",
  "legs": [
    { "date": "2026-08-20", "from": "RJTT", "to": "RJOO", "dep_time": "09:00" },
    { "date": "2026-08-22", "from": "RJOO", "to": "RJTT", "arr_time": "17:00" }
  ],
  "days": ["2026-08-20","2026-08-21","2026-08-22"],   // 省略時はLEGの初日〜最終日
  "options": { "early_departure": null, "late_arrival": null },  // null=時刻から自動判定
  "price_book": null,        // null=いま有効な版
  "flight_minutes": null     // 指定すればコアを呼ばずその値で計算(版比較・実績精算用)
}
→
{
  "price_book": "2026-04", "currency": "JPY",
  "lines": [
    { "code":"base",     "label":"基本料金",   "qty":1,     "unit":700000, "amount":700000, "src":"types.C510.base_fee" },
    { "code":"flight",   "label":"飛行料金",   "qty":2.083, "unit":400000, "amount":833000, "src":"types.C510.hourly_domestic" },
    { "code":"charter",  "label":"貸切料金",   "qty":3,     "unit":600000, "amount":967000, "src":"types.C510.charter_per_day" },
    { "code":"transfer", "label":"空港内送迎", "qty":1,     "unit":120000, "amount":120000, "src":"airports.RJTT.transfer" }
  ],
  "subtotal": 2620000, "tax": 262000, "total": 2882000,
  "meta": { "legs":[{"from":"RJTT","to":"RJOO","air_min":62,"billed_min":65}],
            "warnings": [] }
}

// パラメータ(設定ページが使う。書き込みは将来 社内認証必須)
GET  /api/pricing/params            // いま有効な版
GET  /api/pricing/params?book=2025-04
PUT  /api/pricing/params            // 版ごと差し替え(版を切らずに上書きはさせない)
POST /api/pricing/books/:id/activate // 有効化(他の版の is_active を落とす)
GET  /api/pricing/books

設計上の約束

  • エンジンは定価だけを返す。代理店割引・キックバックは代理店ポータル側の管理。割引率がエンジンに入ると社外公開できなくなる
  • 1件=1リクエスト。まとめ/分けの比較は呼び出し側がN回叩いて比べる
  • 飛行時間は差し込める。flight_minutes を渡せる形にしておくと、版どうしの比較も実績時間での再計算もできる
  • 合計0は必ずエラー。いまのHPスクレイプの「静かに0円」を繰り返さない
  • 見積を保存する側が price_booklines を丸ごと持つ。あとで料金が改定されても請求額が動かない

公開(HP)向けの追加

  • APIキー + レート制限(未認証の公開口になるため)
  • CORS を刷新後HPのオリジンに限定
  • 公開版では src(パラメータの出所)を落とす
  • 問い合わせ導線に使うので、計算不能でも理由を返す(運航不可空港・型式未設定・国際運航の認可制約など)
9. 本体の置き場所 — API は切り離す・D1 は master に相乗り

ここは 「Worker(API)をどこに置くか」と「D1(パラメータ)をどこに置くか」を別々に決められるのが要点。D1 は Service Binding を挟まなくても複数の Worker から直接バインドできるので、「APIは独立・データはmasterのDBに同居」という組み合わせが取れる。

D1 は mcj-master-prod に相乗り(price_* テーブル群)

  • パラメータの半分(空港ごとの送迎料金・遠域ゾーン所属)は事実上「空港の属性」。airports と同じDBにある方が素直で、ゾーン割当漏れの検出が1クエリで書ける
  • master には既に aircraft_types(型式諸元)という商売寄りの値も入っている。料金だけ別DBにする強い理由がない
  • 新規D1がゼロ。Free プランはアカウント全体で10個上限で、既に到達した経緯がある
  • 守るルール:テーブル接頭辞 price_ で所有を明示し、書き込みは料金Workerだけ。master worker は price_* を触らない

Worker は独立(mcj-pricing-api

  • 公開トラフィックを master に流したくない。master は社内の単一情報源で、落ちると全社が止まる。HPからの未認証アクセスは別Workerで受けてレート制限をかける
  • 社外公開(HP)と社内利用で必要な保護が違うのを、1か所で設計しきれる
  • /rc/ のCGI廃止の受け皿になる
  • 予約管理に置かない理由:あそこは 「/api/* 既定拒否+公開許可リスト」 の認証設計で、未認証の公開口と相性が悪い
消費者 刷新後HP(公開) 予約管理(社内見積) 代理店ポータル + 割引はここで乗せる オーナーポータル LINE秘書 APIキー / Binding mcj-pricing-api POST /api/pricing/quote GET/PUT /api/pricing/params 1件を計算する純関数+公開口 飛行時間コア flight-calc shared/ に1本(コピーを解消) D1 直接バインド mcj-master-prod(D1) airports / aircraft / aircraft_types … master が所有 price_books / price_type_rates / price_zones price_zone_rates / price_airport_fees / price_rules ↑ 書き込むのは料金Workerだけ
図3:将来像。料金APIは独立させて公開口を持たせ、パラメータは空港・機材と同じDBに置く。HPは「APIを叩く画面」になる。
仮フェーズ(companyapps + KV)から本体へ移るときは、KVのJSONを price_* テーブルへ流し込むだけになるよう、構造を1対1に保っておく。
10. 決定ログ
#論点決定効いてくるところ
1課金する飛行時間の定義と丸め空中ETE(風・空港ファクター込み)を LEGごとに5分切り上げ。ブロックは使わない。往復非対称はそのまま出す4章の式。rules.time_basis=air / unit=5 / scope=leg / mode=ceil
2テクランの扱い考慮しない。燃料度外視で直行したときの空中ETEで計算コアは ete() だけ呼ぶ。assessItinerary() は使わない(5章)
3連続5日以上の値付け問題ない。むしろ伸ばしたいHPの4日上限に由来する hp_days ブロックを撤廃(12章 Phase4)
4割引をエンジンに入れるか入れない。代理店ポータル等で別管理。エンジンは基本の計算のみ8章「定価だけを返す」。公開APIにそのまま出せる
5多通貨いまはJPYのみ。将来USDを実装するprice_books.currency 列だけ先に持つ(6章)
6遠域ゾーンの帰属空港ごとに選ぶ。海外運航開始で空港もゾーンも増える前提ゾーン追加+割当のUI、割当漏れの検出2種(6章)
7版と再現性見積時点の額がそのまま請求額。版管理+有効化フラグを持つprice_books.is_active、見積側に price_book と明細を焼き付け(8章)
8編集権限・監査最初は権限不要。いずれ挟む(管理方法は別途)price_audit は最初から記録。認証は後付けできる形にしておく
9本体の置き場所いまは companyapps。本格運用時に切り離す。パラメータD1は master に相乗り9章。mcj-pricing-api Worker + mcj-master-prodprice_*
10切替の合格基準HPとの合致は問わない(料金変更を予定)。companyapps 内で十分検証してから公開へ13章。検証は「HP再現」から「版どうしの差分確認」へ
A金額の端数1,000円未満切り捨て。丸めてから貸切に充当(HP実挙動と同じ順序)4章の floorNrules.amount_round_unit=1000 / mode=floor
B時刻未定のときの早朝・深夜時刻が入力されていなければ加算しない内訳に「時刻未定のため未計上」と出す
C風補正に使う月各LEGの運航日の月。運航日未定なら当月コア呼び出し時の month
D飛行単価②の判定範囲LEG単位。旅程全体のどこかが海外なら②、という判定にするなら再検討4章の式に明記
E料金表にない型式型式ごとに is_internal / is_public の2フラグを持つ。事業機化が未定なら公開しない6章。master の事実(就航開始・認可)とは分けて管理
F改定の中身まずは現行のままのパラメータで作る。改定は次の版で12章 Phase2。エンジンは値を持たないので改定を待たずに実装できる
G遠域の数え方実質フェリー料金なので一連の発地と最終着陸地のみ。同じ空港でも重複排除しない(2026-08-13 確定)4章の式。切替パラメータは持たない(誤設定で請求が狂うため)
HCJ4の海外運航料金Mustangと同額 250,000円/件。HPの表が結合セルだった(2026-08-13 確定)3章の表。types.C25C.overseas_ops_fee
11. 細部の決定
A金額の丸め
決定:1,000円未満切り捨て

HP準拠の1,000円未満切り捨てとする。丸めてから貸切料金に充当する順序も同じ(4章の実測表)。いまの見積書と桁の見え方が変わらない。羽田→大島なら 166,666.67 → 166,000

単位は rules.amount_round_unit のパラメータなので、あとから100円に変えることもできる(版を切れば過去の見積は動かない)。

  • B 時刻未定の早朝・深夜 … 加算しない。内訳に「時刻未定のため未計上」と出す。
  • C 風補正の月 … 各LEGの運航日の月。運航日未定なら当月。
  • D 飛行単価②の判定 … LEG単位。旅程全体で判定する運用になったら再検討。
  • E 型式の公開is_internal / is_public の2フラグ(6章)。事業機化が未定なら公開しない。
  • F 改定の中身 … まずは現行のままのパラメータで作る。改定は次の版で。
12. 進め方
Phase 1

companyapps で作る

パラメータ設定(KVのJSON)+シミュレーション。飛行時間は同一Pages内の /flight-calc/flight-calc.js を読む(コピーを増やさない)。

本番影響ゼロ・既存経路に触らない

Phase 2

現行料金を版として投入

現行のままのパラメータを「2026-04 標準」として入れ、代表ケースを回して内訳の妥当性を確認。HPと突き合わせるのは参考まで

ここで料金改定の議論の土台ができる

Phase 3

mcj-pricing へ切り出す

Worker化+パラメータを mcj-master-prodprice_* へ。同PRで flight-calcshared/ に集約し3コピーを解消。

要 高橋相談(新Worker・masterへのmigration)

Phase 4

社内の見積を載せ替える

予約管理の charterQuoteHandler を mcj-shift のHPスクレイプから mcj-pricing の Service Binding へ差し替え。4日上限のブロックを外す。

本番影響あり・段階切替+差分監視

Phase 5

HPに公開APIを出す

刷新後HPが叩く公開口(APIキー・レート制限・CORS)。ここまで来ると /rc/ のCGIが不要になる。

HP側の進行と歩調を合わせる

Phase 6

後片付け

mcj-shift/hp-calculator.tsmcj-quote/pricing.ts・空き確認画面の概算モデルを撤去。料金の実装を1本にする。

残していると必ず食い違う

Phase 1〜2 はこのリポジトリ内で完結し、誰の業務も止めない。Phase 3 以降は本番影響が出るので着手前に高橋さんへ相談する。
13. 検証

HPとの一致を条件にしないので、検証の軸は2つになる。①内訳が式どおりに積み上がっているか(回帰)と、②版を変えたときに金額がどう動くか(差分)。②はそのまま 料金改定の検討ツール になる。

① 回帰 — 代表ケースを固定

  • 飛行時間を固定値で差し込み(flight_minutes)、パラメータを変えていないのに金額が動いたら落ちる、という形にする
  • ケースは「単発 / 連続2日 / 中日1日 / 中日2日 × 近距離・遠距離・遠域あり」+連続7日(HPでは作れなかった形)+海外発着
  • 非線形の確認:羽田発着の往復1件なら遠域0回、片道2件に割ると各1回。遠域が起終点なら往復ぶん2回

② 差分 — 版どうしの比較

  • 同じケース集合を「現行 2026-04」と「改定案」で流し、1件ずついくら変わるかの一覧を出す
  • 影響の大きい順に並べれば、改定の議論がそのまま数字でできる
  • 飛行時間の定義変更(空中ETE・5分切り上げ)による増減も、この形で1回だけ測っておく
参考として、現行HPの実測値(2026-08-03・税込・往復)を残しておく。新エンジンはこれと一致しないが、桁が違わないことの目安にはなる。
C510 羽田⇄伊丹 単発×2件 3,124,000 / 連続2日 2,222,000 / 中日1日 2,882,000 ・・・ CJ4 羽田⇄新千歳 単発×2件 5,148,000 / 連続2日 3,289,000 / 中日1日 4,114,000。中日1日ぶんの増分は C510 660,000・CJ4 825,000(=貸切料金/日 ×1.1)で一定。
mcj-companyapps / pricing — 設計壁打ち 第5版(2026-08-13)