システム全体像
糖化アンケートOCR照合システムは、歯科医院で実施する糖化関連アンケートの回収・OCR読み取り・照合・データ管理を行うシステムです。3つのプロジェクトで構成されています。
データフロー(2グループ体制)
Group A(QRコード組): 患者がQRコードからGoogleフォームに回答→スプレッドシートに自動保存。PDF紐づけツールでスキャンPDFと紐づけ。
Group B(紙回答組): 紙アンケートをスキャン→OCR処理→照合→スプレッドシートに送信。
データストア
| スプレッドシート | 内容 | 特徴 |
|---|---|---|
| メイン(回答データ) | Googleフォーム+OCRからの回答 | 患者1人=1行、No.で連番管理 |
| 検体PpP値シート | PEN/PYD/PpP値データ | 同一患者に複数行(歯ごと) |
| HbA1c追跡シート | HbA1c・体重等の時系列データ | 同一患者に複数行(測定日ごと)、動的列追加可 |
技術スタック
Python(PyMuPDF, OpenCV, Pillow)/ HTML+JS(GitHub Pages)/ Google Apps Script / Claude API / Gemini API / Google Spreadsheet + Drive
アンケート作成・開発
5ツール ▶概要
React+Viteで構築されたWebアプリ。医療機関を選択するとGoogleフォームのプレフィルURLが自動生成され、QRコードに変換してA4印刷用レイアウトに埋め込みます。
アルゴリズム
getFormInfo)でフォームの公開URLとentry IDを取得2. 医療機関名をentry IDに埋め込んだプレフィルURLを生成
3. URLをQRコードライブラリで変換
4. A4レイアウトにQRコード+アンケート内容を配置
概要
表面と裏面が別々にスキャンされたPDFファイルを、1つの2ページPDFに結合するツールです。
アルゴリズム
- pdf-libでブラウザ上でPDFを結合(サーバー不要)
- PDFDocument.load()で2つのPDFを読み込み → PDFDocument.create() → copyPages()で表裏を結合 → save()
- 1組モード:表面・裏面を個別にドロップして結合
- バッチモード:複数PDFをまとめて選択 → ファイル名順でペアリング(奇数番目=表面、偶数番目=裏面)→ 一括結合
- Google Driveへ直接アップロード(GAS uploadSplitPdfアクション)またはローカルダウンロード
概要
元PDFフォルダ(Google Drive)内の全PDFファイルのページ数を一括チェックし、2ページ(表裏セット)以外のファイルを検出・警告します。
アルゴリズム
- GAS getDrivePdfListアクションで元PDFフォルダの全ファイルリストを取得
- 各ファイルにGAS getPdfPageCountアクションを3件ずつ並列で呼び出し
- ページ数 === 2 → ✅ 正常、それ以外 → ⚠️ 異常として判定
- サマリーカード(合計/正常/異常/取得失敗)で結果を可視化
- 異常ファイルを先頭にソートし、PDFビューアや結合ツールへのリンクを表示
- フィルタ機能(全て/正常のみ/異常のみ)
概要
患者がQRコードからアクセスするGoogleフォーム。医療機関名が事前選択された状態で開きます。回答はスプレッドシートに自動保存されます。
概要
アンケート作成ツールのソースコードリポジトリ。React+Vite構成でVercelにデプロイされています。
OCR読み取り・照合
13ツール ▶概要
メインのPython処理パイプライン(verify_survey.py、約3,300行)。スキャンPDFを画像化し、傾斜補正→基準点検出→質問領域切り出し→AI OCR→照合HTML生成を行います。
処理フロー
- PDF選択 → PyMuPDFで画像変換(200-300 DPI)
- 傾斜補正(2方式: テンプレートマッチング / Hough変換)
- 基準点検出(2点参照方式)→ 相対座標で領域切り出し
- Claude API / Gemini APIでOCR読み取り
- 照合用HTML生成 → ブラウザで確認・編集
- JSON保存 → PDF移動
傾斜補正アルゴリズム
1. 二値化(Otsu法)→ 左マージン(幅の15%)で水平射影
2. テキスト行を検出し「質問」文字列のテンプレートを抽出
3.
cv2.matchTemplate() で画像全体からマッチング(閾値0.7)4. 2点(質問1+質問13[新]/質問15[旧])の座標で角度算出:
atan2((x2-x1)/(y2-y1))5.
cv2.getRotationMatrix2D() で回転補正方式2: Hough変換(フォールバック)
1. Canny エッジ検出 → HoughLinesP で直線検出
2. 近水平線(|角度| < 15°)をフィルタ → 中央値を傾き角度とする
2点参照方式
- 新フォーマット(2ページ): 質問1 + 質問13 を基準点
- 相対座標(0.0〜1.0)で全質問の領域を定義(A4 300DPI基準: 2480×3509px)
- 用紙の位置ずれ・傾きに依存しない安定した切り出し
OCRモデル
claude-sonnet-4-20250514, temperature=0, max_tokens=4096- Gemini API:
gemini-3-flash-preview(フォールバック)- 2段階方式: フルページ一括OCR → 個別フィールド再OCR(ID・生年月日等)
- 信頼度スコア(high/medium/low)+候補値の出力
概要
Pythonなしでブラウザ上で完結するOCR照合ツール。Google DriveのPDFをPDF.jsで表示し、Gemini APIでOCR処理します。
左右パネル同期スクロール
getBoundingClientRect() で取得2. 左パネル: 矩形をパネル上端に移動するスクロール量を算出(限界クランプ)
3. スクロール後のPDF矩形の予測画面Y座標を計算
4. 右パネル: 回答ブロックを予測Y座標に合わせるスクロール量を算出
5. 右パネルも限界到達 → 不足分だけ左パネルのスクロールを戻す
6. 両パネルを同時に
scrollTo({ behavior: 'smooth' })
矩形データ管理
質問位置はマウスドラッグで定義→GAS ScriptPropertiesに永続保存。localStorageをキャッシュとして併用。
PDF表示
PDF.js canvas描画を基本とし、失敗時はBlob URL + embed方式にフォールバック(CSP制限対策)。
→ アプリを開く概要
Google Driveの元PDF(ScanData)フォルダ内の全PDFを一括でGemini OCR処理。進捗バー表示、個別再実行可能。結果はGAS ScriptPropertiesにJSON保存。
→ アプリを開く概要
複数人が同時に照合作業できる共有版。排他ロック機構で衝突を防ぎます。
排他ロック機構
- ステータス:
unverified → in_progress → verified- ロック取得時にlockedBy(ユーザー名)とlockedAt(タイムスタンプ)を記録
- 30分タイムアウトで自動解除(他のユーザーが取得可能に)
- Shift+OKで残り質問を一括確定
概要
Googleフォームの回答データとスキャンPDFを紐づけるツール。スコアベースマッチングで候補を自動提示します。
マッチングアルゴリズム
- ID番号: 0-30点(完全一致=30点、部分一致=Levenshtein類似度×30)
- 名前: 0-30点(Levenshtein距離による類似度)
- 生年月日: 0-20点(完全一致のみ)
- 医療機関名: 0-20点(完全一致=20点)
- 合計スコア70%以上で候補として提示
Levenshtein類似度 = (maxLen - distance) / maxLen
Q14抜歯位置入力
紐づけ時にQ14(抜歯位置×3行: 右/左+上/下+番号)を手入力。紐づけ完了後、PDFは入力済みフォルダへ自動移動。
→ アプリを開くデータ管理
8ツール ▶概要
Google DriveのPDFをGemini 3 Flash Previewで自動読み取りし、医療機関名・患者ID・名前・生年月日・QRチェック有無を抽出してインデックス化。
アルゴリズム
2. 各PDFをBase64エンコード → Gemini 3 Flash Preview APIに送信
3. OCRプロンプトで5フィールド(hospital, patientId, patientName, birthdate, qrCheckbox)を抽出
4. 結果をスプレッドシート「PDFインデックス」シートに保存
概要
検体サンプルの氏名(カタカナ)や生年月日から、スプレッドシートの通し番号(No.)を検索するツール。
あいまい検索アルゴリズム
- 生年月日でも絞り込み可能
- 検索結果をLevenshtein距離昇順でソート
- No.クリックでクリップボードにコピー
概要
3つのスプレッドシートを患者No.で1行/患者に統合し、CSVダウンロードするツール。
統合アルゴリズム
1. メインシートを読み込み、No.をキーにpatientMapを構築
2. PpPシートを読み込み、各患者のPpPデータリストを収集 → maxPpp算出
3. 追跡シートを読み込み、各患者の追跡データリストを収集 → maxTrack算出
4. 統合ヘッダー = 基本列 + PpP1_〜PpPn_列 + 追跡1_〜追跡n_列
5. 各患者を1行に展開、No.昇順ソート
HTML側: 列グループ色分け(基本=白、PpP=水色、追跡=薄紫)、BOM付きUTF-8 CSV
検査値入力
4ツール ▶概要
HbA1c・体重・血糖値等の追跡データを入力・閲覧・編集するWebツール。動的に列(検査項目)を追加可能。
主な機能
- 患者No.で検索→過去データ一覧表示
- 動的列追加: 「列を追加」ボタンで新しい検査項目を追加
- 編集モード: 既存データの上書き更新(GAS updateTracking)
- URL入力対応: リンクとして表示
- 縦型テーブル: 項目名を縦に並べて表示
概要
HbA1c追跡スプレッドシートを直接開くリンク。Googleスプレッドシートで直接データを確認・編集できます。
概要
検体のPEN・PYD値を入力し、PpP値を自動計算して記録するツール。
PpP値計算
PpP値 = PYD ÷ PEN(小数点以下4桁)入力フロー:
1. 患者No.で検索 → 医療機関名・名前・生年月日を自動取得
2. 歯の位置(右/左+上/下+番号)を入力
3. PEN・PYD値を入力 → PpP値が自動計算
4. 登録ボタンで保存(検索済みでないと登録不可)
バリデーション
patientConfirmedフラグ: 検索ボタンを押して患者情報が確認されていないと登録できない仕組み。検索忘れによる空データ登録を防止。
概要
検体PpP値スプレッドシートを直接開くリンク。
データフロー
3ツール ▶GAS API リファレンス
Google Apps Script Web App(Code_gs_complete.js)はJSONP形式で通信し、actionパラメータでルーティングします。
主要アクション一覧
| アクション | 機能 | 認証 |
|---|---|---|
| getHospitalList | 医療機関リスト取得 | なし |
| addHospital | 医療機関追加 | なし |
| addSurveyResponse | アンケート回答追加(No.自動採番) | なし |
| viewData | 全回答データ取得 | パスワード |
| getNoList | 患者No.一覧取得 | なし |
| addSpecimen | PpP検体データ追加(10列) | なし |
| getSpecimenList | PpP検体データ取得 | なし |
| addTracking | 追跡データ追加 | なし |
| getTrackingList | 追跡データ取得 | なし |
| updateTracking | 追跡データ更新 | なし |
| addTrackingColumn | 追跡シートに列追加 | なし |
| generateBigData | 3シート統合データ生成 | パスワード |
| getDrivePdfList | Drive PDFファイル一覧 | なし |
| indexPdfWithGemini | PDFをGemini OCRでインデックス化 | なし |
| lockOcrResult / unlockOcrResult | OCR結果の排他ロック | なし |
| askAssistant | AIアシスタント(Gemini) | パスワード |