# -*- coding: utf-8 -*-
"""第6回 PDF「確認事項(第6回)」の自己点検。
原稿 (docs/handoff/TeamWorks確認事項_第6回_20260908.md) の文言が
すべて PDF に含まれているか、フォントが日本語フォントで埋め込まれているかを見る。
PDF のパスは第1引数で渡せる。省略時はこのスクリプトと同じディレクトリ。
check_teamworks_qa5_pdf.py(第5回用)と同じ構成に、
「4. 識別子の語中改行」の検査を足したもの。
★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_確認事項_第6回_20260908.pdf"))
if not os.path.exists(PDF):
raise SystemExit("PDF が見つからない: %s\n"
"先に make_teamworks_qa6_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 = [
# 表題・前文
"確認事項(第6回)", "TeamWorks(DB開発側)",
"作成日:2026-09-08", "DFARobotics",
"参照用アカウントを発行いただき、ありがとうございました",
"GRANTSELECTONclean_watch.*", "DB名clean_watch",
"2026-09-08に受領しております",
"また第5回以降にいただいた",
"error_typeの24値", "error_levelの4値",
"いずれもすでに反映しています",
"本書は次の4部構成です",
"質問は2件です。今回は質問を2件に絞ってあります",
"エラー種別についてお伺いする予定だった件は",
"当方で確認できるようになりましたので取り下げました",
# 前文の表
"解決した件のご共有", "いただいた回答をどう反映したかのご報告",
"接続先(ホスト名・ポート)", "のご連絡",
"あとはこれだけで接続できます", "最優先",
"最新のDDLのお願い", "第5回からの継続。A-1/A-3/B-3b",
"継続と保留", "DB更新用アカウント/B-1・B-2は保留で了解しております",
"回答不要", "高",
# 1-1
"1-1.参照用アカウントを受領しました",
"にて発行いただきました", "ありがとうございます",
"認証情報は当方の環境変数ファイルにのみ保管し",
"文書・リポジトリには残していません",
"あとは接続先さえ分かれば繋がる状態です",
# 1-2
"対応表を作成しました",
"Y-1/Y-2のご回答をありがとうございました",
"24値がすべてEmergencyStopのような英字の識別子であること",
"その一覧を整理した資料を作成し",
"実装の突き合わせ先としています",
"varchar(100)という長さも、この形であれば納得がいきました",
# 1-3
"エラー種別の扱いを変更しました",
"貴社側の追加対応は不要です",
"7分類", "ハンドルアップ", "LostLocation", "オフライン",
"ブラシエラー", "緊急停止", "充電不足", "その他",
"に寄せて表示する設計でおりました",
"いただいた24値と突き合わせたところ",
"7分類のうち3つに対応する値が見当たりませんでした",
"ここで当方が推測で対応付けを行うと",
"現場に誤った指示を出すおそれがあります",
"モーター異常を「ブラシエラー」と表示してしまう",
"そこで方針を変更し",
"DBに格納されている値を、変換せずそのまま画面に表示する方式にしました",
"画面にはEmergencyStopのような識別子がそのまま出ます",
"これは意図した挙動です",
"実データの分布を確認したうえで、必要になった時点で表示名を整えます",
"貴社側で追加の対応が必要になることはありません",
"貴社が後から値を追加された場合でも",
"当方の画面が壊れなくなります",
# 1-4
"Warning/Eventの非格納も、そのまま前提にしました",
"はファイルに記録するけど",
"データベースに情報として格納しない予定",
"とのご注記を承知しました",
"当方はFatal/Errorの2つだけが読める前提で実装します",
"重要度の表示もこの2段階を前提に設計しています",
"内容をみて後から判断するかもしれない",
"コード自体は4値すべてを受け取れる形にしてあります",
"当方の画面は壊れません",
# 1-5
"1-5.エラー種別についての照会は、当方での確認に切り替えました",
"第5回・第6回で",
"ハンドルアップやブラシエラーがerror_typeのどの値に当たるか",
"をお伺いする予定にしておりました",
"1-3のとおり、格納されている値をそのまま表示する方式に変更しました",
"分類の対応付けについてお伺いする必要がなくなりました",
"あわせて",
"参照用アカウントをいただきましたので、"
"error_typeに実際に入っている値は当方で確認できます",
"これらの照会は取り下げます",
"実データを確認して疑問が残った場合は、あらためてお伺いします",
"具体的な件数や値を添えてお尋ねします",
"ご判断いただきやすいかと存じます",
# 2. 接続先
"をお教えいただけますでしょうか",
"アカウントはいただきましたので、あとは",
"接続先のホスト名(またはIPアドレス)と、ポート番号",
"をお教えいただければ接続できます",
"あわせて確認させてください",
"接続元IPの制限はありますか",
"当方の接続元をお伝えします", "のでお知らせください",
"SSL/TLS接続は必須でしょうか",
"証明書の受け渡し方法", "もお教えください",
"接続先はステージング環境でしょうか、本番環境でしょうか",
"参照のみ/負荷のかけ方", "を合わせたいためです",
"なぜ急いでお願いしているか",
"9月中にPPIH様向けにデモを実施する可能性があります",
"実データでお見せできるかどうかが、この接続情報で決まります",
"サンプルデータでの動作確認は済んでおり",
"画面自体は動いています",
"実データに接続できれば、そのままデモに使えます",
"接続情報だけが、現時点で当方から埋められない部分です",
"接続でき次第、当方で確認する内容を先に共有しておきます",
"貴社への照会をこれ以上増やさないためです",
"繋がった時点で照会が減ります",
"robot_error_reports.error_type",
"実際に出現する値と件数の分布",
"第6回1-5(取り下げた照会の裏取り)",
"robots.status", "実際に出現する値",
"第2回でいただいたコード表", "との突き合わせ",
"週次サマリのweekに", "6が入る行があるか", "第5回X-1",
"week=0/month=0ANDweek=0の粒度", "第5回X-2",
"月跨ぎ週が", "2行に分かれて格納されているか", "第5回X-3",
"error_levelにWarning/Eventが入っていないか", "第6回1-4",
"貴社への照会を減らせます",
"確認した結果は、こちらからご報告します",
# 3. DDL
"最新のDDLをいただけますでしょうか(第5回からの継続)",
"第4回で", "A-1/A-3/B-3bは対応済み",
"とのご回答をいただきましたが",
"受領済みのclean_watch_20260907.sql",
"2026-09-0710:29受領", "には入っておりませんでした",
"3つの方法", "09-02版との全体差分",
"CREATETABLEブロックからの項目抽出",
"追加されるはずの制約名の全文検索",
"いずれも同じ結果でした",
"お願いしていた7項目のうち、反映されていたのは2項目です",
"受領済みDDLの状態",
"error_idを主キーと位置付けて対応",
"未反映", "PRIMARYKEY無し。UNIQUEKEYはerror_idのみ",
"contact_type/emailの型",
"一部のみ",
"customer_contacts.contact_typeはtinyintunsignedに修正済み",
"facility_contacts.emailがvarchar(255)のまま",
"security_status_histories.reported_at", "のDEFAULT",
"timestampNOTNULLのまま",
"反映いただいていた2件",
"clean_task_reportsのcreated_at/updated_atのDEFAULT",
"当方のスキーマにも取り込み済みです",
"参照用アカウントをいただいたので、当方でも確認できるようになりました",
"prismadbpullでスキーマを取り込めば",
"DDLの再受領を待たずに現状を確認できます",
"この点でも接続情報(第2部)が効いてきます",
"取り込めるのは「今こうなっている」という状態だけで",
"それが意図された最終形かどうかは当方には判断できません",
"まとまった段階でDDLをいただけると確実です",
"受領しましたら、当方でその日のうちに照合してご報告します",
"第5回と同じ運用です",
"急かす意図はありません",
"いずれも当方の開発を止めていません",
"状況が分かれば十分です",
# 4. 継続と保留
"4.継続と保留",
"継続:DB更新用アカウントの発行(第1回から)",
"status/comment/responded_at/resolved_at",
"の4カラムのみ", "UPDATE可、という権限をお願いしております",
"後で必要なテーブルに更新権限を付与する",
"とのご連絡をいただいており、承知しております",
"恐れ入りますが、発行の見込みをお教えいただけますでしょうか",
"当方の作業は止まっておりません",
"対応状況の更新機能は画面まで実装済みで",
"書き込み先を差し替えるだけの状態です",
"時期の目安さえ分かれば、そこに合わせて段取りします",
"保留で了解しているもの:B-1/B-2",
"外部キー3本とインデックスについて「後で登録する」との",
"保留で了解しております", "当方の開発は止まっていません",
"登録された際に、DDLの再受領時にあわせて確認します",
"データ保持ポリシーについて(催促ではありません)",
"過去データをどれだけ遡って保持するかについては",
"当方の都合で後回しにしております",
"画面の期間指定の上限に関わる話ですが、P1の実装には影響しません",
"貴社へのお願いではありませんので、ご対応は不要です",
# おわりに
"おわりに",
"今回は、お伺いすることを2件に絞りました",
"優先度をつけるとすれば次の順です",
"アカウントはいただいたので、これだけで接続できます",
"9月中のデモを実データで行えるかがここで決まります",
"受領後、当方でその日のうちに照合してご報告します",
"1(接続先)が最優先です",
"これだけ先にいただければ、当方で確認できることが一気に増えます",
"第2部の表に挙げた6件は、繋がった時点で当方で確認できますので",
"その分だけ貴社への照会が減ります",
"引き続きよろしくお願いします",
]
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", "GRANT SELECT ON clean_watch.*",
"error_type", "error_level", "EmergencyStop", "LostLocation",
"varchar(100)", "Fatal / Error",
"robot_error_reports.error_type", "robots.status",
"week = 0 / month = 0 AND week = 0",
"clean_watch_20260907.sql",
"customer_contacts.contact_type", "facility_contacts.email",
"security_status_histories.reported_at",
"clean_task_reports", "control_error_histories",
"robot_error_reports", "prisma db pull",
"created_at / updated_at",
"status / comment / responded_at / resolved_at",
"tinyint unsigned", "varchar(255)", "timestamp NOT NULL",
"PRIMARY KEY", "UNIQUE KEY", "CREATE TABLE", "SSL/TLS",
]
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)