ENGINEERING RECORD
柔道整復施術録 — 個人開発記録
接骨院・整骨院向けの施術録アプリを、企画から配布・運用まで個人で開発しています。この文書では、何を考え、何を調べ、どこで判断を誤り、どう直したかを記録します。
概要
TABLE — 開発の実績
| 項目 | 実績 |
|---|---|
| 開発期間 | 2025年12月〜継続中(8ヶ月) |
| コード規模 | TypeScript 約16万行 / DBテーブル29 / マイグレーション8世代 |
| 開発量 | 690コミット / テストファイル162本 |
| リリース | 署名・公証済みで7バージョンを配布(最新 v0.7.0) |
| 利用形態 | 1〜3人規模の個人院 |
| 技術構成 | Electron 35 + Next.js 14 + TypeScript + SQLite(Prisma) + Zod |
| 担当範囲 | 制度調査・要件定義・設計・実装・テスト・署名配布・障害対応のすべて |
← 横にスクロールできます →
「柔道整復施術録」は、接骨院・整骨院における施術録の作成、保存、検索、監査、復旧を支援するローカルファーストなデスクトップアプリです。
このアプリが解こうとしている問題は、「紙を画面に置き換えること」ではありません。受領委任に係る施術録には、正確な記載、照会時の提示、施術完結後5年間の保存が求められます。つまり5年後に読めること、誰がいつ何を書き換えたのかを説明できること、PCが壊れても元に戻せることまでが、ひとつづきの責任です。これを、専任のIT担当者がいない1〜3人の施術所で継続運用できる形にすることが目標でした。
設計基準としては、厚生労働省「医療情報システムの安全管理に関するガイドライン 第7.0版」(以下、電子カルテガイドライン)が求める真正性・見読性・保存性を参照しました。誰が・いつ・何を記録したかを残す認証と監査ログ、施術録の確定、訂正理由を含む更新履歴、検索・PDF出力、暗号化バックアップ、整合性検証、復旧手順までを、ひとつの運用として実装しています。
施術録データは外部へ自動送信せず、利用者のPCと、利用者が指定したバックアップ先で管理します。基本機能はインターネットに依存せず、1台のPCから運用できます。本体のソースコードは非公開で、汎用化して切り出したコア部分のみ別リポジトリで公開しています。
なぜ作ったか
現場で見えていたこと
医療現場にいて、施術録がどう書かれているかを日常的に見てきました。
日常業務が進行しながら施術録を手書きで記載するのは、それ自体が重労働です。患者が途切れない時間帯ほど記載は後回しになります。そして書いた紙は、施術完結後5年間、院内で保管し続けなければなりません(※受領委任を受けた場合のみ)。保管場所と管理コストが積み上がっていきます。
一方で現場には、記録業務を効率化して、施術データを検索可能な構造化データとして蓄積したいというニーズが確実にありました。「去年の同じ患者に何をしたか」を紙の束から探し出す作業には、誰も価値を感じていません。
既存サービスを3社調べた
では既存の電子化サービスを導入すればよいのではないか。そう考えて、主要な3社について、公開情報から次の観点を確認しました。
TABLE — 既存3社の公開情報でどこまで確認できたか
| 確認した観点 | 対応する要件 | 3社の公開情報で確認できたか |
|---|---|---|
| 入力のしやすさ・予約・会計・請求 | 業務効率 | 3社とも詳細に記載あり |
| 記録の確定と訂正履歴 | 真正性 | 確認できず |
| 監査ログの保持 | 真正性 | 確認できず |
| 権限管理 | 真正性 | 確認できず |
| バックアップの検証と復旧手順 | 保存性 | 確認できず |
| 事業者と利用者の責任分界 | 安全管理 | 確認できず |
← 横にスクロールできます →
前半は3社とも詳しく説明されている一方、後半は公開情報からは確認できませんでした。ここで私が問題だと感じたのは、各社が手を抜いているということではなく、ガイドラインが存在しないため、各社がコストと機能の観点だけを基準に動かざるを得ない状態になっていることです。
基準がないこと自体が構造的な問題
医科の診療録は、医師法によって作成・保存義務が法律上明文化されています。一方、柔道整復師の施術録には、法に同等の規定がありません。受領委任を受けない限り、施術録の作成自体が義務ではないのです。
規定がないので、「基準を満たしていること」を説明する動機がサービス提供側に生まれません。結果として、電子化という看板は先行しているのに、電子カルテガイドラインが求める基本要件を満たしているかどうかを、導入する施術所の側から判断できない状態になっていました。
ただ、柔道整復師が扱っているのは要配慮個人情報です。法に明文の規定がないことと、守らなくてよいことは、別のことだと考えました。加えて、公的保険を扱うものは全てデータとして収集・活用したいという国の方向性があり、厚生労働省は末端の診療所であっても電子カルテ導入を推し進めています。医科での導入率が100%に近づく将来を考えれば、柔道整復だけが基準の外に置かれ続けるとは思えませんでした。
そこで、自分で基準を満たすものを作ることにしました。同時に、それが低コストで多くの施術所に届くことには社会的な意味があると考えました。1〜3人の個人院にとって、導入費・月額費・専任IT担当者の不在・クラウドへの患者情報保存・通信障害への不安は、そのまま導入の障壁になるからです。
解く問題の定義
以上から、本開発で解く問題を次のように定義しました。
柔道整復施術録が持つ行政・民事・情報法上の責任を、単なる入力・保存機能ではなく、真正性・見読性・保存性、認証、権限管理、監査、バックアップ、復旧、運用責任まで含む「電子保存」として具体化し、専任のIT担当者がいない小規模な施術所でも継続運用できる形で提供する。
何を作らないと決めたか
施術所の業務には、受付、予約、問診、施術、施術録の作成、会計、保険請求、経営管理と、多くの仕事があります。これらをすべてひとつのアプリにまとめる選択肢もありました。しかし本開発で対象としたのは、柔道整復師が自らの責任で作成する施術録だけです。
意図的に対象外にしたものを明示しておきます。
TABLE — 意図的に対象外にしたもの
| 作らなかったもの | 理由 |
|---|---|
| 保険請求(レセプト)計算 | 請求額の誤りは施術所の金銭的損害に直結します。既存レセコンが長年担ってきた領域を、個人開発の後発が引き受ける理由がありません |
| 予約・会計 | 施術録の責任範囲と直接関係しません。ここまで載せると「レセコンごと置き換えてください」という提案になり、導入障壁が跳ね上がります |
| クラウド保存前提の構成 | 通信障害で施術記録が書けなくなります。また患者情報の預かり先としての責任分界を、個人で背負い切れません |
← 横にスクロールできます →
請求計算を外した判断は、スキーマにもそのまま残しています。施術マスタ(ProcedureMaster)と施術明細(TreatmentDetail)は参考単価の欄を持ちますが、コメントに「最終的な請求計算はレセコン側で行う」と明記し、レセコン側コードを保持する欄(externalCode)だけを用意しました。計算はしない、連携の余地だけ残すという線引きです。
「既存の業務を置き換えない」ことを、最初から動かせない制約として固定しました。施術所から見て、いま使っているレセコンに触れないまま、施術録だけを電子化できる状態を目指しています。
そのうえで、このアプリの価値を「紙を画面に置き換えること」ではなく、次の一連の状態を成立させることだと定義しました。
要件整理とアーキテクチャ・技術選定
要件は三つに分けて管理しました。
- 業務機能: 患者、施術案件、来院、施術録の記録(確定・訂正・ログ管理)、検索、PDF出力
- システム品質: 利用者の特定、権限制御、変更履歴、監査、オフライン動作、復旧可能性
- 院内運用: バックアップ媒体の保管、復旧情報の管理、定期的な点検と復旧確認
業務をどうモデル化したか
施術録を「1件のメモ」として持つと、後から必要になる説明ができなくなります。柔道整復では、同じ患者が別の負傷で再来したとき、保険の適用単位そのものが変わるためです。そこで負傷エピソード(同一初検日のグループ)を中心に据え、次の階層でモデル化しました。
全体は29テーブルで構成しています。設計上、特に気を使ったのは次の三点です。
- 負傷エピソードを初検日で一意にする: 同一カルテ内の初検日をJST基準のキーに正規化したうえで一意制約をかけています。日付の境界が実行環境のタイムゾーンで揺れると、エピソードの切れ目そのものが変わってしまうためです。
- 柔整の負傷名をマスタとして分離する: 部位と負傷種別の組み合わせを自由入力にせず、マスタから選ぶ形にしました。後から検索・集計できる構造化データとして残すためです。
- 旧データとの互換を捨てない: 施術録は個別の負傷への旧い紐づけを残したまま、エピソード単位の紐づけへ移行できるようにしています。すでに配布済みのバージョンでデータを持っている利用者がいるためです。
技術選定
技術選定にあたっては、採用したものと同じくらい、検討して落としたものを記録しています。
TABLE — 採用したものと、落とした案
| 要件 | 採用 | 検討して落とした案 | 落とした理由 |
|---|---|---|---|
| 通信障害でも業務を止めない | Electron + ローカルNext.js + SQLite | Webアプリ + クラウドDB | 回線断で施術記録が書けなくなります。患者情報の預かり先としての責任分界も個人では負えません |
| 同上 | 同上 | Tauri + Rust | 配布サイズと起動速度では有利ですが、Next.js / Prismaの資産をそのまま使えず、個人で保守できる範囲を超えると判断しました |
| 記録と変更履歴を曖昧にしない | Prisma + migration(29テーブル / 8世代) | 手書きSQL + 独自のバージョン管理 | 旧バージョンのDBを持つ利用者が実在する以上、移行の再現性を人力に頼れません |
| 画面とAPIで同じ規則を適用する | Next.js + TypeScript + Zod | 画面側のみのバリデーション | ローカルとはいえAPIを直接叩かれた場合に、検証が素通りします |
| 障害後に正しく復旧できる | 暗号化バックアップ + manifest + ハッシュ検証 + 隔離復旧 | ファイルコピーのみのバックアップ | バックアップが取れていることと、そこから戻せることは別物です。取れているのに壊れている状態を検出できません |
← 横にスクロールできます →
制約も併記しておきます。ローカル正本は端末が単一障害点になること、Prisma migrationは旧データとの互換性を維持し続ける必要があること、ローカルサーバーの管理が発生すること、バックアップ媒体と復旧情報は院内で管理してもらう必要があること。これらは消えていません。後述の運用設計で引き受けています。
システム構成
この要件から、アーキテクチャは「一台のPCの中で、画面・API・業務ロジック・データベースを分けながら、ひとつのアプリとして配布する構成」としました。対象とする施術所の規模と運用を考えると、最新技術を多く採用することより、データの正本がどこにあり、誰が何に責任を持ち、障害時にどう戻すのかを説明できる構成の方が重要だと判断しています。
要件は機能名だけで管理せず、実装箇所、保存されるデータ、院内運用、確認方法まで対応づけました。通常の型検査やテストに加えて、患者登録から施術録の確定、PDF出力、監査ログまでの業務導線、バックアップの作成、改変検知、本番データを変更しない隔離復旧を、確認対象としています。
BFFとして何を担わせたか
Electron の画面から見ると、Next.js はデータベースの前に立つBFFです。Electron Main が Next サーバーを子プロセスとして起動し、127.0.0.1 に bind します。固定ポートではなく、起動のたびに空きポートを確保する方式です。待ち受け先が起動ごとに変わるため、同一マシン上の他プロセスから狙われにくくなります。外部インターフェースには公開していません。
リクエストは次の順で通ります。
Renderer(React)
↓ HTTP / 127.0.0.1:<起動ごとの動的ポート>
Route Handler(136本)… 入力検証・認証・権限・監査の宣言
↓
use-cases 層 … 業務ロジックとトランザクション境界
↓
Prisma → SQLite
Server Actions は使わず、すべて Route Handler に寄せました。認証・権限・入力検証・監査を一つの経路で強制したかったためです。あわせて export const dynamic = "force-dynamic" を明示し、静的最適化も無効にしています。
横断処理を三つのラッパーに集約する
各ルートに同じ前処理を手で書くと、書き漏れがそのまま認可漏れになります。そのため横断処理はラッパーに寄せました。
TABLE — 横断処理のラッパー
| 仕組み | 役割 |
|---|---|
withAuditedRoute | 監査イベントを宣言的に付与する。ハンドラの外側で、カテゴリ・操作種別・権限区分・対象エンティティの解決方法をメタデータとして宣言する |
requireAuth → requireApiPermission | 認証(アカウント状態の確認を含む)を通してから権限を判定する |
guardWriteOperation | 読み取り専用モード、メンテナンスモード、DB移行ロック中の書き込みを 503 で拒否する |
← 横にスクロールできます →
結果として、各ルートの中身は「スキーマ定義 → 認証 → 権限 → ユースケース呼び出し」に揃いました。入力検証は Zod のスキーマをルート単位で定義し、画面側とAPI側に同じ規則が効くようにしています。
権限は表として一箇所に持つ
権限をロールごとの条件分岐としてコードに散らすと、追加や変更のたびに全経路を見直すことになります。そこで 3ロール × 16リソース × 6アクションのマトリクスとして、一箇所に定義しました。
- リソース: 患者、カルテ、来院、施術録、負傷、施術明細、利用者、監査ログ、システム設定、バックアップ、エクスポート、インポート、スキャン文書、ログイン試行、緊急操作、施術マスタ
- アクション: 参照、作成、更新、削除、エクスポート、管理
これだけでは足りないので、二段構えにしています。ひとつは対象レコード単位の判定で、その患者・その施術録にアクセスしてよいかを個別に確認します。もうひとつは HTTP メソッドからアクションを導出する変換で、ルート側の宣言漏れを減らすためのものです。
監査ログをどこで書くか
設計上いちばん迷ったのがここでした。
監査ログを業務更新の外側で書くと、業務更新が失敗したのに監査だけが残る、あるいはその逆が起こります。どちらも記録が事実と食い違うという点で、真正性の要件に反します。
そこで withAuditedRoute には、監査をラッパー側で書くか、ユースケース内のトランザクションで既に書いたかを、ハンドラの戻り値で申告できるようにしました。真正性が要る操作は業務更新と同一トランザクションに監査を含め、そうでない操作はラッパー側に任せます。
ユースケース層は Prisma クライアントを引数で受け取る形にしてあるため、トランザクションクライアントをそのまま渡せます。同じ口をテスト時の差し替えにも使っています。
エラーは層ごとに型で持つ
認証層とユースケース層で、それぞれ型付きのエラーを定義しました。認証層は未認証・権限不足・アカウントロック・一時停止・削除済みを区別し、施術録のユースケース層は記録なし・ロック・確定済みを区別します。HTTPステータスをエラー側に持たせているため、ルートごとに状態コードを書き分ける必要がありません。5章のエラーメッセージ設計は、この区別があって初めて成立しています。
条件分岐の仕様はデシジョンテーブルで固める
要件を文章で書くと、「〜の場合は〜する。ただし〜のときは〜」という但し書きが入れ子になり、条件の組み合わせに抜けがあっても気づけません。特に起動処理・認証・確定処理のように、失敗の種類ごとに利用者への案内を変える必要がある箇所では、抜けがそのまま「何も表示されずに止まる」という挙動になります。
そこで分岐の多い仕様は、実装前にデシジョンテーブルへ落としてから作りました。以下は実装と対応している主なものです。
起動時のデータベース解決
旧バージョンからの移行があるため、起動時に「どのDBを正本として開くか」を決める必要があります。上から順に評価し、最初に一致した行を採用します。
DECISION TABLE — 起動時のデータベース解決
| # | manifest | アプリ識別との適合 | manifestが指すDB | 旧データ候補 | 判定 |
|---|---|---|---|---|---|
| 1 | 不正 | — | — | — | 起動中止(破損または非互換) |
| 2 | あり | 不一致 | — | — | 起動中止(別アプリのデータ) |
| 3 | あり | 一致 | 存在・正常 | — | そのまま使用 |
| 4 | あり | 一致 | 欠落または不正 | — | 起動中止(破損または非互換) |
| 5 | なし | — | — | 採用済みマーカーあり | 起動中止(正本が失われている) |
| 6 | なし | — | — | 不正な候補あり | 起動中止(破損または非互換) |
| 7 | なし | — | — | 2件以上 | 起動中止(移行元を特定できない) |
| 8 | なし | — | — | 1件 | 旧データを採用して移行 |
| 9 | なし | — | — | 0件 | 新規作成 |
← 横にスクロールできます →
表にして分かったことが二つありました。
一つは、「起動しない」で終わらせてはいけないことです。中止に至る行が5つあり、それぞれ利用者に案内すべき内容が違います。破損なのか、別アプリのデータなのか、移行元が絞れないのか。同じ「起動しない」でも、次にやることが変わります。
もう一つは、7行目に隠れた前提があることです。候補が何件かを数えるには、パスの同一性を先に定義しておく必要があります。macOSのファイルシステムは既定で大文字小文字を区別しないため、文字列として比較すると同一のDBが2件に見えます。この表は、パスをrealpathで解決して同一性を判定してから候補を数える、という前提の上に成り立っています。文章で仕様を書いていたときには、この前提を意識していませんでした。
アカウント状態の判定
認証を通す前に、アカウントの状態を確認します。
DECISION TABLE — アカウント状態の判定
| # | ユーザー | status | ロック期限 | 判定 |
|---|---|---|---|---|
| 1 | 存在しない | — | — | 未認証として拒否 |
| 2 | 存在する | SUSPENDED | — | 一時停止として拒否 |
| 3 | 存在する | DELETED | — | 削除済みとして拒否 |
| 4 | 存在する | ACTIVE | 未来の時刻 | ロック中として拒否(解除時刻を案内) |
| 5 | 存在する | ACTIVE | 過去の時刻 | ロックを自動解除し、失敗回数をリセットして通過 |
| 6 | 存在する | ACTIVE | なし | 通過 |
← 横にスクロールできます →
重要なのは5行目です。ロック期限を過ぎた場合の行を書かないと、一度ロックされたアカウントが永久に解除されません。文章では書き落としやすい条件ですが、表にすると空欄として目に見えます。
施術録の確定
確定は一度きりで、後戻りできない操作です。実行前に状態を確認します。
DECISION TABLE — 施術録の確定可否
| # | 記録 | 削除状態 | 確定フラグ | 確定者・署名の各項目 | 判定 |
|---|---|---|---|---|---|
| 1 | 存在しない | — | — | — | 404 |
| 2 | 存在する | 削除済み、または削除項目が不整合 | — | — | 409(ロック) |
| 3 | 存在する | 正常 | 確定済み | — | 409(確定済み) |
| 4 | 存在する | 正常 | 未確定 | いずれかが埋まっている | 409(ロック) |
| 5 | 存在する | 正常 | 未確定 | すべて空 | 確定を実行 |
← 横にスクロールできます →
4行目は本来起こらない状態です。未確定なのに確定者や署名が入っている記録は、確定処理が途中で失敗したか、外部から書き換えられたかのどちらかです。判断がつかない以上、上書きして進めるより止める方を選びました。確定を「フラグを立てる操作」ではなく「状態の整合が取れている記録にだけ許される操作」として定義したことになります。
削除状態の判定をisDeletedだけで行わず、削除日時・削除者・削除理由まで含めて整合を見ているのも同じ理由です。片方だけが埋まっている記録は、正常な削除でも正常な未削除でもありません。
セキュリティ・配布・運用・障害対応
施術録の電子化では、データをどこに置くかだけでなく、誰が利用できるか、何を変更できるか、操作の経緯を後から説明できるか、障害後に業務を再開できるかまでを、同じ問題として扱う必要があります。
確定と訂正をどう設計したか
真正性の中心は、確定した記録を上書きさせないことです。施術録には確定フラグ・確定者・確定日時を持たせ、確定後の変更は上書きではなく新しい版として積む形にしました。訂正には理由の記録を必須とし、変更前後のスナップショットを、変更者と変更種別(作成/更新/削除/確定)とともに履歴へ残します。
同時編集には、バージョン番号による楽観的ロックをかけています。1台のPCを複数人で共用する前提では、別のスタッフが開いたままにした画面から、古い内容で上書きされる事故が現実に起こりうるためです。
施術録には電子署名とタイムスタンプを保持する欄も用意し、タイムスタンプの発行元がシステム内部か外部機関かを区別できるようにしています。ただし現時点で外部機関との接続は行っておらず、埋まるのは内部の値だけです。欄を先に用意しているのは、この種の情報が後から遡って付与できない性質のものだからです(9章)。
保証できる範囲と、できない範囲
セキュリティは「対策した機能の数」ではなく、「どこまで保証できるかを言えること」だと考えています。そのため実装した対策と、まだ保証できない範囲を分けて説明することを重視しました。
TABLE — 実装済みの対策と、保証できない範囲
| 項目 | 現状の実装 | 保証できない部分 |
|---|---|---|
| 認証 | パスワードのハッシュ化 / アカウントロック / 多要素認証 / 失効可能なセッション | — |
| 権限制御 | 役割ベースで制御(管理者・セキュリティ管理者・施術者) | すべての処理経路で一律に強制できておらず、経路ごとの確認に依存しています |
| 確定後の変更禁止 | 確定操作と訂正理由付きの更新履歴 | 同上。全経路での強制には至っていません |
| 個人情報の暗号化 | 一部項目をAES-256-GCMで暗号化 | DB全体は暗号化していません。端末を物理的に持ち去られた場合は守れません |
| 監査ログ | 連鎖ハッシュにより途中の改変を検出 | 検出できるだけで、防止はできません。外部機関に保存された変更不能な証跡ではありません |
| Electron境界 | contextIsolation / sandbox / nodeIntegration: false | — |
| 端末障害 | 暗号化バックアップ + 整合性検証 | ローカル正本は端末が単一障害点です。外部媒体へのバックアップ運用と媒体管理は、院内の運用責任としています |
← 横にスクロールできます →
監査ログに連鎖ハッシュを入れたのは、施術録が民事上の証拠になりうるからです。「書き換えていない」ことを後から説明できる必要があります。ただしハッシュの連鎖は自分のDBの中で閉じているので、DBごと作り直されれば辻褄は合ってしまいます。これは自己証明であって、第三者証明ではありません。外部タイムスタンプの付与を次の課題としています。
何を記録として残すか
後から説明できる状態を作るには、業務データとは別に、起きたことそのものを残す必要があります。29テーブルのうち7つをログに割り当てました。
TABLE — ログ7テーブルが残しているもの
| ログ | 残しているもの |
|---|---|
| 監査ログ | 誰がいつどの操作をしたか(連鎖ハッシュ付き) |
| 施術録履歴 | 変更前後のスナップショット、変更者、変更種別 |
| 権限変更ログ | 誰が誰の役割・状態を、どんな理由で変えたか |
| ログイン試行ログ | 成否と失敗理由、多要素認証の要否と結果 |
| バックアップ実行ログ | 実行種別(自動/手動)、成否、所要時間、manifestのハッシュ |
| 復旧テストログ | テストの成否と、実測した復旧時間・復旧時点 |
| 緊急時操作ログ | 全アカウントのロックや全セッションの強制ログアウトと、その実行理由 |
← 横にスクロールできます →
基準は単純で、「やったはず」で終わる項目を作らないことです。とくに後半の三つは、バックアップと復旧を運用として扱う以上、実行されたかどうかが残らなければ意味がないと考えて追加しました。復旧テストでは目標復旧時間(RTO)と目標復旧時点(RPO)を実測値として記録し、「戻せるはず」ではなく「何秒で、どの時点まで戻せたか」を数字で残しています。
緊急時操作に理由の記載を必須にしているのも同じ考え方です。強い権限ほど、行使した記録が必要になります。
配布もプロダクトの一部として扱う
Mac版では、arm64 / x64成果物の生成、Developer ID署名、Apple公証、staple、Gatekeeper検査、更新用メタデータ、公開物の再ダウンロードとchecksum確認までを、ひとつのリリース経路にまとめました。
完了条件は「成果物が生成されたこと」ではなく、「公開されたファイルを再取得して、利用者と同じ経路で起動できること」に置いています。この基準は、後述するDMG未署名インシデントを経て決めたものです。
障害時の手順
障害が起きたとき、初期化や再インストールを急ぐのが一番危険です。次の順序で対応することを手順として固定しました。
- 01事象と影響範囲の記録
- 02書き込みの停止
- 03現状DB・バックアップ・ログの保全
- 04隔離環境での検証
- 05復旧
- 06利用者と同じ経路での再確認
- 07再発防止
そして障害対応をその場の手作業で終わらせず、Runbook、テスト、リリースゲートへ変換するところまでを運用と考えています。
利用者がエラーを越えられるようにする
利用者は1〜3人の個人院で、IT担当者がいません。エラーを読んだ人が、そのまま対処する人になります。そのため画面に出す文言は、原因の技術的な正確さではなく、次に何をすればよいかを書く方針にしました。
「データベース接続に失敗しました」と表示しても、施術所の側には打つ手がありません。いま何が起きていて、業務を続けられるのか、続けられないなら何をすればよいのかが分かることを基準にしています。同じ理由から、アプリ内に表示されるリリースノートも技術用語を使わず、画面上の操作で説明する形式に統一しました。内部向けの詳細は、利用者に見せる文言とは分けてログに残しています。
現場に出してから変えた三つの設計
配布して実際に使われはじめると、私が机上で立てた要件には入っていなかった要求が出てきました。いずれも「効率化」の話ではなく、現場の文化や責任感覚に関わるものでした。
① 「1台のPCを複数人で使う」ことへの抵抗感 → ロールによる責任の区分け
最初に返ってきたのは、機能の要望ではなく違和感の表明でした。1台のPCを複数のスタッフが共用して施術録を書く状態が、感覚的に受け入れられないというものです。
掘り下げると、二つの別々の不安が混ざっていました。ひとつは「何かミスがあったとき、誰の操作か分からないので責任の所在が曖昧になる」という事後の話。もうひとつは「そもそも自分が触るべきでない情報に手が届いてしまう」という事前の話です。
紙の施術録では、筆跡と押印が自然に責任を可視化していました。共用PCはその機能を無言で奪います。これは操作性の問題ではなく、責任の設計の問題だと理解しました。
対応として、利用者ロールを管理者・セキュリティ管理者・施術者の三つに分け、事後の追跡と事前の制限を別々の仕組みで解いています。
- 事後: すべての操作を監査ログに操作者付きで記録し、施術録の変更は変更前後のスナップショットと変更者・変更種別(作成/更新/削除/確定)を履歴として保存する
- 事前: ロールに応じてアクセスできる範囲を制限し、権限そのものの変更も専用のログ(
PermissionChangeLog)に残す
「誰がやったか分かる」だけでは不十分で、「そもそも触れない」までを併せて用意したことで、共用への抵抗感が下がりました。
② 文字に起こさない項目がある → チェック表形式の施術明細
次に出てきたのは、施術録の文化に関する指摘でした。実施した物理療法機器の種類や設定値といった項目は、もともと施術録に文章として書き起こす習慣がない。しかし記録として残し、後から振り返りたいというニーズはある、というものです。
これは重要な指摘でした。私は施術録を「文章を書く場所」として設計していましたが、現場が求めていたのは文章にする手前の、印をつけるだけの記録でした。文章化を強制すると、記載の負担が紙より増えてしまいます。
対応として、施術マスタ(ProcedureMaster)と施術明細(TreatmentDetail)を追加しました。設計上のポイントは次の三つです。
- マスタ側に表示順(
displayOrder)とカテゴリ(手技療法・物理療法など)を持たせ、施術録の画面上でチェック表として並べられるようにした - 明細側は施術部位、回数・数量、補足メモを持ち、機器の種類や設定を選択で記録できるようにした
- 参考単価の欄は持たせつつ、請求計算はしない方針を維持した(3章の線引きどおり)
結果として、文章の記載量を増やさずに、後から検索・振り返りができる構造化データが溜まる形になりました。
③ 通院が終わった患者のカルテを閉じたい → 状態管理と紙への出力
三つ目は、通院が終了してもう記録することのない患者のカルテについて、紙に印刷してクローズしたいという要望でした。
最初は電子化を進めたいのに紙に戻すのかと思いましたが、話を聞くと理由がありました。稼働中のカルテと終了したカルテが同じ一覧に並び続けると、日々の業務で目的のカルテを探しにくくなる。そして終了したカルテは5年間の保存対象として、業務システムとは別の場所に置いておきたい、という運用上の要求でした。
対応として、カルテに状態を持たせました(ChartStatus: 施術中 / 治癒 / 中止 / 転医)。単なる開閉フラグではなく終了の理由を区別しているのは、後から照会を受けたときに経緯を説明できる必要があるためです。あわせて日本語フォントを埋め込んだPDF出力を用意し、終了したカルテを紙として持ち出せるようにしました。
この三つに共通していたのは、いずれも私が制度文書と既存サービス調査からは導けなかった要求だということです。制度から降ろした要件と、現場から上がってくる要求は、別の経路で集めないと揃わないというのが、ここでの学びでした。
開発の進め方 — AIエージェントとの分担と検証
分解の順序
思いついた機能から実装するのではなく、制度上の根拠と現場の制約からMUST要件を定め、検証可能な変更単位へ分解する順序を固定しました。
- 01根拠・現場の制約
- 02MUST要件
- 03実装単位
- 04自動検査
- 05実機・配布物での確認
- 06証跡と運用手順
何を委任し、何を委任しなかったか
8ヶ月で約16万行を個人開発できたのは、実装をAIエージェントに委任したからです。そのうえで、委任してよい判断とそうでない判断の線を最初に引きました。
TABLE — 判断の分担
| 私が持った判断 | AIエージェントに委任した作業 |
|---|---|
| 何を作らないか(責任範囲の決定) | 実装案の作成 |
| 制度上の根拠づけとMUST要件の確定 | テストの追加 |
| 受入条件の定義 | 調査の下書き |
| 差分レビューと採否 | 差分レビューの一次確認 |
| 配布するかどうかの判断 | 文書化・定型リファクタ |
← 横にスクロールできます →
運用としては、独立したタスクをgit worktreeで並列に走らせ、完成した差分をレビューしてからマージしています。AIの出力は完成品ではなく変更案として扱い、実際の差分・動作・配布物を自分で確認してから採否を決めました。
型検査・lint・テストをマージの必須ゲートにしているのは、生成量が人間のレビュー速度を上回るためです。機械が落とせるものを機械に落とさせないと、レビューが破綻します。
境界をどう作るか — 一人開発における組織設計
コンウェイの法則に従えば、組織の境界がそのままシステムの境界になります。一人開発の問題は、境界が自然には生まれないことです。放置すれば全体がひと続きの塊になります。
そこで、独立して並列実行できる単位にタスクを割り、それぞれをgit worktreeの別ワーカーへ渡す形にしました。結果として、並列実行できる単位に切り出せるかどうかが、モジュール分割が妥当かの判定として機能しています。切り出そうとして他のタスクとぶつかるものは、設計が絡まっている合図でした。
同時に、人間の介在点そのものを設計対象として扱っています。委任する範囲を広げるほど、どこに承認を挟むかが品質を決めるためです。現在は三か所に人間の承認を置いています。
TABLE — 人間の承認ゲート
| 承認ゲート | 何を止めているか |
|---|---|
| マージの直前 | 自動検査が通っていても、差分・動作・配布物を自分で確認するまで取り込まない |
| リリースの直前 | 品質ゲート通過後も、配布の実行は明示的な判断を経る |
| 対外文書の作成 | 責任を負う人間が最終的に内容を決定する(8-2の紹介状) |
← 横にスクロールできます →
いずれも「AIが間違えるから」ではなく、責任を負う人間が通らない経路を作らないという基準で置いています。
検証で見ている範囲
型検査、lint、ユニットテスト、ビルドだけでなく、Electron上でのE2Eと実際の業務導線を確認対象にしています。
- 業務導線: 患者登録 → 施術録の作成 → 確定 → PDF出力 → 監査ログまでを通しで実行する
- バックアップ: 生成、復号、改変検知、DB整合性、隔離復旧までを確認する
- 配布: 公開されたファイルを再取得し、利用者と同じ経路で起動できることを完了条件にする
テストファイルは162本ありますが、重要なのは本数ではなく、利用者が実際に通る境界を検査対象に含められているかです。この基準に至った経緯は次章に書きます。
画面を作ったこと以上に、「なぜ必要か」「どこまで保証するか」「失敗したときにどう戻すか」を説明できる状態へ近づけたことが、この開発の成果だと考えています。
技術的な難所
ローカルファーストを選んだことで、クラウドサービス側に任せられるはずの問題まで、アプリ側で引き受ける必要がありました。
最も苦労したのはネイティブモジュールです。Node.jsとElectronではSQLiteのネイティブモジュールに必要なABIが異なり、開発環境で全テストが通っているのに配布版が起動しないという状態が発生します。原因が自分のコードの外にあるため、通常のデバッグの発想が効きません。最終的に、プロジェクトが使うNodeメジャーバージョンを強制し、ABIの不一致をビルド時に検出するガードを入れることで再発を止めました。
加えて、旧バージョンのDB、保存先、migration、暗号鍵、更新前バックアップも維持し続ける必要があります。空のDBだけを使ったテストでは、これらは一切検出できませんでした。
失敗・撤退・方針転換
個人開発で最も難しかったのは、機能を作ることではなく、作った機能を捨てることでした。実装できることと、このプロダクトが責任を持つべきことは、同じではありません。
8-1. 4ヶ月かけて作ったスマホ連携を、すべて削除した
作ったもの(2026年3月〜4月)
施術中にスマホから下書きを入力し、PCで承認して施術録にする機能です。院内LANでのHTTPS公開、施術所ごとのTLS証明書の発行、DNS登録、端末ペアリングと診断UI、下書き承認フロー、初期3台無料の課金導線まで実装しました。実機でのペアリング導線まで通しています。
気づいたこと
実装が終わった時点で、自分が何を背負ったのかを数えてみました。
証明書の有効期限が切れたら、誰が更新するのか。端末を紛失した施術所から連絡が来たとき、失効させるのは誰か。院内Wi-Fiに繋がらないという問い合わせに、私は答えられるのか。外部障害が起きたとき、施術を止めずに復旧できるのか。
1台のPCで完結していたときの責任範囲は「アプリが正しく動くこと」でした。スマホ連携を入れた瞬間、それが「院内ネットワークが正常であること」まで広がっていました。1〜3人の個人院に、証明書更新や端末失効の運用を任せることもできません。これは個人開発者が持てる範囲を超えていました。
判断(2026年6月)
まず機能を停止し、ユーザー向けの表示を落としました。リリースノートからも記述を削除し、その後、関連する約3万行を削除しています。4ヶ月分の実装を惜しむ気持ちはありましたが、壊れたときに私が直せない機能を配布物に残す方が有害だと判断しました。
残したもの
この撤退から、機能の採否基準そのものを変えました。「実装できるか」ではなく「障害時に自分が復旧まで説明できるか」で決める。以降の判断はすべてこの基準に従っています。
8-2. その他の撤退・方針転換
- 来院受付: ソース上には画面がありながら、配布版では利用できない状態が続いていました。導線と文書を削除し、判断基準を「コードが存在すること」から「利用者が配布物で実際に使えること」に改めました。
- 紹介状のAI生成: バックエンド、Electron連携、質問生成、編集画面まで作ったうえで、AI生成部分を削除しました。紹介状で必要なのは、柔道整復師が内容を確認し、自ら編集し、保存・出力できることであって、AIを介すこと自体ではないと考え直したためです。現在は人が内容を決定する単純な流れを残しています。紹介状は医療機関宛の対外文書で、内容の責任は柔道整復師にあります。承認ゲートは責任を負う人間が最終決定する位置に置くべきで、生成の手前に置いても意味がないという整理です。
- クラウド同期: 必要性は認識しつつ、本体には実装していません。同期を追加すると、暗号化だけでなく、端末の信頼、鍵の喪失、端末失効、競合解決、復元、委託先との責任分界まで成立させる必要があります。現在は調査と設計を専用ブランチへ隔離し、技術・法務・復旧の条件がそろうまで現行機能として扱わない方針にしました。
8-3. 「自動検査が通ったから大丈夫」を捨てた
配布まわりでは、通常のWeb開発用テストでは検出できない問題が連続しました。
- DMGコンテナが未署名のまま配布経路に乗っていた
- 旧データ保存先で大文字小文字の衝突が起きた
- ネイティブモジュールのABIが不一致で、配布版だけ起動しなかった
いずれも型検査・lint・ユニットテストはすべて通っていました。さらに現行コードを再監査したところ、削除済み機能への参照が残り、Electron単体の型検査が失敗する問題も見つかりました。原因は単純で、通常の品質検査にElectronが含まれていなかったことです。
DMG未署名については、修正だけで終わらせず、署名・公証・stapleの検証をリリース経路に組み込み、再発防止策を文書として残しました。ここで学んだのは、テスト数ではなく、利用者が通る境界を検査できているかが問題だということです。
これから
現時点で保証できていない範囲は、そのまま次の課題です。
TABLE — 残っている課題
| 課題 | 内容 |
|---|---|
| 監査ログの第三者証明 | 外部タイムスタンプを付与し、自己証明から第三者証明へ引き上げる |
| DB全体の暗号化 | 現在は一部項目のみ。端末の物理的な持ち去りに対応する |
| 権限制御の一本化 | すべての処理経路で権限と確定後変更禁止を強制する仕組みへ統一する |
| クラウド同期 | 技術・法務・復旧の条件がそろった時点で本体へ取り込む |
← 横にスクロールできます →
その先について考えていることも書いておきます。AIエージェントの登場で開発コストが下がった今、この種のプロダクトが無償に近い形で提供され、適切にデータ収集と実績集積ができたとき、各院は自らの情報を自分で管理運用できるようになります。それは業界として自治的にガイドラインを発出するきっかけになり、行政に対して主導権を持って提案できる状態につながると考えています。
この開発から得た最大の学びは、機能数を増やすことではなく、責任範囲を決め、説明できない機能から撤退し、利用者が実際に通る経路で検証することでした。完成したと見せることより、いま保証できる範囲と残っている課題を区別して説明できることを重視しています。
付録. 設計領域カバレッジ
設計としてどの領域をどこまで扱ったかを整理します。扱わなかった領域も、判断の理由とともに記載します。
TABLE — 設計領域カバレッジ(38領域)
| 層 | 領域 | 本開発での扱い | 本文 |
|---|---|---|---|
| 上流 | 業務設計 | 施術所業務のうち、施術録のみを対象と決定 | 3章 |
| サービス設計 | 1〜3人の個人院向け。低コストで、既存レセコンを置き換えない | 2・3章 | |
| UX設計 | 現場のフィードバックを受けて三点を再設計 | 6章 | |
| 情報設計 | 患者→カルテ→負傷エピソード→来院→施術録の階層 | 4章 | |
| ドメイン設計 | 負傷エピソード単位でモデル化。柔整の負傷名をマスタ化 | 4章 | |
| アーキテクチャ | システム方式設計 | ローカル完結。画面・API・データを層で分離 | 4章 |
| インフラ設計 | 施術所のPC1台。サーバーを持たない | 4章 | |
| ネットワーク設計 | localhostのみ。院内LAN公開は一度実装して撤退 | 4・8章 | |
| セキュリティ設計 | 認証・権限・暗号化・監査。保証範囲を明示 | 5章 | |
| 可用性・信頼性設計 | 端末が単一障害点であることを明示し、バックアップと復旧で受ける | 4・5章 | |
| 性能・キャパシティ設計 | 5年保存を前提にSQLiteの範囲内と判断。復旧は時間と時点を実測 | 5章 | |
| コスト設計 | 利用者側は導入費・月額を抑制。開発側は署名と公証の維持費を継続負担 | 2・5章 | |
| 運用設計 | 障害対応手順、バックアップ運用、復旧テストの記録 | 5章 | |
| 移行設計 | マイグレーション8世代。旧DB・保存先・暗号鍵の互換を維持 | 4・7章 | |
| 外部設計 | 画面・UI設計 | 記載負担を増やさない方針。チェック表形式の施術明細など | 6章 |
| API設計 | API経路に認証・権限・入力検証を集約 | 4章 | |
| バッチ設計 | 自動バックアップの定期実行と、その実行ログ | 5章 | |
| イベント設計 | 操作を監査イベントとして記録し、連鎖ハッシュで連結 | 5章 | |
| データ設計 | 29テーブル。業務データと記録(ログ)を分離 | 4・5章 | |
| 認証・認可 | 多要素認証、失効可能セッション、アカウントロック | 5章 | |
| 権限設計 | 3ロール。権限の変更自体もログとして残す | 5・6章 | |
| エラーメッセージ設計 | IT担当者がいない前提で、次に何をするかを書く | 5章 | |
| 仕様の曖昧さ除去 | 起動時のDB解決、アカウント状態、確定可否をデシジョンテーブルで確定 | 4章 | |
| 内部設計 | モジュール設計 | 並列実行できる単位に切り出せるかを、分割の判定に使用 | 7章 |
| テーブル設計 | 論理削除、版管理、旧データ互換の維持 | 4・5章 | |
| 状態遷移設計 | 施術録(下書き→確定→訂正)、カルテ(施術中→治癒・中止・転医) | 5・6章 | |
| トランザクション設計 | Application層に境界を集約 | 4章 | |
| 例外設計 | 利用者向けの文言と、内部向けの詳細を分離 | 5章 | |
| ログ設計 | 7テーブル。「やったはず」で終わる項目を作らない | 5章 | |
| キャッシュ設計 | 扱っていない。ローカル完結のため必要性がないと判断 | — | |
| 命名規約 | 業務用語(負傷エピソード等)を、コードとDBの名前に一致させる | — | |
| 横断 | テスト設計 | 162ファイル。業務導線・バックアップ・配布物を対象に含める | 7章 |
| CI/CD設計 | 型検査・lint・テストをマージの必須ゲートに設定 | 7章 | |
| リリース設計 | 署名・公証・staple・再取得検証をひとつの経路に統合 | 5・8章 | |
| 組織設計 | 一人+AIワーカー。境界が生まれないので意図的に作り出す | 7章 | |
| AI時代 | コンテキスト設計 | 行動規範と禁止事項を、リポジトリ内の文書として管理 | 7章 |
| エージェント設計 | worktreeによる並列実行と、差分レビューを経た統合 | 7章 | |
| 人間の介在点設計 | マージ前・リリース前・対外文書の三か所に承認を置く | 7・8章 |
← 横にスクロールできます →
網羅していること自体より、どこを扱わないと決めたかを説明できることを重視しました。空欄ではなく判断として埋まっている状態を、設計の完了条件としています。