# -*- coding: utf-8 -*-
"""第7回 PDF「確認事項(第7回)」の自己点検。
原稿 (docs/handoff/TeamWorks確認事項_第7回_20260910.md) の文言が
すべて PDF に含まれているか、フォントが日本語フォントで埋め込まれているかを見る。
PDF のパスは第1引数で渡せる。省略時はこのスクリプトと同じディレクトリ。
check_teamworks_qa6_pdf.py(第6回用)と同じ構成。
★2026-09-10 の改訂に追随済み★
1. 旧第4部(ダンプ元サーバが MariaDB)は削除された。旧5→4、旧6→5。
MariaDB 関連は第1部 1-4(謝意と当方の対応状況)だけを検査する。
2. 第3部の依頼は A-2 / A-3b / B-2 / B-3b の4件のみ。
B-1(外部キー/索引)と A-1(主キー)の取り下げは 1-5 で検査する。
3. 質問は3件。サマリ表・おわりにの優先順表も3件に追随している。
★must に「ページを跨ぐ語」を入れないこと★
抽出テキストにフッター(D-Watch / DFA Robotics とページ番号)が挟まり、
空白を詰めても一致しません。第3回で bigint auto_increment が実際に引っかかりました。
★4 の検査があっても、表の列幅崩れの目視確認は省かないこと★
4 は「識別子が行の途中で割れていないか」を抽出テキストで見るだけで、
枠のはみ出し・改ページ位置・重なりは見られません(pdf/README.md)。
"""
import os
import re
import sys
from pypdf import PdfReader
PDF = (sys.argv[1] if len(sys.argv) > 1 else
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"DWatch_確認事項_第7回_20260910.pdf"))
if not os.path.exists(PDF):
raise SystemExit("PDF が見つからない: %s\n"
"先に make_teamworks_qa7_pdf.py を実行するか、"
"PDF のパスを第1引数で渡すこと。" % PDF)
raw = open(PDF, "rb").read()
reader = PdfReader(PDF)
pages = [p.extract_text() for p in reader.pages]
text = "\n".join(pages)
flat = re.sub(r"\s+", "", text)
print("== 1. ページ数とサイズ ==")
print("ページ数:", len(reader.pages), " サイズ:", len(raw), "bytes",
"(%.1f KB)" % (len(raw) / 1024))
print("抽出文字数:", len(text))
print("\n== 2. フォント埋め込み ==")
print("/FontFile2 出現回数:", raw.count(b"/FontFile2"))
biz = sorted(set(re.findall(rb"/([A-Z]{6}\+[A-Za-z0-9\-]*BIZ[A-Za-z0-9\-]*)", raw)))
print("BIZUDGothic を含むフォント名:", biz)
if not biz:
print(" ★NG★ BIZ UDGothic が埋め込まれていない")
# 中国語向けフォントが混ざっていないこと(CLAUDE.md §0-3)
CN = [b"SimSun", b"SimHei", b"MicrosoftYaHei", b"MSYaHei",
b"NotoSansSC", b"NotoSerifSC", b"SourceHanSansSC", b"SourceHanSerifSC"]
hit = [n.decode() for n in CN if n in raw]
print("中国語向けフォント:", hit if hit else "混入なし")
if hit:
print(" ★NG★ 中国語向けフォントが混ざっている")
print("\n== 3. 原稿の全項目が含まれるか ==")
must = [
# 表題・前文
"確認事項(第7回)", "TeamWorks(DB開発側)",
"作成日:2026-09-10", "DFARobotics",
"最新のDDL(clean_watch_20260909.sql)をお送りいただき",
"ありがとうございました",
"2026-09-09受領。第6回でお願いしていた項目です",
"率のカラムを小数第一位まで持つ形(double(4,1))に変更いただき",
"当方からお願いしていた内容が、そのまま反映されておりました",
"これで当方の判定処理が正しく行えるようになります(詳細は第1部)",
"本書は次の5部構成です",
"今回は、お伺いすることを3件に絞りました",
"接続先/DDLの未反映4件/precheckの値域",
"お伺いする件数が少ないほど、早くお返事をいただけると考えたためです",
"第1部と第5部は当方からの共有ですので、回答は不要です",
# 前文の表(5部構成)
"1.照合結果のご共有", "いただいたDDLを当日中に照合しました",
"MariaDBの件と、依頼2件の取り下げもこちらです", "回答不要",
"2.接続先", "(ホスト名・ポート)",
"第6回からの継続。あとはこれだけです", "最優先",
"3.DDLの未反映項目", "(4件)",
"採番・桁数・DEFAULT・索引の食い違いです", "お手すきの折に",
"4.precheckに入る", "値の範囲",
"反映時期と既存データの扱い", "中",
"5.面積の型変更について", "共有のみ。質問はありません",
# 1. 照合結果
"1.照合結果のご共有(回答不要です)",
"第6回で", "受領しましたら、その日のうちに照合してご報告します",
"とお約束していた件です",
"2026-09-09に受領し、当日中に照合を完了しました",
# 1-1
"1-1.照合方法",
"ローカルのMySQLに読み込み、09-07版・09-02版と",
"information_schemaで機械的に差分を取りました",
"目視ではなく、列名・型の両方を双方向で突き合わせています",
# 1-2
"1-2.率のカラムが小数第一位になっていました(13列)",
"変更前", "変更後",
"tinyintunsigned(整数)", "double(4,1)", "(小数第一位)",
"対象は", "13列", "です",
"テーブル", "列",
"customer_summaries/", "facility_summaries",
"operation_ratio/launch_ratio/", "clean_ratio/error_ratio(各4列)",
"clean_status_histories/", "clean_statuses/", "clean_task_reports/",
"robots/", "section_robot_statuses", "clean_ratio(各1列)",
"これで当方の判定が正しく行えるようになりました",
"当方は清掃完了率から4段階(◎○△×)の判定を行っており",
"その境界値は90.0/90.1/70.0/70.1/10.0/9.9と、すべて小数第一位までです",
"整数で格納されていると",
"例えば89.6%が90に丸められて格納され",
"本来「○」であるべきものが「◎」になってしまう",
"という問題がありました",
"今回の変更で、この問題は解消しました",
# 1-3
"1-3.テーブル・列の増減はありませんでした",
"CREATETABLEは39で、09-07版・09-02版と同数です",
"テーブル名の集合も完全に一致しました",
"列の増減もありません",
"今回の変更は型(と一部のDEFAULT)だけでした",
"なお、面積のカラムもdecimalからdoubleに変わっておりましたので",
"この点だけ第5部で申し添えます",
"当方に実害はありません",
# 1-4 MariaDB(質問ではなく謝意と対応状況)
"1-4.本番がMariaDBであることをお教えいただき",
"本書をお送りする前に、本番サーバがMariaDBであることを",
"お教えいただきました。ありがとうございます",
"09-09版のダンプヘッダが5.5.5-10.11.13-MariaDB-logに",
"なっており、09-07版までのMySQL8.0.41と異なっておりましたので",
"お伺いしようとしていた件です",
"先にご回答いただけましたので、本書からは質問を取り下げました",
"当方の対応状況を申し添えます",
"アプリの実装に生のSQLは1件もありません",
"すべてPrismaのクエリビルダ経由ですので",
"現時点で対応が必要な箇所はありません",
"DBクライアントのドライバも、すでにMariaDB用のもの",
"(@prisma/adapter-mariadb)を使っております",
"どうしてもMariaDB特有のDMLを使いたい場合は",
"その箇所をお知らせください」とのご指示、承知しました",
"その必要が生じた際にご連絡します",
"なお、", "当方のローカルの検証環境はMySQLです", "ので",
"今後もし生のSQLを書く場合は、",
"MySQLとMariaDBの双方で通る書き方に注意いたします",
# 1-5 B-1 / A-1 の取り下げ
"1-5.これまでお願いしていた2件(外部キー/索引・主キー)を、",
"今回の依頼から外します",
"第5回・第6回でお願いしておりました",
"B-1(clean_timetable_idの外部キー/索引)",
"A-1(robot_error_reportsの主キー)",
"当方の実装が固まり、いずれも当方側で支障が無いことが",
"はっきりしました",
"今回の依頼から外させてください",
"B-1(clean_timetable_idの外部キー・索引)",
"当方は、これらのテーブルを全件取得し、",
"アプリ側で突き合わせる実装にしました",
"clean_timetables/clean_status_histories/",
"robot_error_reportsを、それぞれ",
"WHERE句なしの全件取得", "で読み出しています",
"読み出したあと、clean_timetable_idをキーにした対応表を",
"メモリ上に作り、そこで結合しています",
"つまり、clean_timetable_idで「検索」をしておりません",
"索引はWHERE句で絞り込むときに効くものですので、",
"この実装では使われません",
"この実装はすでに完成しており、外部キーが無いまま",
"清掃ログの画面が動いております",
"どの索引が必要かは、実際のクエリの形と、",
"実データの量・分布で決まります",
"当方はまだ実データを1行も見ておらず、",
"絞り込みのクエリも書いておりません",
"この状態で特定の索引を名指しでお願いするのは、",
"当方の根拠が薄いと考え直しました",
"したがって、現時点で支障はありません",
"将来クエリの形が変わって索引が必要になりましたら、",
"その時点で具体的なクエリを添えて、",
"あらためてご相談します",
"過去の確認書では外部キーの追加をお願いしておりましたが",
"当方の実装方針が定まった結果、当方の見立てが変わりました",
"お手数をおかけしました",
"A-1(robot_error_reportsの主キー)",
"error_idを識別子として使う方針は第4回のご回答でいただいており",
"当方はその前提で実装しております",
"主キーが無くても当方の実装は動いております",
"error_idにUNIQUEがありますので、一意性は担保されています",
"当方の実装で支障が無い以上、",
"DDLの変更をお願いする根拠がありませんので",
"こちらも依頼から外します",
# 2. 接続先
"をお教えいただけますでしょうか—最優先",
"第6回でもお願いした件で、恐れ入りますが繰り返しになります",
"参照用アカウントは2026-09-08にいただいておりますので",
"あとは接続先だけです",
"接続先のホスト名(またはIPアドレス)と、ポート番号",
"をお教えいただければ接続できます",
"あわせて確認させてください",
"接続元IPの制限はありますか",
"ある場合、", "当方の接続元をお伝えします", "のでお知らせください",
"SSL/TLS接続は必須でしょうか",
"証明書の受け渡し方法", "もお教えください",
"接続先はステージング環境でしょうか、本番環境でしょうか",
"当方の扱い(参照のみ/負荷のかけ方)を合わせたいためです",
"なぜ急いでお願いしているか",
"9月中にPPIH様向けにデモを実施する可能性があります",
"実データでお見せできるかどうかが、この接続情報で決まります",
# 2. 準備状況
"当方の準備状況",
"接続情報さえいただければ、すぐに使える状態になっております",
"済んでいること",
"いただいたDDLからローカルにDBを作成し、実DDLと列レベルで完全一致させました",
"差分0件を機械的に確認済み",
"prismadbpullでスキーマを取り込み済みです",
"今回の23列の型変更も反映済み",
"施設マスタ・スケジュール・D-Secログ・D-Secアラート・"
"清掃詳細ログの5画面が、",
"そのローカルDB経由で動くところまで実装済みです",
"接続先をいただければ、接続文字列を差し替えるだけです",
"また第6回でお伝えしたとおり、接続でき次第",
"error_typeの値の分布・robots.statusの実出現値",
"週次サマリのweekの値域など",
"当方で数えられるものは当方で確認します",
"その分だけ貴社への照会が減ります",
# 3. 未反映項目(4件)
"3.DDLの未反映項目(4件)を、",
"お手すきの折にご対応いただけますでしょうか",
"お忙しいところ恐縮です。3回目のお願いになります",
"第5回・第6回でお願いしていた項目のうち",
"4項目が09-09版でも未反映でした",
"今回のDDLは率と面積の型変更が主眼だったかと存じますので",
"これらは単に今回のスコープに入っていなかっただけかと思っております",
"状況だけ共有させてください",
"お願いしていた内容", "09-09版の実測",
"A-2", "履歴テーブルのidを", "AUTO_INCREMENTで採番",
"clean_status_histories.id/", "clean_timetable_histories.idとも",
"bigint(20)unsignedNOTNULL。", "AUTO_INCREMENT無し",
"A-3b", "facility_contacts.emailを", "varchar(100)に",
"varchar(255)DEFAULTNULLのまま",
"(contact_typeはtinyint(3)unsigned", "で反映済みです)",
"B-2", "robot_task_versions.clean_schedule_id", "にインデックス",
"KEYは", "robot_task_versions_robot_id_foreign", "(robot_id)のみ",
"clean_schedule_idのインデックスは0件",
"B-3b", "security_status_histories.reported_at", "にDEFAULT",
"timestampNOTNULL、", "DEFAULTなしのまま",
# 3-1
"3-1.この4件は、当方の設計上のお願いではなく、具体的なズレです",
"1-5で取り下げた2件(外部キー/索引・主キー)は",
"「DBの設計をどうするか」という判断を伴うお願い", "でした",
"当方の実装方針が固まり、必要がなくなりましたので",
"取り下げました",
"一方、この4件は性質が違います",
"いずれも、採番・桁数・DEFAULT・索引の有無という、",
"具体的で機械的な食い違いです",
"食い違いの中身", "補足",
"採番の仕組み", "idにAUTO_INCREMENTが無く、",
"誰が採番するのかが決まっていない状態", "です",
"桁数", "「emailはvarchar(100)とします」と",
"ご回答いただいた桁数が、", "varchar(255)のままです",
"索引の有無", "定義書でfkと記載されている列",
"ですが、", "実DDLには外部キーも索引もありません",
"DEFAULTの有無", "timestampNOTNULLに", "DEFAULT句がありません",
"いずれも「どちらが正なのか」が当方には判断できない箇所です",
"意図的なものでしたら、",
"その旨だけお知らせいただければ結構です",
# 3-2
"3-2.お手すきの折で結構です",
"いずれも当方の開発を止めておりません",
"お手すきの折に、まとめてご対応いただければ十分です",
"急かす意図はありません", "状況が分かれば大丈夫です",
"次回まとまった段階でDDLをいただければ、"
"同じように当日中に照合してご報告します",
# 4. precheck
"clean_timetables.precheckに入る値の範囲をお教えいただけますでしょうか",
"4-1.現状の理解",
"clean_timetables.precheckは現行",
"tinyintunsigned/既定値0", "です(09-09版でも変化なし)",
"DFA側で「0=本清掃/60=60分前/30=30分前」のように",
"分前の数値をそのまま入れる方向でご検討いただいている",
"と伺っております",
"設定値によっては", "20なら20分前", "のように",
"任意の分数が入りうるとも伺っております",
"4-2.お伺いしたいこと",
"この理解で合っていますでしょうか",
"いつ頃反映される見込みでしょうか",
"既存のデータはどうなりますでしょうか",
"1/2のまま残るのか、60/30に更新されるのか",
"新旧の値が混在する期間はありますでしょうか",
"0以外に予約値はありますでしょうか",
"特に4番をお伺いしたいです",
"混在すると、当方は1を「1分前」と読み違えてしまうためです",
"値だけを見て新旧を判別する方法がありませんので",
"期間が分かれば当方で切り替え時期を合わせられます",
"4-3.なぜお伺いするか",
"アラート画面で「60分前の異常」「30分前の異常」を出し分けるのに",
"この値を使います",
"値の意味を取り違えると、現場への連絡タイミングの判断を誤ります",
"「30分前のチェックで異常が出た」のか「60分前で出た」のかによって",
"現場に間に合うかどうかが変わるためです",
"DDLからは値の意味が読めませんでした",
"カラムコメントが空のため",
"型はtinyintunsigned(0〜255)ですので",
"60も30も20も、型の変更なしで入ります",
"つまり", "値の意味の変更はDDLには現れません", "ので、お伺いする次第です",
# 5. 面積(共有のみ)
"5.面積のカラムの型変更について(共有のみ。回答は不要です)",
"これは要望でも質問でもなく、事実の共有だけです",
"5-1.実測",
"09-09版で、面積のカラムも型が変わっておりました(", "10列", ")。",
"decimal(12,6)unsigned",
"double(12,6)", "(customer_summaries/", "facility_summariesはdouble(14,6))",
"task_area/clean_area",
"5-2.当方に実害はありません",
"この変更で当方が困ることはありません",
"理由は2つです", "理由",
"viewerに面積を表示する箇所は1つもありません",
"画面に出るのは%だけです",
"面積は完了率の計算材料としてのみ使う想定でしたが、"
"率を小数でいただけるようになったため、"
"当方で計算し直す必要も無くなりました",
"したがって、お戻しいただく必要はありません",
"5-3.意図の確認は今回は見送ります",
"当初は「率の小数化に伴う変更でしょうか」と",
"お伺いしようと考えておりましたが",
"上記のとおり当方に実害がありませんので、",
"今回は質問といたしません",
"お伺いする件数を絞り、",
"早くお返事をいただきたいためです",
"もし何か意図がおありでしたら、",
"次回ついでの折にお聞かせいただければ十分です",
# おわりに
"おわりに",
"優先度をつけるとすれば次の順です",
"順", "項目",
"アカウントはいただいておりますので、これだけで接続できます",
"9月中のデモを実データで行えるかがここで決まります",
"precheckの値の範囲と", "反映時期",
"新旧混在の期間があるかどうかを特に知りたいです",
"DDLの未反映4件", "(A-2/A-3b/B-2/B-3b)",
"お手すきの折で結構です。", "当方の開発は止まっていません",
"1(接続先)が最優先です",
"これだけ先にいただければ、当方で確認できることが一気に増え、"
"その分だけ貴社への照会が減ります",
"なお、これまでお願いしておりました外部キー/索引(B-1)と",
"主キー(A-1)は、",
"当方の実装方針が固まり必要がなくなりましたので、",
"今回の依頼から外しました", "(1-5に経緯を記しました)",
"率のカラムの小数化、そして本番がMariaDBであることのご教示、",
"あらためてありがとうございました",
"引き続きよろしくお願いします",
]
miss = [m for m in must if re.sub(r"\s+", "", m) not in flat]
print("検査項目:", len(must), " 欠落:", len(miss))
for m in miss:
print(" ★欠落★", m)
print("\n== 4. 識別子の語中改行 ==")
# wordWrap="CJK" は ASCII の語中でも折り返す。折り返されると抽出テキストに
# 改行が入るため、空白を詰めない生の抽出テキストで原形のまま探す。
IDENT = [
"clean_watch_20260909.sql",
"double(4,1)", "double(12,6)", "double(14,6)",
"decimal(12,6) unsigned", "tinyint unsigned", "tinyint(3) unsigned",
"information_schema", "CREATE TABLE",
"customer_summaries", "facility_summaries",
"operation_ratio", "launch_ratio", "clean_ratio", "error_ratio",
"clean_status_histories", "clean_statuses", "clean_task_reports",
"section_robot_statuses",
"task_area", "clean_area",
"prisma db pull", "error_type", "robots.status",
"robot_error_reports", "error_id",
"clean_status_histories.id", "clean_timetable_histories.id",
"bigint(20) unsigned NOT NULL", "AUTO_INCREMENT",
"facility_contacts.email", "varchar(100)", "varchar(255) DEFAULT NULL",
"contact_type",
"robot_task_versions.clean_schedule_id",
"robot_task_versions_robot_id_foreign",
"clean_schedule_id",
"security_status_histories.reported_at", "timestamp NOT NULL",
"clean_timetable_id", "clean_timetables",
"@prisma/adapter-mariadb",
# PRIMARY KEY / CONSTRAINT は A-1・B-1 を落としたので本文から消えている
"SSL/TLS",
"MySQL 8.0.41", "5.5.5-10.11.13-MariaDB-log",
"clean_timetables.precheck",
]
broken = [i for i in IDENT if i not in text]
print("検査した識別子:", len(IDENT), " 語中改行:", len(broken))
for b in broken:
print(" ★語中で割れている★", b)
print("\n== 5. 認証情報が載っていないこと ==")
# 参照用アカウントのユーザー名・パスワードは PDF に書かない(原稿にも無い)
SECRET = ["password", "PASSWORD", "IDENTIFIED BY", "passwd", "pwd="]
leak = [s for s in SECRET if s in text]
print("認証情報らしき語:", leak if leak else "なし")
print("\n== 6. 判定 ==")
ok = (not miss) and bool(biz) and (not hit) and (not broken) and (not leak)
print("結果:", "OK" if ok else "NG")
sys.exit(0 if ok else 1)