# -*- coding: utf-8 -*-
"""第4回 PDF「確認事項(第4回)」の自己点検。
原稿 (docs/handoff/TeamWorks確認事項_第4回_20260907.md) の文言が
すべて PDF に含まれているか、フォントが日本語フォントで埋め込まれているかを見る。
PDF のパスは第1引数で渡せる。省略時はこのスクリプトと同じディレクトリ。
check_teamworks_qa3_pdf.py(第3回用)と同じ構成。
★must に「ページを跨ぐ語」を入れないこと★
抽出テキストにフッター(D-Watch / DFA Robotics とページ番号)が挟まり、
空白を詰めても一致しません。第3回で bigint auto_increment が実際に引っかかりました。
"""
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_確認事項_第4回_20260907.pdf"))
if not os.path.exists(PDF):
raise SystemExit("PDF が見つからない: %s\n"
"先に make_teamworks_qa4_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 = [
# 表題・前文
"確認事項(第4回)", "TeamWorks(DB開発側)", "clean_watch_20260907.sql",
"作成日:2026-09-07", "DFARobotics",
"修正版のDDLをありがとうございました", "受領したその日に照合しています",
"本書は次の3部構成です",
# サマリ表
"1.修正版DDLの照合結果", "7件中2件",
"5件がまだ入っていません", "状況をお知らせください",
"2.新しくお願いしたいこと", "W-1は集計の食い違いに直結します",
"3.第3回からの継続", "まだ回答をいただいていないもの",
# D-3 の引用ブロック
"第3回でお伺いしたD-3(セキュリティ解除の判定)は当方で解決しました",
"制御盤の生死報告", "セキュリティ解除の状態",
"セキュリティ解除はエラーにならず別の値で管理される",
"security_statuses.status=0の継続から当方で導出します",
"回答は不要です",
# 1. 照合結果
"clean_watch_20260902.sqlとの差分は14行でした",
"テーブル数は39で変化ありません",
"反映されていたもの(2件)",
"customer_contacts.contact_type", "bigintunsigned", "tinyintunsigned",
"clean_task_reports", "DEFAULTCURRENT_TIMESTAMP", "ONUPDATE",
"★まだ入っていないもの(5件)★",
"A-1", "A-2", "A-3b", "B-1", "B-2", "B-3b",
"error_idを主キーと位置付けて対応", "PRIMARYKEY無しのまま",
"履歴テーブルのidを", "auto_incrementで採番",
"AUTO_INCREMENT無し", "50で09-02版と同数",
"facility_contacts.emailがvarchar(255)のまま",
"外部キー3本を追加", "3本とも無し",
"CONSTRAINT総数が48で09-02版と同数",
"clean_statuses_clean_timetable_id_foreign",
"robot_task_versions.clean_schedule_id",
"KEYはrobot_idのみ",
"security_status_histories.reported_at",
"timestampNOTNULLのまま",
"3つの方法", "各CREATETABLEブロックからの項目抽出",
"追加されるはずの制約名の全文検索",
"これから対応", "意図的に見送られた",
"まとまった段階で改めてDDLをいただけますか",
"急かす意図はありません",
"いずれも当方の開発を止めていません",
"状況が分かれば十分です",
# 2. W-1
"2.新しくお願いしたいこと(2件)",
"W-1", "月跨ぎの週を、どちらの月に数えていますか",
"週次サマリを作成いただくのはTeamWorks様側",
"毎週日曜6:00", "前週日〜土",
"両者で「週の帰属」が食い違うと、同じ週が別の月に集計されます",
"その月の1日を含む週が、その月の第1週",
"1日でも翌月が混ざれば、その週は翌月の第1週",
"2026-08-23(日)〜08-29(土)", "8月の週",
"2026-08-30(日)〜09-05(土)", "9月第1週",
"2026-09-06(日)〜09-12(土)", "9月第2週",
"サマリテーブル側も同じルールでしょうか",
"週の開始日が属する月",
"当方の画面(9月第1週)と食い違います",
"どちらに合わせるべきかご意見をいただけますか",
"当方はどちらでも実装できます",
"月次・年次は暦月・暦年の日単位で締める",
# 2. W-2
"W-2", "robot_error_reports.error_typeに入る値の一覧をご作成いただけますか",
"実装は止めずに進めます", "未知の値はすべて「その他」に寄せます",
"一覧をいただければ分類の精度が上がります",
"清掃開始の60分前・30分前のロボット状態",
"robots.statusのコード表と突き合わせると",
"3つが対応先不明",
"待機中", "0Standby",
"充電中", "201ChargingLine/202ChargingPile",
"セキュリティ有効", "security_statuses.status=1から判定",
"ハンドルアップ", "緊急停止", "オフライン", "該当なし",
"網羅でなくて構いません",
"SELECTDISTINCTerror_type",
"急ぎではありません", "届いた時点で分類ルールに追加します",
# 3. 継続
"3.第3回からの継続(2件)",
"D-1", "D-4",
"link_schedules.intervals", "VarChar(120)",
"実際に入っている値の例を1〜2件",
"D-Secログの通信断判定が実装できません", "最優先でお願いします",
"PrecheckItem.idの切替対象",
"型はvarcharのままという理解でよいか",
"数値型に変わる可能性があるなら", "破壊的変更",
"DB更新用アカウントの発行",
"のみUPDATE可",
# 末尾
"おわりに",
"D-1→W-1→1の5件→W-2の順",
"D-1が無いとD-Secログの通信状態が出せません",
"実装が進む前に揃えておきたいです",
"当方の作業を止めていません",
"引き続きよろしくお願いします",
]
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. 判定 ==")
ok = (not miss) and bool(biz) and (not hit)
print("結果:", "OK" if ok else "NG")
sys.exit(0 if ok else 1)