介護事業所向け 人員配置基準チェック内蔵 シフト自動作成クラウド
シフト作成の手間を減らすツールではありません。 人員基準欠如減算という経営リスクを未然に防ぐためのソリューションです。
- 紹介動画(2分57秒): docs/紹介動画_CareShift_Guard.mp4 / 字幕 .srt 発表スライドの最終ページに埋め込み済み(外部の動画共有サービスを使いません)
- デモ環境:
準備中(構築手順は下記デプロイ手順のとおり) - 要件定義書: docs/クラウドプラットフォーム実習Ⅱレポート5_20122049_曽我幸太郎.docx
- 数理定式化: docs/シフト最適化_数理定式化.md
- デプロイ手順: docs/デプロイ手順.md
- 発表スライド: docs/発表スライド_CareShift_Guard.pptx
介護保険法に基づく指定基準では、通所介護事業所は職種ごとに定められた人数以上を 配置しなければなりません。これを満たさない状態を 人員基準欠如 といい、 該当すると 基本報酬の30パーセントが減算 されます。
しかもこの減算は、欠如が生じた翌月または翌々月から解消月まで、 利用者全員に対して 適用されます。
月間の介護報酬が800万円の事業所で2か月継続した場合、逸失収益は 480万円。
この欠如の多くは、職員の急な退職や欠勤への対応の遅れという運用上の理由で発生します。 シフトを組む段階で基準への適合を常時監視できていれば、相当程度は予防できます。
| 課題 | 本ソリューションによる解決 |
|---|---|
| シフト確定後に基準充足を検証する手段がない | 生成・修正のたびに常勤換算を再計算し、下回る日を赤で警告 |
| 管理者がExcelで月20時間 | 制約を登録すれば10秒以内に実行可能解を生成 |
| 常勤換算の検算が属人化 | 算定式と根拠を画面・帳票に明示 |
| 勤務形態一覧表を毎回手作業 | 実地指導の提出様式をワンクリックでExcel出力 |
| 実地指導で「誰がいつ確定したか」を答えられない | 操作を追記専用の監査ログに残す(更新・削除・切り詰めをトリガで拒否) |
提案書で「職員は自分のスマートフォンから希望を入れる」と書いている以上、 実際に片手で操作できなければなりません。断点は 720px で、 それ未満を「職員が片手で持つ画面」として扱います。
| 対応 | 理由 |
|---|---|
| ナビゲーションを下端の固定タブに移す | 画面上端は親指が届かない。介護現場は立ったまま操作する |
| タブはアイコンと文字を常に併記 | アイコンだけでは意味が伝わらず、押す前に迷う |
| 希望休は日付を1回押すだけ(もう1回で取消) | 以前は「区分を選ぶ→登録を押す」の2手で、31日分を入れるには手数が多すぎた |
| 状態は色と文字の両方で示す | 色だけでは色覚特性のある職員に伝わらない |
| 操作要素の高さは最低 44px | 指で押せる大きさの下限 |
| 入力欄の文字は狭い画面で 16px 以上 | これを下回ると iOS Safari が焦点時に勝手に拡大する |
| 31日×職員数の表は狭い画面に出さない | 横スクロールする表を片手で追うのは現実的でない |
| 職員には「自分のシフト」を縦の一覧で出す | 日付・勤務区分・時間帯・実働時間を1行で読める |
| 職員はログイン中の端末で自分の行だけを見る | 他人の勤務は職員の主目的ではない |
| 一覧の表はカードに組み替える | data-label を擬似要素で見出しにする |
職員向けの表示では、次のものを出しません。
- 求解の状態と目的関数(運用の判断材料であり、職員には意味を持たない)
- 人員配置基準の違反件数(対処できるのは管理者だけで、職員には不安だけを与える)
- 確定前のシフト(下書きを配ると、後から変わったときに現場が混乱する。 実務でも勤務表は確定してから配布する)
同種の製品(ジョブズマイスター for 介護福祉 や カイポケ など)も、職員側の機能は 「シフトの確認」と「希望休の入力」に絞られています。本ソリューションも 同じ2機能を下端タブの先頭2つに置いています。
| 権限 | できること |
|---|---|
| 管理者 | すべて。マスタ管理、シフト生成・手修正・確定公開、アカウント管理、監査ログ閲覧 |
| 職員 | シフト表の閲覧、自分の希望シフトの入力、勤務形態一覧表の出力 |
アカウントの運用方針は次の3点です。
- 削除しない。 監査ログが実行者を参照しており、消すと「誰が操作したか」を 辿れなくなります。退職時は無効化します。無効化するとその利用者の既存の ログインもその時点で切れます。
- 管理者は他人のパスワードを設定しない。 初回設定と再設定はワンタイム リンクを発行し、本人が決めます。管理者が決めて口頭やチャットで伝えると、 伝達経路に平文が残ります。
- 有効な管理者を0人にできない。 誰もアカウントを直せない状態になり、 データベースへの直接操作が必要になります。
パスワードは NIST SP 800-63B に沿って 12文字以上とし、記号や大文字の混在は 求めません。覚えられない文字列を強いると付箋に書かれ、かえって弱くなるためです。 推測されやすい語とメールアドレスの一部は拒否します。
再設定リンクは 単回使用・24時間有効で、平文はサーバに保存しません (SHA-256 のハッシュのみ)。再発行すると前のリンクは無効になります。
パスワードの変更・再設定・アカウントの無効化・権限変更を行うと、
users.session_epoch が1増えます。セッションは署名付き Cookie で
サーバ側に状態を持たないため、この世代番号が無いと
「パスワードを変えたのに古い Cookie でそのまま入れる」状態になります。
自己申請の「パスワードを忘れた」導線は実装していません。 SMTP やメール配信サービスを要件に入れると、提案の費用構成に月額が増え、 本資料で示した損益分岐点の根拠が変わります。 運用は「本人が管理者に依頼し、管理者が画面でリンクを発行して渡す」とします。 規模が拡大して管理者の手間が問題になった段階で足す想定です。
git clone https://github.com/FloatedGecko667/careshift-guard.git
cd careshift-guard
make init # .env を作成(値を設定してください)
make vendor # htmx を取得(任意。無くても全機能が動作します)
make up # コンテナを起動
make migrate # スキーマを適用(db/schema.sql を実行)
make seed # 人員配置基準ルールと初期データを投入
# → http://localhost:8080証跡の取得と提出物の生成まで一気に行う場合:
make demo # 起動・スキーマ適用・デモデータ投入・テスト・スモークテスト
make evidence # 証跡テキストを画像化
make xlsximg # 勤務形態一覧表を画像化
make pv # 紹介動画(MP4)と字幕(SRT)を生成
make slides # 発表スライドに画像と動画を埋め込むテストの実行:
pip install -e '.[dev]'
make test # 238件・約60秒(コンテナ内では 236 passed / 2 skipped)
make lint # ruffhtmx は任意依存です。読み込まれていなくても、すべての操作は通常の フォーム送信で完結します。htmx がある場合のみ、希望シフト画面で 該当する日の行だけを部分更新します(段階的強化)。
介護現場では古い端末や制限された環境も想定されるため、 JavaScript が動かなければ操作できない画面は作りません。 外部CDNにも依存しません。他社の停止でアプリが壊れるうえ、 閉域網の顧客環境では読み込めないためです。
利用者(PC / スマートフォン)
│ HTTPS
▼
┌─ Oracle Cloud Infrastructure(大阪リージョン / IaaS)──────────┐
│ Flexible Load Balancer(TLS 1.3 終端) │
│ │ │
│ ┌ VM1: Ampere A1 2 OCPU / 12GB ─ Docker Compose ─────────┐ │
│ │ ① nginx リバースプロキシ / 静的配信 │ │
│ │ ② web FastAPI + Jinja2 + HTMX │ │
│ │ OR-Tools CP-SAT(シフト最適化・同期実行) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌ VM2: Ampere A1 2 OCPU / 16GB ──────────────────────────┐ │
│ │ ③ db PostgreSQL 18 │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
授業で指定された活用技術のうち コンテナ技術(Docker Compose) と IaaS(Oracle Cloud Infrastructure) の2項目を充足しています。
シフト作成は組合せ最適化問題であり、制約を満たすことを決定論的に保証できる ソルバーで解くべき領域です。確率的に出力を生成する大規模言語モデルは、 制約に違反した解をもっともらしく出力しえます。
「AIが作ったシフトが人員基準を満たしていなかった」という事故は、 本ソリューションの存在意義そのものを否定します。 そのため技術選定の段階で、その可能性を構造的に排除しています。
最適化は OR-Tools CP-SAT が担い、機械学習も学習データも使いません。
商用のソリューション提案に用いやすいライセンス (MIT / 修正BSD / Apache 2.0 およびこれらに準ずるもの)に限定しています。
| 用途 | ソフトウェア | ライセンス |
|---|---|---|
| Webフレームワーク | FastAPI | MIT |
| ASGIサーバー | Uvicorn | 3条項BSD |
| テンプレート | Jinja2 | 3条項BSD |
| 画面の部分更新 | htmx | 0BSD |
| 最適化ソルバー | Google OR-Tools | Apache 2.0 |
| DB接続 | SQLAlchemy | MIT |
| マイグレーション | Alembic | MIT |
| PostgreSQLドライバ | pg8000 | 3条項BSD |
| セッション署名 | itsdangerous | 3条項BSD |
| フォーム受信 | python-multipart | Apache 2.0 |
| Excel出力 | openpyxl | MIT |
| パスワードハッシュ | argon2-cffi | MIT |
| RDBMS | PostgreSQL 18 | PostgreSQL License(BSD系) |
| リバースプロキシ | nginx | 2条項BSD |
| コンテナ | Docker Engine / Compose | Apache 2.0 |
| ホストOS | Ubuntu Server 26.04 LTS | — |
意図的に除外したもの
| 除外したもの | 理由 | 代替 |
|---|---|---|
| Grafana / Loki | AGPLv3 | OCI Monitoring(標準機能) |
| Redis | RSALv2 / SSPL | ジョブキューを持たない構成にして不要化 |
| psycopg 3 | LGPL 3.0 | pg8000(3条項BSD) |
機能面では有力な選択肢ですが、SaaSとして第三者に提供する本ソリューションでは ライセンス上の解釈リスクを負う合理性がないと判断しました。
careshift-guard/
├── app/ アプリケーション本体
│ ├── main.py FastAPI エントリポイント
│ ├── config.py 環境変数の読み込みと妥当性検査
│ ├── db.py 接続(SQLAlchemy Core)
│ ├── repository.py DBアクセス。SQLは定数に集約
│ ├── service.py DBの行と最適化エンジンの変換(純粋関数)
│ ├── solver.py シフト最適化エンジン(OR-Tools CP-SAT)
│ ├── excel.py 勤務形態一覧表の Excel 出力
│ ├── presenter.py 画面用コンテキストの組み立て
│ ├── security.py Argon2id・セッション・試行回数制限
│ ├── deps.py 認証と権限の依存関係
│ ├── jobs.py 職種の表示名と判定対象
│ ├── routers/ auth / masters / requests / schedules
│ ├── templates/ Jinja2 テンプレート
│ └── static/ 静的ファイル(nginx が直接配信)
├── db/
│ ├── schema.sql スキーマ定義(唯一の正)
│ └── seed.sql 人員配置基準ルールと初期データ
├── migrations/
│ ├── env.py 接続文字列は DATABASE_URL から読む
│ └── versions/
│ └── 0001_initial_schema.py schema.sql を実行するだけ
├── nginx/default.conf
├── tests/
│ ├── demo_data.py 合成データ生成と制約の独立監査
│ ├── test_solver.py 最適化エンジン
│ ├── test_service.py 業務ロジック(DB不要)
│ ├── test_sql.py SQLの構文とテナント分離
│ ├── test_routes.py 経路・権限・入力検証
│ ├── test_auth.py 認証
│ ├── test_accounts.py アカウント・再設定・監査ログ
│ ├── test_responsive.py 狭い画面での操作性
│ ├── test_excel.py 帳票(数式を実評価)
│ └── test_app.py 疎通・設定
├── scripts/ サンプル生成
├── docs/
│ ├── シフト最適化_数理定式化.md
│ ├── デプロイ手順.md OCI構築・証跡取得・つまずきやすい点
│ ├── 紹介動画_構成台本.md 動画の構成と再生成手順
│ ├── 紹介動画_*.mp4 / *.srt make pv で再生成できる
│ ├── 発表スライド_*.pptx make slides で再生成できる
│ ├── images/ 構成図・スクリーンショット
│ └── samples/ サンプル出力(レビュー用に意図的にコミット)
├── .github/workflows/ci.yml lint / test / SQL構文検証
├── compose.yaml nginx / web / db の3コンテナ
├── Dockerfile
├── alembic.ini
├── pyproject.toml
└── Makefile
生成物の扱いを明確に分けています。docs/samples/ はレビュアーがクローンせず
結果を確認できるよう意図的にコミットし、それ以外の生成物(テスト出力など)は
.gitignore で除外します。この区別を曖昧にするとリポジトリが汚れます。
db/schema.sql を唯一の正とし、Alembic の初回リビジョンはそれを実行するだけです。
SQLAlchemy は ORM ではなく Core として、接続とクエリ構築にのみ使います。
モデル定義と SQL の2箇所にスキーマを書くと、必ずどちらかが古くなります。
make test で 238 件を検証します(約60秒)。
うち2件は Excel の数式を LibreOffice で実評価するため、LibreOffice を
含まないコンテナ内では skip されます(148 passed, 2 skipped)。
この2件はホスト側で実行し、証跡を docs/images/17_excel_formula.png に残しています。
LibreOffice を顧客提供イメージに入れる理由がないため、この分割は意図的です。
| 対象 | 件数 | 主な検証内容 |
|---|---|---|
test_solver.py |
22 | 端数処理、実行可能性、ハード制約の独立監査、求解性能、再現性 |
test_service.py |
33 | 職種コードの変換、兼務の按分、在職期間、希望の振り分け、解の変換 |
test_sql.py |
48 | 全SQLをPostgreSQL 18のパーサで構文検証、テナント分離の担保、監査ログの追記専用 |
test_accounts.py |
44 | 権限の境界、トークンの単回使用と期限、世代によるセッション失効、監査記録 |
test_responsive.py |
24 | viewport・断点・固定タブ・タップ領域・表のカード化、職員向け表示の範囲 |
test_routes.py |
29 | 権限、入力検証、確定の可否、帳票出力、段階的強化 |
test_auth.py |
18 | ハッシュ、セッション改竄、試行回数制限、Cookie属性 |
test_excel.py |
8 | LibreOfficeで数式を評価し、Pythonの独立計算と突合 |
test_app.py |
11 | 疎通、設定の妥当性検査 |
データベースを起動せずに全ロジックを検証できる構成にしています。
app/service.pyは意図的にデータベースへ触らない純粋関数のみで構成app/repository.pyの SQL は定数に集約し、pglast(PostgreSQL 18 の パーサ実体)で構文検証。サーバを立てずにタイプミスや構文誤りを検出できる- ルーティングのテストは repository をモックに差し替える
特に重視している検証は次の3つです。
ハード制約の独立監査。 ソルバーの実装を信用せず、生成された割当そのものを
別実装の監査関数で再検査します(tests/demo_data.py の audit)。
ここで違反が出るなら制約の書き方が誤っています。
Excelの数式の実評価。 openpyxl は数式を評価しないため、LibreOffice で
csv に変換して実際に計算させ、Python の独立計算と突き合わせます。
「数式が正しく書けているか」まで検証できます。
テナント分離の担保。 すべての業務クエリが office_id で絞り込んで
いることをテストで強制しています。経路が増えても抜けません。
| 職員数 | 日数 | 変数概算 | 求解時間 | ステータス | 基準違反 |
|---|---|---|---|---|---|
| 20 | 31 | 3,100 | 0.63 s | OPTIMAL | 0 件 |
| 30 | 31 | 4,650 | 1.67 s | OPTIMAL | 0 件 |
| 40 | 31 | 6,200 | 1.82 s | OPTIMAL | 0 件 |
| 60 | 31 | 9,300 | 6.16 s | OPTIMAL | 0 件 |
OPTIMAL は最適性を証明済みという意味です。
なお当初は10秒の上限に達しても証明できず FEASIBLE 止まりでしたが、
休業日を公休に固定する制約を明示したところ全規模で OPTIMAL になりました。
ソルバーのパラメータ調整より、業務上あたりまえの制約をモデルに書くほうが効きます。
- 勤務形態一覧表(xlsx) — 兼務の按分と日別判定を含む
- シフト表画面(基準充足) — ブラウザで開いてください
- シフト表画面(基準違反あり) — 違反日が赤で表示されます
サンプルでは 月間の常勤換算では全職種が基準を満たしているのに、 日別では違反が出ています。機能訓練指導員が2名しかいないため、 両方が休む日に0になるからです。
月次集計だけを見ていると「適合」に見えてしまう。 この乖離を可視化することが、本ソリューションの中核です。
MIT License — LICENSE を参照してください。
- コミットメッセージは Conventional Commits 形式(
feat(scope): summary) - 静的解析は ruff(
make lint) - 生成AI(Claude、Gemini CLI、GitHub Copilot)を開発支援に使用しています。 製品機能としては生成AIを組み込んでいません(上記「生成AIを製品に組み込まない理由」参照)