チャーター料金の計算を 自前の料金エンジンとして作り直す ための設計資料。作るものは ①パラメータ設定ページ ②料金シミュレーションページ の2つで、置き場所は当面 mcj-companyapps の /pricing。将来は刷新後のHP・予約管理・代理店ポータル・オーナーポータルから叩かれる 社内外まとめて1本の料金API にする。
第1版で挙げた論点10件はすべて方針が決まった。いちばん大きいのは「HPとの金額一致を条件にしない」こと。料金改定を前提にするので、エンジンは HPの再現装置ではなく、これからの料金の正 になる。飛行時間も 空中ETEを5分切り上げ という自前の定義で持つ。
細部6件も決着し、未決はゼロ。端数はHPの実挙動を実測して確認したうえで 1,000円未満切り捨て・丸めてから貸切料金を充当 に決めた(4章)。この版をそのまま実装仕様として使える状態。次は12章 Phase1。
第6版(2026-09-16)で 14章を追加。刷新後HP mcj-web との接続、版の改訂日方式(改定日を先に仕込んで自動で切り替える)、旧LINE見積Bot mcj-quote の廃止を決めた。版が切り替わる基準日は見積を作る日時。
- 課金する飛行時間=空中ETE(風補正・空港ファクター込み)を LEGごとに5分切り上げ。ブロックタイムは使わない。往復で時間が違うのはそのまま出す。
- テクランは料金計算に持ち込まない。燃料を度外視して直行したときの空中ETEで計算する。運航上テクランが要るかどうかは別の判断(配置エンジンの仕事)。
- 連続5日以上も計算する。HPの4日上限が外れる。むしろ長期の受注を伸ばしたいので、そこで見積が出ないのは機会損失。
- エンジンは定価のみを返す。代理店割引・キックバックは代理店ポータル側で管理する。
- 通貨は当面JPYのみ。USDは将来やるので、データモデルには通貨の軸だけ入れておく。
- 遠域ゾーンは空港ごとに選ぶ。海外運航が始まり空港もゾーンも増えていく前提で、地理条件のハードコードにはしない。
- 版(プライスブック)+有効化フラグを持つ。見積時点の額がそのまま請求額なので、見積を出した瞬間の版と明細を焼き付ける。
- 編集権限は最初は不要。ただし後から挟むので、誰がいつ何を変えたかの記録は最初から取る。
- 置き場所=いまは companyapps、本格運用時に切り離す。パラメータのD1は mcj-master に相乗り。(9章)
- HPとの金額一致は条件にしない。companyapps 内で十分検証してから公開へ進む。現行料金からの変更を予定している。
- 飛行料金は1,000円未満を切り捨て、その丸め後の値で貸切料金を充当する(HPの実挙動と同じ順序)。
- 型式ごとに「社内で使える/公開してよい」の2フラグを持つ。事業機化が未定の機体を公開しないための栓。
- 最初の投入は現行のままのパラメータ。改定は次の版で。
① パラメータ設定ページ /pricing/params
- 型式ごとに 基本料金・貸切料金/日・飛行単価①②・海外運航料金・早朝/深夜料金 を設定
- 遠域ゾーンを自由に追加。ゾーンごと × 型式ごとに 遠域料金・施設料
- 空港ごとに 空港内送迎料金・所属遠域ゾーン を割当(マスタの空港一覧から検索)
- 税率・丸め規則・版の作成と有効化
- 変更は 保存の前に差分プレビュー、保存後は版として残る
② 料金シミュレーションページ /pricing/sim
- 型式・日程・区間(複数日・複数LEG)・早朝深夜を入れると内訳つきで金額が出る
- 飛行時間は
flight-calc.jsが算出(直行・空中ETE・5分切り上げ) - 内訳の各行が どのパラメータから来たかを表示(値付けを説明できる状態にする)
- 版どうしの比較モード — 現行料金と改定案で代表ケースがいくら変わるかを一覧(13章)
/rc/ を触ると全部止まる。自前化の一番の理由は、HP刷新でこの依存が切れること。HP側は同僚が担当する予定で、/rc/ を残すか引っ越すかも未決。料金エンジンを自前で持てば、HPは「料金APIを叩くだけの画面」になり、/rc/ のCGIごと廃止できる。
副次的に、4日上限が外れる(連続5日以上を営業部へ回している運用が不要になる)・失敗が静かに0円にならない・料金改定をこちらの都合でできるようになる。
mcj-quote/workers/src/pricing.ts にも自前実装があるが、C510定数のみ・遠域を全LEGの発着地で加算しており料金表とズレている。流用しない。新エンジン完成時に撤去対象。これは 移行の出発点であって、目標ではない。現行から料金を変更する予定なので、この値は「最初に投入する版(2026-04)」として入れておき、改定は新しい版で行う。
| 項目 | Mustang(C510) | CJ4(C25C) | 課金単位 | 適用条件 |
|---|---|---|---|---|
| 基本料金 | 700,000 | 1,050,000 | 円 / 件 | 1件につき1回 |
| チャーター(貸切)料金 | 600,000 | 750,000 | 円 / 日 | 飛行料金に充当される(4章) |
| 飛行単価① | 400,000 | 600,000 | 円 / 時間 | 国内離発着 |
| 飛行単価② | 500,000 | 700,000 | 円 / 時間 | 出発地または到着地が海外 |
| 海外運航料金 | 250,000 | 250,000 | 円 / 件 | 国際運航時(HPの表は両型式で結合セル=同額。2026-08-13 確認) |
| 早朝出発料金 | 280,000 | 280,000 | 円 / 件 | 初日の出発が 8:59 まで |
| 深夜到着料金 | 280,000 | 280,000 | 円 / 件 | 最終日の到着が 21:00 以降 |
| 遠域発着料金(対象地域) | C510 | CJ4 |
|---|---|---|
| 北海道 | 120,000 | 200,000 |
| 奄美以南 | 200,000 | 320,000 |
| 那覇以南 | 350,000 | 520,000 |
| 台湾 | 500,000 | 750,000 |
| 韓国 | 250,000 | 350,000 |
| グアム・サイパン | — | 750,000 |
| 香港・マカオ | — | 1,300,000 |
| 空港内送迎料金 | 金額 |
|---|---|
| 千歳・成田・羽田・関西・福岡・鹿児島・那覇 | 120,000 |
| 中部 | 150,000 |
| 海外空港施設利用料金・送迎料金 | 金額 |
|---|---|
| 台湾 | 650,000 |
| 韓国 | 500,000 |
| グアム・サイパン | 500,000 |
| 香港 | 1,500,000 |
| マカオ | 550,000 |
C510 / C25C。エンジンのキーは マスタ側の type_code に統一し、表示名だけHP表記に寄せる。G550(N679MS)・HA420 は料金表にないので当面 料金未設定=見積不可(→ 11章の確認5)。// 1件(=連続して飛ぶ日の塊 ひとかたまり)あたり・税抜 合計税抜 = 基本料金 // 件に1回 + Σ日 [ 飛行料金(その日) + max(0, 貸切料金/日 − 飛行料金(その日)) ] // ← 明細の「貸切料金」 + Σ使用空港 空港内送迎料金 × 立ち寄り回数 + 遠域発着料金(発地) + 遠域発着料金(最終着陸地) // 実質フェリー料金。同じ空港でも重複排除しない + 海外運航料金(国際運航なら件に1回) + Σ海外空港 海外空港施設利用料金 // HPは空港内送迎に含めて表示する(v0も同じ扱い) + 早朝出発料金 + 深夜到着料金 // 立ち寄り回数(2026-09-08 実測) 立ち寄り回数(空港) = その空港への到着回数 + (発地 かつ 最終着陸地ではない なら 1) // 羽田→大島→羽田 = 羽田1回(行って戻るだけ。発地と最終着陸地が同じなら1回に数える) // 羽田→大島→羽田→大島 = 羽田2回 // 降りずに出るだけの空港は0回(到着していないので送迎が発生しない) // 回送(機体移動)レグも到着として数える // 回送(機体移動)レグ … 日をまたいで空港がつながっていないときにHPが自動で挟む // 飛行時間には入るが飛行料金には入らない。ただし到着として送迎の回数には数える 前日の最終地によっては挟まれない(2026-09-08 実測)。挟まれないと翌日の出発空港には送迎料金がかからない。 // [大島→八丈島][成田→八丈島] → 回送 八丈島→成田 が入り 成田に送迎 120,000 がかかる // [大島→香港][台湾松山→大島] → 回送が入らず 台湾松山の 650,000 はかからない // 挟まれない空港(全98空港を実測)= 海外17空港 + 北大東 + 山口宇部 の19。規則ではなく表で持つ // (hp-ferry-origins.json)。つまり同じレグ構成でも「日の切り方」で送迎料金が変わる。 // その日の飛行料金(★LEGごとに丸めてから足し、その値で貸切に充当する。丸める単位と順序が金額に効く) 飛行料金(日) = Σその日のLEG floorN( 課金時間(LEG)[分] ÷ 60 × 飛行単価 ) 飛行単価 = 出発地か到着地が海外なら hourly_intl(単価2) / それ以外は hourly_domestic(単価1) // 判定はLEG単位(羽田→那覇は単価1・那覇→台北は単価2) floorN = 1,000円未満切り捨て(HPの実挙動に合わせる。単位はパラメータで変更可) // 日合計を丸めるのではない。C510 5レグの日で1,000円ずれる(決定ログJ) // 課金時間の定義(第2版で確定) 課金時間(LEG) = ceil5( 空中ETE(直行・風補正込み) ) 空中ETE = 大圏距離 ÷ 巡航速度 × 60 + 固定分 + 風補正 固定分 = 20分(羽田・成田の発着は30分) 風補正 = α(季節,型式) × 東西成分 ÷ 100 // 東向きは短縮・西向きは延長 ceil5(x) = 5分単位で切り上げ // 1:02 → 1:05 / 0:58 → 1:00 // taxi は含めない(ブロックタイムは使わない) // テクランは考慮しない(燃料度外視で直行したときの値) // 早朝・深夜は時刻が入力されているときだけ判定する(未入力なら加算しない) // 風補正の季節αは各LEGの運航日の月。運航日未定の問い合わせでは当月を使う 合計税込 = 合計税抜 × (1 + 税率)
いちばん間違えやすいのは貸切料金の充当。「総額に対して」ではなく「日ごとに」充当する。飛ぶ日は飛行料金で吸収され、吸収しきれない差額だけ貸切料金が乗る。飛ばない中日は満額が乗る。
そしてもう一段、順序の罠がある。HPは飛行料金を丸めてからその値で貸切料金を充当している(下表)。先に充当してから丸めると金額が変わるので、この順序を守る。
| 区間(C510・単価400,000/時) | HPの飛行時間 | 理論値(丸め前) | HPの飛行料金 | HPの貸切料金 | 検算 |
|---|---|---|---|---|---|
| 羽田 → 大島 | 0:25 | 166,666.67 | 166,000 | 434,000 | 600,000 − 166,000 |
| 羽田 → 松本 | 0:35 | 233,333.33 | 233,000 | 367,000 | 600,000 − 233,000 |
| 羽田 → 八丈島 | 0:45 | 300,000.00 | 300,000 | 300,000 | 端数なし |
| 羽田 → 南紀白浜 | 1:05 | 433,333.33 | 433,000 | 167,000 | 600,000 − 433,000 |
| 名古屋 → 仙台 | 1:10 | 466,666.67 | 466,000 | 134,000 | 600,000 − 466,000 |
| 羽田 → 稚内 | 2:25 | 966,666.67 | 966,000 | 0 | 飛行料金が60万超 |
| 羽田 → 石垣 | 4:10 | 1,666,666.67 | 1,666,000 | 0 | 同上 |
なお 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)。
- 「件」の定義=連続して飛ぶ日の塊。連続する日は機材を通しで確保する=1件なので割らない。日ごとに割ると基本料金が件数ぶん乗る。
- エンジンの責務は「1件を正しく計算すること」まで。塊が複数あるときの「まとめるか分けるか」の最適化は、エンジンを何度か呼ぶ上位レイヤの仕事として分離する。
- 遠域は 発地と最終着陸地のみ=実質フェリー料金。羽田→下地島→羽田は起終点が羽田なので該当なし、下地島→羽田→下地島は起終点が下地島なので下地島の遠域料金×2。羽田→新千歳の片道は着地ぶんの1回で、往復を1件にまとめると0回・片道2件に割ると各1回。この非線形があるので、まとめ/分けの損得は静的ルールで決め打ちせず必ず両方計算して比べる。
- 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/)。
<script src="/flight-calc/flight-calc.js"> で既存ファイルをそのまま参照する。コピーは増えないし、今日から動く。shared/flight-calc/ に1つ置き、各プロジェクトはそこを import。そのPRで既存3コピーもまとめて解消する。GET /api/feasibility を実行時に叩く案は採らない。将来は逆に予約管理が料金エンジンを呼ぶので相互依存になる。設計の芯は 「遠域ゾーン」を実体として持ち、空港をそこに割り当てること。海外運航が始まって空港もゾーンも増えていく前提なので、地理条件のハードコードにはしない。ゾーンを足す → 空港を割り当てる の2操作だけで対応できる状態にする。
もうひとつは 版(プライスブック)+有効化フラグ。見積時点の額がそのまま請求額になるので、見積を作った瞬間に「どの版で出したか」と明細を焼き付ける。改定は「新しい版を作る → 内容を詰める → 有効化する」の3手。
| テーブル | 列 | 役割 |
|---|---|---|
price_books | id / name / currency / effective_from / is_active / status(draft·active·archived) / note / created_by / created_at | 版。currency は当面 JPY 固定だが列は持つ(将来USD) |
price_type_rates | book_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_zones | book_id / zone_code / name / sort | 遠域ゾーンの定義。ここを増やすのが「適宜追加」の実体 |
price_zone_rates | book_id / zone_code / type_code / remote_fee / facility_fee | ゾーン × 型式の金額。CJ4のみ設定(C510は「—」)も表現できる |
price_airport_fees | book_id / icao / transfer_fee / zone_code | 空港ごとの送迎料金と所属ゾーン。未登録の空港は 0円・ゾーンなしで落ちる(=警告対象) |
price_rules | book_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_audit | id / 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" } } }
ゾーン割当の漏れ対策。空港が増えるほど「ゾーンを割り当て忘れて静かに安くなる」事故が起きやすい。対策を2つ入れる。
① 設定ページに 「マスタにあるがゾーン未割当の空港」一覧 を常設する。
② 見積時、国内でない空港(country_code <> 'JP')にゾーンが無い場合は警告を返す。海外は必ずどこかのゾーンに属するはずなので、これで取りこぼしを拾える。
型式ごとの公開フラグ(2つ持つ)
is_internal… 社内見積・予約管理・各ポータルで計算してよいかis_public… HPの公開APIに出してよいか- 新しい型式は internal のみ ON で投入し、値付けを検証してから public を立てる。事業機化が未定の機体を外に出さない栓になる
- 版とは独立。改定の版を切っても公開状態はその版の設定に従う
master の「事実」とは分ける
- 就航開始日
aircraft.service_start、機体の有効aircraft.is_active、国際運航の認可type_intl_rulesは master が持つ事実。料金側では持たない(二重管理になる) - 料金側のフラグは 「料金を出してよいか」という意思だけを表す
- 見積が通る条件は master の事実 AND 料金側のフラグ。どちらで弾かれたかを理由として返す
// 見積(本体。将来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_bookとlinesを丸ごと持つ。あとで料金が改定されても請求額が動かない
公開(HP)向けの追加
- APIキー + レート制限(未認証の公開口になるため)
- CORS を刷新後HPのオリジンに限定
- 公開版では
src(パラメータの出所)を落とす - 問い合わせ導線に使うので、計算不能でも理由を返す(運航不可空港・型式未設定・国際運航の認可制約など)
ここは 「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/* 既定拒否+公開許可リスト」 の認証設計で、未認証の公開口と相性が悪い
price_* テーブルへ流し込むだけになるよう、構造を1対1に保っておく。| # | 論点 | 決定 | 効いてくるところ |
|---|---|---|---|
| 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-prod の price_* |
| 10 | 切替の合格基準 | HPとの合致は問わない(料金変更を予定)。companyapps 内で十分検証してから公開へ | 13章。検証は「HP再現」から「版どうしの差分確認」へ |
| A | 金額の端数 | 1,000円未満切り捨て。丸めてから貸切に充当(HP実挙動と同じ順序) | 4章の floorN。rules.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 | 遠域の数え方 | 実質フェリー料金なので一連の発地と最終着陸地のみ。同じ空港でも重複排除しない。海外も同じ扱いで、関西⇄台北の往復1件は遠域0(海外運航料金と施設利用料は乗る)(2026-08-13 確定) | 4章の式。切替パラメータは持たない(誤設定で請求が狂うため) |
| M | 空港内送迎の数え方 | 空港ごと1回ではなく「立ち寄り回数」= その空港への到着回数 +(発地かつ最終着陸地でないなら1)(2026-09-08 実測)。行って戻るだけなら1回、経由して何度も降りればその回数、降りずに出るだけなら0回 | 4章の式。rules.transfer_scope='visit'。海外空港の施設利用料も同じ数え方(HPは空港内送迎に合算して表示する) |
| N | 回送(機体移動)レグ | 日をまたいで空港がつながっていないとHPが自動で挟む。飛行料金には乗らないが送迎の回数には数える。しかも前日の最終地によっては挟まれない(2026-09-08 実測)。挟まれないのは海外17空港+北大東+山口宇部の19空港で、規則で説明できないので全98空港を実測した表で持つ | 同じレグ構成でも日の切り方で送迎料金が変わる。hp-ferry-origins.json と PricingV0.withFerryLegs() |
| I | HP刷新前のバックアップ | 現行HPの計算を総当たりで採取し、凍結版 v0 として GitHub に持つ(2026-09-08)。飛行時間 9,506区間×2機種・空港98・料金定数はすべて実測から逆算 | tools/hp-backup/ と frontend/pricing/v0/。HP刷新・料金改定のあとも過去の見積を1円まで辿れる |
| J | 飛行料金を丸める単位 | LEGごとに1,000円未満切り捨て。日合計を丸めるのではない(2026-09-08 実測で確定) | C510 5レグの日で1,000円ずれる。rules.flight_round_scope=leg。1日1レグの検証しかしていなかったため長く見えていなかった |
| K | HPの飛行時間の向き | 向きで値が違う区間がある(松本→成田 1:40 / 成田→松本 0:40)。無向にまとめず、向きつきで持つ(2026-09-08 実測) | v0のマトリクスは <発ID>-<着ID>。往復の金額が非対称になるのはHPの実挙動 |
| L | HP掲載の料金表と計算ページのズレ | Mustangの海外料金が2つの資料で食い違う(2026-09-08 実測)。 ・韓国の遠域発着:料金表 250,000 / 計算ページ 350,000 ・グアム・サイパン、香港・マカオ:料金表は Mustang 「-」(設定なし)だが、計算ページは 700,000 で計算してしまう ・海外空港施設利用料:金浦とグアム圏だけ Mustang 650,000(料金表は 500,000)。CJ4 は 500,000 で表どおり | v0は計算ページ側に合わせてある(実際に出した見積がそちらだから)。2026-04版をどちらに寄せるか、そもそも Mustang で海外に飛ぶのかは要判断。空港ごと・機種ごとの額は airports.*.transfer_by_type で持てるようにした |
| O | USD版の凍結(v0-usd) | USDページ mcjet.jp/rc/usd/ も同じやり方で総当たり採取して凍結した(2026-09-09)。為替換算では一致しない別の料金体系で、挙動も一部違う。・USDページは回送(機体移動)レグをまったく挟まない(全98空港で実測)→ 同じ旅程でも空港送迎の回数が変わる ・CJ4のグアム・サイパン発着に遠域発着料金がかからない(JPYページは 750,000) ・海外運航料金 Mustang 3,900 / CJ4 1,800(JPYはどちらも 250,000) ・空港送迎が機種で違う空港の顔ぶれが逆(JPYは金浦とグアム圏、USDは台湾4空港と香港・マカオ) ・金額の丸めは 100 USD未満切り捨て・LEGごと ・飛行時間表はJPYと1分も違わない(4,753ペア×2機種×両方向を採取して照合)ので表は1つを共有する | v0-price-book-usd.json ほか。画面 /pricing/v0/ の右上で JPY / USD を切り替える。掲載の英語料金表とのズレは known_discrepancies に書き出してある |
| H | CJ4の海外運航料金 | Mustangと同額 250,000円/件。HPの表が結合セルだった(2026-08-13 確定) | 3章の表。types.C25C.overseas_ops_fee |
| P | 版が切り替わる基準日 | 見積を作る日時(2026-09-16 決定)。運航日基準にすると改定の公表前に新料金が漏れる | 14-1章。見積側が price_book と明細を焼き付けるので、いったん出した見積は改定後も動かない |
| Q | 版の切替方法 | is_active を捨て、版が改訂日 effective_from を持つ。有効版=改訂日が過ぎた中で最新。null は下書き | 14-1章。改定日を先に仕込めるので当日の作業が要らない。as_of で過去時点の再現もできる |
| R | HPが叩く口の置き場所 | 独立Worker mcj-pricing-api(9章の決定を実行)。companyapps には公開口を置かない | 14章。mcj-web の Pages Function → Service Binding で中継。companyapps は社内の編集・検証画面として残す |
| S | 就航空港一覧の公開可否 | 公開してよい(2026-09-16 回答)。GET /v1/airports で配る | 14-2章。mcj-web の静的一覧(89空港)と v0(98空港)のズレも同時に解消する |
| T | 内訳の見せ方 | APIは常にフル内訳を返し、表示の粒度はHP側で決める。当面は現行HPと同じ表示、今後は丸める | 14-2章。見せ方が変わってもAPIを触らずに済む |
| U | 旧LINE見積Bot mcj-quote | 廃止(2026-09-16 決定)。D1が既に存在せず全ルート500で、実質2026-03から死んでいた | 14-3章。コード・資料6か所の撤去と、本番Worker/Pages削除(要 高橋確認)・LINE webhook の解除 |
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 改定の中身 … まずは現行のままのパラメータで作る。改定は次の版で。
companyapps で作る
パラメータ設定(KVのJSON)+シミュレーション。飛行時間は同一Pages内の /flight-calc/flight-calc.js を読む(コピーを増やさない)。
本番影響ゼロ・既存経路に触らない
現行料金を版として投入
現行のままのパラメータを「2026-04 標準」として入れ、代表ケースを回して内訳の妥当性を確認。HPと突き合わせるのは参考まで。
ここで料金改定の議論の土台ができる
mcj-pricing へ切り出す
Worker化+パラメータを mcj-master-prod の price_* へ。同PRで flight-calc を shared/ に集約し3コピーを解消。
要 高橋相談(新Worker・masterへのmigration)
社内の見積を載せ替える
予約管理の charterQuoteHandler を mcj-shift のHPスクレイプから mcj-pricing の Service Binding へ差し替え。4日上限のブロックを外す。
本番影響あり・段階切替+差分監視
HPに公開APIを出す
刷新後HPが叩く公開口(APIキー・レート制限・CORS)。ここまで来ると /rc/ のCGIが不要になる。
HP側の進行と歩調を合わせる
後片付け
mcj-shift/hp-calculator.ts・mcj-quote/pricing.ts・空き確認画面の概算モデルを撤去。料金の実装を1本にする。
残していると必ず食い違う
HPとの一致を条件にしないので、検証の軸は2つになる。①内訳が式どおりに積み上がっているか(回帰)と、②版を変えたときに金額がどう動くか(差分)。②はそのまま 料金改定の検討ツール になる。
① 回帰 — 代表ケースを固定
- 飛行時間を固定値で差し込み(
flight_minutes)、パラメータを変えていないのに金額が動いたら落ちる、という形にする - ケースは「単発 / 連続2日 / 中日1日 / 中日2日 × 近距離・遠距離・遠域あり」+連続7日(HPでは作れなかった形)+海外発着
- 非線形の確認:羽田発着の往復1件なら遠域0回、片道2件に割ると各1回。遠域が起終点なら往復ぶん2回
② 差分 — 版どうしの比較
- 同じケース集合を「現行 2026-04」と「改定案」で流し、1件ずついくら変わるかの一覧を出す
- 影響の大きい順に並べれば、改定の議論がそのまま数字でできる
- 飛行時間の定義変更(空中ETE・5分切り上げ)による増減も、この形で1回だけ測っておく
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)で一定。
刷新後HP mcj-web の見積UIを料金エンジンに繋ぐ段になった。HP側は 料金を一切計算しない(mcj-web の CLAUDE.md「独自の料金計算ロジックを実装しない」)ので、金額の正はこのエンジンだけになる。接続点は public/design/c*/quote.js の requestQuote() 関数ひとつで、そこを差し替えれば繋がる状態まで HP 側は出来ている。
ここで9章の「本格運用時に切り離す」を実行に移す。公開トラフィックを迎えるのは companyapps ではなく独立Worker mcj-pricing-api。companyapps /pricing/ はパラメータ編集・シミュレータ・v0閲覧の社内画面として残す。
あわせて、近々料金改定があることが決まったため、版の切替を is_active の1フラグから 改訂日(effective_from)方式に変える。改定版を事前に作って日時を仕込んでおけば、当日の作業ゼロで切り替わる。
mcj-pricing-api だけ。社内も同じWorkerを叩くので、HPと社内の見積が食い違う余地がない。計算コード(pricing-core.js / pricing-v0.js)は shared/ に1本置き、Worker と companyapps の両方が読む。公開の口は独立Worker mcj-pricing-api(9章の決定を実行)
- HPからは 直接CORSで叩かない。mcj-web の Pages Function
/api/quoteからサーバ間で呼ぶ。Origin制限・レート制限・キー秘匿がそこで片づく - 同一アカウントのWorkerへ URL fetch すると 1042 になるので、mcj-web Pages → pricing は Service Binding
- 旧
mcj-quote-apiはdiscount_rateを呼び出し側が指定できた。この作りは引き継がない(4章・定価のみ)
companyapps に公開口を置かない理由(実測)
functions/api/pricing-params.jsの PUT が今も無認証で、公開Pages URL 上にある。料金パラメータを誰でも書き換えられる状態- KV が FedEx照合のOAuthトークンと同じ namespace(
COMPARISON_CACHE)の相乗り MASTER_API/SCHEDULE_APIの Service Binding と同じデプロイ単位。PDF分割の修正で公開HPの料金が落ちうるし、公開側のロールバックに社内ツールが巻き込まれる
7章では「版+有効化フラグ」としていたが、フラグ1つだと改定日を予約できない(当日に人が押しに行くことになる)。版が改訂日を持ち、APIがその時点で有効な版を自分で選ぶ形に変える。
// price_books(6章の定義からの差分) id // 'v0' | '2026-04' | '2026-11' ... label // 表示名 currency - is_active // ← 捨てる + effective_from // '2026-11-01'(その日の 00:00 JST から)。null = 下書き note // 有効版の決め方(as_of 省略時は「いま」) 有効版 = effective_from IS NOT NULL AND effective_from <= :as_of の中で最新のもの
これで何ができるか
- 改定版を事前に作り込んで日時を入れて放置 → 当日に勝手に切り替わる。デプロイも当日作業も不要
effective_fromが null の間は下書き。編集途中の版が公開に出ない- 改定日の前倒し・後ろ倒しは日時の書き換えだけ
effective_from = 今にすれば即時切替にもなる(「任意のタイミングで切り替える」も同じ仕組みで足りる)- 副産物として
as_ofを渡せば過去の時点の料金を再現できる。v0を凍結した目的とそのまま噛み合う
v0 と改定版でデータの出所が分かれる
- v0 は凍結・不変なので JSON を Worker にバンドルする(最大
hp-flight-times.json224KB)。KV も D1 も要らない - 改定版は編集されるので
price_*(または仮フェーズのKV)から読む - 版の一覧(メタ)はストアに持ち、データの実体だけ分岐する。v0 も
effective_fromを過去日として同じ表に並べる - すぐ改定が来るので、この分岐は最初から入れておく。あとから足すと有効版の判定を二重に書くことになる
11/1 改定として、10/20 に「12/5 に飛ぶ便」の見積を出したらどちらの料金か、という話。
見積側が price_book と明細を焼き付ける(8章)ので、いったん出した見積は改定後も動かない。基準日が効くのは「新しく見積を作るとき」だけになる。
切替日は一発勝負なので、次の2つは実装時に必ず潰す。
① Workers の時刻はUTC。D1 の datetime('now') も localtime もUTCで、過去に踏んでいる。「2026-11-01 00:00 に切り替える」つもりで素直に書くと 9時間ずれて 10/31 15:00 に切り替わる。実装では日付だけを持ち、'YYYY-MM-DD' を「その日の 00:00 JST」と読む(比較の直前に +09:00 を付ける)。画面の入力が date なので、書き方を1つに固定できるほうが事故が少ない。切り替わる瞬間が 00:00 JST であることは回帰テストで固定した。
② 公開APIのレスポンスをキャッシュしない。mcj-web の Pages Function で素直にキャッシュを効かせると、改定日をまたいで古い額が出続ける。cache-control: no-store か、ごく短いTTLにする。
// 見積(8章の /api/pricing/quote の公開版。割引は受け付けない・src は落とす) POST /v1/quote { "type_code":"C510", "currency":"JPY", "legs":[{ "date":"2026-11-05", "from":"RJTT", "to":"RJOO", "dep_time":"09:00" }], "options":{ "early_departure":null, "late_arrival":null } } // price_book は受け付けない(公開は常に「いま有効な版」) → { "price_book":"2026-11", "currency":"JPY", "lines":[...], "subtotal":..., "tax":..., "total":... } GET /v1/airports // 有効版で見積できる空港。HP側の静的一覧を捨てる GET /v1/books // 版の一覧と改訂日(社内画面用) POST /v1/quote?as_of=... // 過去時点の再現。社内のみ・公開口には出さない
内訳はフルで返し、見せ方はHP側で決める
- 当面は現行HPと同じ表示。今後まとめて丸める方針だが、APIは常にフル内訳を返す
- こうしておけば、表示の粒度が変わってもAPIを触らずに済む
- 公開版では
src(パラメータの出所)は落とす(8章)
就航空港の一覧は公開してよい(2026-09-16 回答)
- mcj-web は空港名を
quote.jsに静的に持っている。これをGET /v1/airportsに置き換える - いま既にズレている:mcj-web 89空港(国内85+台湾4)/ v0 98空港(国内81+台湾4+韓国7+香港マカオ2+グアム圏4)
- 版によって空港も変わるので、一覧もAPIから配るのが正しい
mcj-quote は廃止mcj-web の docs/quote-api-contract.md が接続先候補として mcj-quote-api を挙げていたので現況を確認したところ、すでに死んでいた。代理店向けにLINEへ「羽田→福岡 往復 3名」と投げると見積を返すBotで、agencies.discount_rate に ARILA 12% 等を持っていたもの。割引の役目は代理店ポータル(mcj-sales)へ移行済み。
死んでいる根拠(2026-09-16 実測)
- D1
mcj-quote-prodが もう存在しない。wrangler d1 listに無く、指定すると 7404(7403/10000 と違い transient ではない) - Worker は残っていて
GET /api/agenciesが 500。しかも素の textInternal Server Error= onError をJSON化した #676(2026-07-24)以降デプロイされていない - 実質の最終更新は 2026-03-29
mcj-shift/workers/wrangler.tomlのQUOTE_API_URLはsrc/から一度も参照されていない(Env の型宣言だけ)の死に設定mcj-quote-logic.pages.devは静的ページなので 200 で生きている
撤去する残骸
- コード …
mcj-quote/、mcj-shift のQUOTE_API_URL(toml+型宣言) - 資料 …
README.md、system-map.html、system-overview.html、docs/tech-stack.html、dev.htmlの Quote Bot カード - 別リポジトリ … mcj-web
docs/quote-api-contract.mdを新APIで書き直す - 本番の Worker / Pages 削除は本番操作なので高橋さんに確認してから
- LINEチャネルのwebhookがまだ向いているなら外す。放置すると「代理店がLINEに投げても無反応」が残る
mcj-pricing-api を立てる
v0 を返すだけの最小Worker。凍結JSONをバンドルするので KVもD1も要らない。POST /v1/quote と GET /v1/airports。
要 高橋相談(新Worker)
mcj-web と繋ぐ
Pages Function /api/quote + Service Binding、requestQuote() の差し替え、空港一覧をAPIへ。ここでHPに金額が出る。
mcj-web 側のPR(別リポジトリ)
改訂日方式に載せ替える
is_active を effective_from へ。版の一覧をストアに持ち、v0 とストアで実体を分岐。設定画面に改訂日の入力を足す。
改定に間に合わせる必要あり
改定版を投入して日時を仕込む
改定後のパラメータを新しい版として作り、下書きのまま検証。13章②の差分比較で1件ずついくら動くか一覧を出してから改訂日を入れる。
料金の中身は営業判断
社内4系統を載せ替える
12章 Phase4 と同じ。予約管理の charterQuoteHandler を HPスクレイプから mcj-pricing-api へ。HPと社内が同じ版を見る状態にする。
本番影響あり・差分監視
後片付け
mcj-quote 撤去(14-3)、hp-calculator.ts 撤去、/rc/ のCGI停止。料金の実装を1本にする。
残していると必ず食い違う