Slide 1

Slide 1 text

400超 データポイントを型で制す UPSIDER 与信審査エンジンを支えるTypeScript 「柔軟性」と「結合力」 泉 雄介 — VPoE, UPSIDER

Slide 2

Slide 2 text

自己紹介 アメリカ 音楽大学を卒業後、メディア制作会社で作曲家とし て勤務した ち、システム開発で起業。 モルガン・スタンレー証券会社で 債券取引 システム開発 や、ディー・エヌ・エーにおけるゲームプラットフォーム事業や ヘルスケアサービス開発 リードエンジニア、ラクスル取締 役CTOを経て、株式会社UPSIDERに入社しVP of Engineeringを務める。 言語: Go / TS / Ruby / Perl / C# / JS / Java / PHP CFA協会認定証券アナリスト (2011~) 日本CTO協会理事 (2024~) グロース市場企業社外取締役(2025~)

Slide 3

Slide 3 text

UPSIDER Inc. 「挑戦者を支える、世界的な金融プラットフォームを創る」 というミッション もと、 「UPSIDERカード」を じめ、様々な「利用しやすい」法人向け金融サービスを展開してきた。

Slide 4

Slide 4 text

UPSIDER 審査規模・体制 1 取扱企業 数万社規模 日々更新 2 特徴量 1審査あたり 400 超 データポイント 3 ロジック 4系統 x 系統毎 20以上 チューニング パラメータ 4 運用体制 少人数で 高機能運用 5 開発体制 PdM x 1, App x 1 Data Eng x 1, DS x 1

Slide 5

Slide 5 text

審査アーキテクチャー

Slide 6

Slide 6 text

Java で書け 安全だが重い Backend Technology VS JS で書け いが危ない 「型付け言語とJS由来 柔らかさ」

Slide 7

Slide 7 text

可視化・審査 UX 1. 基本情報 2. 財務情報・入出金明細 3. 残高・バーンレートシミュレーション 4. 支払い・延滞情報 5. そ 他特徴量 可視化や審査UXも重要!

Slide 8

Slide 8 text

業界 常識 vs 現場 要件 VS 業界 常識 (金融ドメイン ) Java / Kotlin / Scala / Go 等による堅牢な実装 安定したデータモデル 一度決めた構 慎重に変更する レイヤーごと 厳密な分割 各層で専用 DTOを持ち同期する UPSIDER 現場要件 TypeScript + Zod 堅牢性とアジリティを両立 変化する審査ロジック 市場変動に合わせて動的に変えたい システム全域 貫通 1つ スキーマでDBからUIまで結合する 技術選定 変更頻度 アーキテクチャ

Slide 9

Slide 9 text

TSがもたらす5つ 強み 1 400 特徴量を「1つ 型」に閉じる 2 ロジックがデータになる(動的Override) 3 Strategyパターンによる審査処理 切り替え 4 フロントとバックエンドを繋ぐ「1 Interface」 5 AIと 相性(おまけ)

Slide 10

Slide 10 text

1 400 特徴量を「 1つ 型」に閉じる

Slide 11

Slide 11 text

スキーマ宣言を「 1回」で済ませる export const zFeatures = z.object({ banks_avg_balance: z.number(), dpd_over_30: z.boolean(), banks_burn_slope: z.number().nullish(), banks_concentration_inflow_1m: z.number().nullish(), // ... 400 lines ... current_credit_line: z.number().nullish(), dpd_over_30_within_36_months: z.boolean().nullish(), }) export type IFeatures = z.infer 1. コンパイル時 型定義 (IFeatures) 2. 境界 ランタイム検証 (z.parse) 3. 部分型へ 派生 (zFeatures.partial()) 4. JSONシリアライズ対応

Slide 12

Slide 12 text

もしJavaで同じことを書いたら? HTTP Domain Persistence Wiring / Test FeaturesRequest Bean Validation付き API用DTO Features Record Immutableなドメインモデル FeaturesEntity & Converter JPAとJSONBカラム 変換 FeaturesMapper & Builders MapStruct等で 型変換・テスト 1つ 概念に 8つ以上 ファイル が飛び散る摩擦

Slide 13

Slide 13 text

もしPure JSで書いたら? 実行時までキー typo に気づかない恐怖 features.dpd_over_30_within_36_month (末尾s抜け) 何も言わずに undefined ですり抜け、金額計算で NaN 事故に。 与信 「1桁 ミス」 補填で 済まない損失になる。

Slide 14

Slide 14 text

2 データ 動的マージ

Slide 15

Slide 15 text

「What-if分析」 要件 動的マージにより、本番と全く同じロジックでパラメータや特徴量を変えたテスト・シミュレーションが実行可能 PostProcess Model Parameter 生成 特徴量 抽出 計算 結果 { 決定与信枠、自動承認 判定 } Parameter Injection ・X以上 スコアで自動承認 ・最低X円以上 与信枠 など 閾値や既定値を上書き Feature Injection ・個社ごと 固有 特殊事情 ・特定 属性値 一時的な変更 など 特徴量を上書き ビジネス影響 シミュレーション ポリシー(パラメータ)変更によって、 全 体 与信枠金額にど ような影響が あるかを計算可能。 個別 特徴量変更による個社へ 影 響 テスト・試算も本番と同じプロセス で検証できる。

Slide 16

Slide 16 text

コードで見る動的マージ 実装 「自動で動く本流」 特徴量と「手動介入(DB設定値)」 オーバーライドをマージする実際 処理 // DB設定値 (JSON列) 用 スキーマ export const zServiceProvisionConfig = z.object({ featureOverrides: zFeatures.nullish(), predictionOverrides: zPredictionModelResponse .partial().nullish(), }).strict() // → 完全に型付けされた overrides が手に入る // マージ実行 (predictProcedure.ts) const mergedFeatures = { // 本流 入力 ...input.features, // DBから オーバーライドをマージ ...(serviceProvision.configuration?.featureOverrides || {}), } 「JS スプレッド構文 気軽さ」を保ちつつ、「静的型推論」と「 Zod 境界検証」で守る

Slide 17

Slide 17 text

動的マージと静的型 両立 1. モデル全体 デフォルト DB 2. 今回 審査だけ 上書き DB 3. Zodによる境界検証 (.parse) 完全に型付けされ、制約を満たした値として下流へ

Slide 18

Slide 18 text

実行時 想定外をシャットアウト Java 限界 Optional 連鎖 何層にも重なるマージ ビジネスサイドに 説明できないコードに陥る 「上書きしたつもりが 出来ていない」バグ 温床 Pure JS 危険 スプレッド構文 {...a, ...b} タイポや未定義オブジェクト によるマージ失敗を 静的に弾けない 値が文字列 ままマージされ 計算時に NaN を誘発 TS  Zod 解 マージ 柔軟性 JS まま 境界に来た瞬間に型と制約を検証 数値 範囲制約も実行時に保証 想定外 キー .strict() で拒否可能

Slide 19

Slide 19 text

3 Strategyパターンによる審査処理 切り替え

Slide 20

Slide 20 text

なぜ Strategy Pattern を用いる か? UPSIDERで 複数ブランドや特殊案件に応じた多様な審査パターンが存在。 「入出力 型 同じまま、内部 計算ロジックだけを柔軟に切り替える」設計が必要。 審査プロセス開始 INPUT 共通 型) ML予測審査パターン 1 UPSIDERカード等 ) ML予測審査パターン 2 PRESIDENTカード等 ) 上場企業・特殊案件用審査 (各種特化ロジック ) 後続プロセス OUTPUT 共通 型) 入出力 型制約 担保 したまま、中身 計算ロジックだけを動的に Switchできる

Slide 21

Slide 21 text

具体 実装 switch (method.postAppraisalProcess) { case PostAppraisalProcess.DEFAULT: return new DefaultPostProcessor().run() case PostAppraisalProcess.RUNWAY: return new RunwayPostProcessor().run() default: // 型による網羅性 強制 const _exhaustive: never = method.postAppraisalProcess throw new Error(`Unsupported`) } ■ Exhaustiveness Check enumに新しい値が追加されたとき、 switch文で処理が漏れているとコンパイル エラーとして検知。 ■ 安全なStrategy分岐 ロジック追加時 「処理漏れ」を人間で なくコンパイラが確実に防ぐ。 ■ 堅牢性へ アプローチ Java 21 sealed interface + pattern matching と同等 安全性をシンプル な文法で実現。

Slide 22

Slide 22 text

新しい審査パターン 追加コスト Interface実装 追加 IAppraisalPostProcessor を満たすクラスを作成 1 Switch文へ 追加 コンパイラが漏れを検知。 既存処理 破壊も防ぐ。 2 構 的型付けと Interface 恩恵により、低コストで安全に ロジックを拡張可能。 テスト審査 テストモードで新しい審査 パターンで結果を確認して適用 3

Slide 23

Slide 23 text

4 フロントとバックを「 1 Interface」で

Slide 24

Slide 24 text

OpenAPI生成モデルと 決別 従来 Java+JS開発 DB Entity DTO / Response Model OpenAPI 仕様 生成 Generated TS Model tRPC  Zod バックエンド Zod スキーマ フロント Mutation 型 コンパイル時 推論 みで直結

Slide 25

Slide 25 text

5 AIと 相性(おまけ)

Slide 26

Slide 26 text

どこに時間を使うようになったか? 1. PRDを書く 3. 3050分くらいAIに頑張ってもらう 2. 穴が空くほどレビューする 4. テストにより時間をかける

Slide 27

Slide 27 text

開発現場に見られる兆候 1Pizza Team チームサイズを 2-Pizza (7〜8人) から 1-Pizza (2〜3人) へ縮小。 エンジニアがAIを駆使して広範囲をカバーする ため、コミュニケーションコストが劇的に下が る。 巨大なPull Request 人間が介在せず、AIに自律的にコーディング させるため、PRが肥大化する。 しかし、型 境界が守られているため 「それ で良し」とする新しい開発スタイル。 人間 役割シフト 不要になるタスク: コーディング、単体テスト 集中すべきタスク: 要件定義、アーキテクチャレビュー、結合テ ストなど上流工程へシフト。

Slide 28

Slide 28 text

TypeScript 、ビジネス 武器 にできる 「静的型付け言語とJS由来 柔らかさ」 型システムを1言語に閉じたまま "守り" と "攻め" を同時に行える。 少人数で複雑な審査プロセスをメンテできる。

Slide 29

Slide 29 text

Thank You 泉 雄介 VPoE, UPSIDER TSKaigi 2026