# -*- coding: utf-8 -*-
"""
TeamWorks 宛 確認事項（第6回）を PDF にする。

第1回〜第5回（make_ddl_review_pdf.py / make_teamworks_qa2_pdf.py /
make_teamworks_qa3_pdf.py / make_teamworks_qa4_pdf.py /
make_teamworks_qa5_pdf.py）の体裁をそのまま踏襲した続編。
A4 縦 / reportlab / 日本語フォント埋め込み / ヘッダー罫線とページ番号。

フォントは BIZ UDGothic（日本語フォント）を埋め込む。
中国語向けフォントは漢字の字形が異なるため使わない。
フォールバックにも混ぜないこと。

BIZ UDGothic は等幅（半角は 0.5em 固定）なので、SQL の枠にもこのフォントを使う。
コードブロック内の空白は &nbsp; に置換して桁を保持する。

原稿は docs/handoff/TeamWorks確認事項_第6回_20260908.md。
文言・件数・表の内容は原稿から変更していない。

★列幅の決め方★
wordWrap="CJK" は ASCII の語中でも改行する。そのため
`security_status_histories.reported_at` / `clean_watch_20260907.sql` /
`customer_contacts.contact_type` のような長い識別子は
  (1) 行頭に来るよう <br/> で明示的に改行してから置く
  (2) 列の内寸（列幅 - 左右パディング 12pt）が識別子の幅
      （BIZ UDGothic は半角 0.5em なので フォントサイズ × 文字数 ÷ 2）
      を上回るように列幅を決める
の2点で語中改行を避けている。check_teamworks_qa6_pdf.py の
「4. 識別子の語中改行」がこれを機械的に検査する。
"""
import os

from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.fonts import addMapping
from reportlab.platypus import (
    BaseDocTemplate, CondPageBreak, Frame, PageTemplate, Paragraph, Spacer,
    Table, TableStyle,
)

FONT_DIR = "C:/Windows/Fonts/"
pdfmetrics.registerFont(TTFont("JP", FONT_DIR + "BIZ-UDGothicR.ttc", subfontIndex=0))
pdfmetrics.registerFont(TTFont("JP-B", FONT_DIR + "BIZ-UDGothicB.ttc", subfontIndex=0))
# <b> タグを太字フォントへ対応づける
addMapping("JP", 0, 0, "JP")
addMapping("JP", 1, 0, "JP-B")

# 出力先。既定はこのスクリプトと同じディレクトリ。
# 環境変数 DWATCH_PDF_OUT で変えられる（例: 送付前に Downloads へ出す）。
OUT_DIR = os.environ.get("DWATCH_PDF_OUT",
                         os.path.dirname(os.path.abspath(__file__)))
OUT = os.path.join(OUT_DIR, "DWatch_確認事項_第6回_20260908.pdf")

# 第1回 PDF と同一のパレット
INK = colors.HexColor("#1e293b")
SUB = colors.HexColor("#475569")
MUT = colors.HexColor("#94a3b8")
LINE = colors.HexColor("#cbd5e1")
HEAD_BG = colors.HexColor("#f1f5f9")
CODE_BG = colors.HexColor("#f8fafc")
NG = colors.HexColor("#b91c1c")   # 実装が止まる区分の強調色
WN = colors.HexColor("#b45309")   # 定義の確認区分の強調色

ss = getSampleStyleSheet()


def st(name, size, leading, font="JP", color=INK, space_after=0, space_before=0,
       left=0, first=0):
    """段落スタイルを作る。left/first でぶら下げインデントを表現する。"""
    s = ParagraphStyle(
        name, parent=ss["Normal"], fontName=font, fontSize=size, leading=leading,
        textColor=color, alignment=TA_LEFT, spaceAfter=space_after,
        spaceBefore=space_before, wordWrap="CJK",
        leftIndent=left, firstLineIndent=first,
    )
    s.bulletFontName = font
    return s


S_TITLE = st("t", 17, 24, "JP-B", INK, 2)
S_SUB = st("s", 9.5, 15, "JP", SUB, 0)
S_H2 = st("h2", 12.5, 18, "JP-B", INK, 4, 10)
S_H3 = st("h3", 10.5, 16, "JP-B", INK, 3, 8)
S_BODY = st("b", 9.5, 15.5, "JP", INK, 4)
S_LEAD = st("ld", 9.5, 15.5, "JP", INK, 2)          # 直後にコード枠や表が続く導入文
S_NOTE = st("n", 9, 14.5, "JP", SUB, 4)
S_LIST = st("li", 9.5, 15.5, "JP", INK, 2, 0, 15, -15)   # 「1. 」のぶら下げ
S_BULL = st("bu", 9.5, 15.5, "JP", INK, 2, 0, 14, -14)   # 「・」のぶら下げ
S_CELL = st("c", 8.8, 13.8, "JP", INK)
S_CELL_S = st("cs", 8.8, 13.8, "JP", SUB)
S_CODE = st("cd", 8.2, 12.6, "JP", INK)
S_TH = st("th", 8.4, 12, "JP-B", SUB)
S_QUOTE = st("q", 9.2, 14.8, "JP", SUB)
S_CALL = st("ca", 10.5, 17, "JP-B", INK)   # 目立たせたい一文を囲む枠の中身


def P(t, s=S_CELL):
    return Paragraph(t, s)


def on_page(canvas, doc):
    """全ページ共通のヘッダー罫線・フッター・ページ番号。第1回の PDF と同一。"""
    canvas.saveState()
    canvas.setFont("JP", 8)
    canvas.setFillColor(MUT)
    canvas.drawString(18 * mm, 12 * mm, "D-Watch / DFA Robotics")
    canvas.drawRightString(A4[0] - 18 * mm, 12 * mm, "%d" % doc.page)
    canvas.setStrokeColor(LINE)
    canvas.setLineWidth(0.4)
    canvas.line(18 * mm, 15.5 * mm, A4[0] - 18 * mm, 15.5 * mm)
    canvas.restoreState()


TBL_STYLE = [
    ("FONTNAME", (0, 0), (-1, -1), "JP"),
    ("BACKGROUND", (0, 0), (-1, 0), HEAD_BG),
    ("LINEBELOW", (0, 0), (-1, 0), 0.6, LINE),
    ("LINEBELOW", (0, 1), (-1, -2), 0.3, LINE),
    ("VALIGN", (0, 0), (-1, -1), "TOP"),
    ("TOPPADDING", (0, 0), (-1, -1), 5),
    ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
    ("LEFTPADDING", (0, 0), (-1, -1), 6),
    ("RIGHTPADDING", (0, 0), (-1, -1), 6),
]


def table(rows, widths, cell_styles=None, head_color=None):
    """rows[0] を見出し行とする表。cell_styles で列ごとの段落スタイルを指定する。"""
    if cell_styles is None:
        cell_styles = [S_CELL] * len(rows[0])
    data = [[P(c, S_TH) for c in rows[0]]]
    for r in rows[1:]:
        data.append([P(c, cell_styles[i]) for i, c in enumerate(r)])
    t = Table(data, colWidths=widths, repeatRows=1)
    style = list(TBL_STYLE)
    if head_color is not None:
        style.append(("TEXTCOLOR", (0, 1), (0, -1), head_color))
    t.setStyle(TableStyle(style))
    return t


def code(lines, width):
    """SQL などを等幅フォントの枠に入れる。空白は &nbsp; にして桁を保つ。"""
    esc = []
    for ln in lines:
        ln = ln.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
        esc.append(ln.replace(" ", "&nbsp;"))
    t = Table([[P("<br/>".join(esc), S_CODE)]], colWidths=[width])
    t.setStyle(TableStyle([
        ("FONTNAME", (0, 0), (-1, -1), "JP"),
        ("BACKGROUND", (0, 0), (-1, -1), CODE_BG),
        ("LINEBEFORE", (0, 0), (0, -1), 1.8, LINE),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 6),
    ]))
    return t


def quote(text, width):
    """いただいた回答の引用。左に縦罫線を引く。"""
    t = Table([[P(text, S_QUOTE)]], colWidths=[width])
    t.setStyle(TableStyle([
        ("FONTNAME", (0, 0), (-1, -1), "JP"),
        ("LINEBEFORE", (0, 0), (0, -1), 1.8, MUT),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("TOPPADDING", (0, 0), (-1, -1), 3),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 0),
    ]))
    return t


def callout(text, width):
    """一番伝えたい一文を薄い背景の枠に入れる（引用ではないので本文色）。"""
    t = Table([[P(text, S_CALL)]], colWidths=[width])
    t.setStyle(TableStyle([
        ("FONTNAME", (0, 0), (-1, -1), "JP"),
        ("BACKGROUND", (0, 0), (-1, -1), CODE_BG),
        ("LINEBEFORE", (0, 0), (0, -1), 1.8, MUT),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("TOPPADDING", (0, 0), (-1, -1), 7),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
        ("LEFTPADDING", (0, 0), (-1, -1), 10),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
    ]))
    return t


def num(items, start=1):
    """「1. 」形式の番号付きリスト。"""
    return [Paragraph("%d. %s" % (i, t), S_LIST)
            for i, t in enumerate(items, start)]


def bull(items):
    """「・」形式の箇条書き。"""
    return [Paragraph("・%s" % t, S_BULL) for t in items]


# ---------------------------------------------------------------- 表データ
# 前文: 4部構成（第4部「エラー情報についての確認」は 1-5 のとおり取り下げた）
SUMMARY_ROWS = [
    ["#", "内容", "優先度"],
    ["1. 解決した件のご共有", "いただいた回答をどう反映したかのご報告",
     "<b>回答不要</b>"],
    ["2. 接続先（ホスト名・ポート）<br/>のご連絡",
     "<b>あとはこれだけで接続できます</b>", "<b>最優先</b>"],
    ["3. 最新のDDLのお願い", "第5回からの継続。A-1 / A-3 / B-3b", "高"],
    ["4. 継続と保留",
     "DB更新用アカウント / B-1・B-2 は保留で了解しております", "—"],
]

# 2. 接続先: あわせて確認させてください
CONN_ROWS = [
    ["#", "確認したいこと", "補足"],
    ["1", "<b>接続元IPの制限はありますか</b>",
     "ある場合、<b>当方の接続元をお伝えします</b>のでお知らせください"],
    ["2", "<b>SSL/TLS 接続は必須でしょうか</b>",
     "必須の場合、<b>証明書の受け渡し方法</b>もお教えください"],
    ["3", "<b>接続先はステージング環境でしょうか、本番環境でしょうか</b>",
     "当方の扱い（参照のみ / 負荷のかけ方）を合わせたいためです"],
]

# 2. 接続先: 接続でき次第、当方で確認する内容
AFTER_ROWS = [
    ["#", "確認する内容", "もともとの照会"],
    ["1", "robot_error_reports.error_type に<br/>"
          "<b>実際に出現する値と件数の分布</b>",
     "第6回 1-5<br/>（取り下げた照会の裏取り）"],
    ["2", "robots.status に<b>実際に出現する値</b>",
     "第2回でいただいたコード表<br/>との突き合わせ"],
    ["3", "週次サマリの week に <b>6 が入る行があるか</b>", "<b>第5回 X-1</b>"],
    ["4", "week = 0 / month = 0 AND week = 0 の粒度", "<b>第5回 X-2</b>"],
    ["5", "月跨ぎ週が<b>2行に分かれて格納されているか</b>", "<b>第5回 X-3</b>"],
    ["6", "error_level に Warning / Event が入っていないか", "第6回 1-4"],
]

# 3. 最新のDDL
DDL_ROWS = [
    ["#", "内容", "受領済みDDLの状態"],
    ["A-1", "robot_error_reports の<br/>error_id を主キーと位置付けて対応",
     "<b>未反映</b><br/>PRIMARY KEY 無し。UNIQUE KEY は error_id のみ"],
    ["A-3", "contact_type / email の型",
     "<b>一部のみ</b><br/>"
     "customer_contacts.contact_type は<br/>"
     "tinyint unsigned に修正済み。<br/>"
     "<b>facility_contacts.email が varchar(255) のまま</b>"],
    ["B-3b", "security_status_histories.reported_at<br/>の DEFAULT",
     "<b>未反映</b><br/>timestamp NOT NULL のまま"],
]

# おわりに: 優先順
PRIO_ROWS = [
    ["順", "項目", "理由"],
    ["1", "<b>接続先（ホスト名・ポート）</b>",
     "<b>アカウントはいただいたので、これだけで接続できます</b>。"
     "9月中のデモを実データで行えるかがここで決まります"],
    ["2", "<b>最新のDDL</b>",
     "受領後、当方でその日のうちに照合してご報告します"],
]


def build():
    doc = BaseDocTemplate(
        OUT, pagesize=A4,
        leftMargin=18 * mm, rightMargin=18 * mm,
        topMargin=18 * mm, bottomMargin=20 * mm,
        title="D-Watch 確認事項（第6回）",
        author="DFA Robotics",
        subject="接続先のご連絡のお願いと、最新のDDLのお願い",
    )
    frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="f")
    doc.addPageTemplates([PageTemplate(id="p", frames=[frame], onPage=on_page)])

    W = doc.width
    CW = W - 6 * mm
    story = []

    # ---------------------------------------------------------- 表題と前文
    story.append(Paragraph("確認事項（第6回）", S_TITLE))
    story.append(Paragraph(
        "宛先: TeamWorks（DB開発側）　作成日: 2026-09-08　"
        "作成者: DFA Robotics", S_SUB))
    story.append(Spacer(1, 9))

    story.append(Paragraph(
        "<b>参照用アカウントを発行いただき、ありがとうございました。</b>"
        "（GRANT SELECT ON clean_watch.* / DB名 clean_watch。"
        "2026-09-08 に受領しております）", S_BODY))
    story.append(Paragraph(
        "また第5回以降にいただいた <b>error_type の24値</b>と "
        "<b>error_level の4値</b>のご回答も、ありがとうございました。"
        "<b>いずれもすでに反映しています。</b>", S_BODY))
    story.append(Paragraph("本書は次の4部構成です。", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(table(SUMMARY_ROWS,
                       [50 * mm, W - 72 * mm, 22 * mm],
                       [S_CELL, S_CELL, S_CELL]))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        "<b>質問は2件です。今回は質問を2件に絞ってあります。</b>", S_BODY))
    story.append(Paragraph(
        "エラー種別についてお伺いする予定だった件は、"
        "<b>当方で確認できるようになりましたので取り下げました</b>（1-5）。",
        S_BODY))

    # ---------------------------------------------------------- 1. 共有
    story.append(CondPageBreak(60 * mm))
    story.append(Paragraph("1. 解決した件のご共有（回答不要です）", S_H2))

    story.append(Paragraph("1-1. 参照用アカウントを受領しました", S_H3))
    story.append(Paragraph(
        "GRANT SELECT ON clean_watch.* にて発行いただきました。"
        "<b>ありがとうございます。</b>"
        "<b>認証情報は当方の環境変数ファイルにのみ保管し、"
        "文書・リポジトリには残していません。</b>", S_BODY))
    story.append(Paragraph(
        "<b>あとは接続先さえ分かれば繋がる状態です</b>（第2部）。", S_BODY))

    story.append(Paragraph(
        "1-2. error_type の24値・error_level の4値をいただき、"
        "対応表を作成しました", S_H3))
    story.append(Paragraph(
        "Y-1 / Y-2 のご回答をありがとうございました。"
        "<b>24値がすべて EmergencyStop のような英字の識別子であること</b>が"
        "分かりましたので、当方でその一覧を整理した資料を作成し、"
        "<b>実装の突き合わせ先としています。</b>", S_BODY))
    story.append(Paragraph(
        "varchar(100) という長さも、この形であれば納得がいきました。", S_NOTE))

    story.append(Paragraph(
        "1-3. エラー種別の扱いを変更しました（貴社側の追加対応は不要です）", S_H3))
    story.append(Paragraph(
        "当方は当初、error_type を<b>7分類</b>"
        "（ハンドルアップ / LostLocation / オフライン / ブラシエラー / "
        "緊急停止 / 充電不足 / その他）に寄せて表示する設計でおりました。", S_BODY))
    story.append(Paragraph(
        "<b>いただいた24値と突き合わせたところ、"
        "7分類のうち3つに対応する値が見当たりませんでした。</b>"
        "ここで当方が推測で対応付けを行うと、"
        "<b>現場に誤った指示を出すおそれがあります</b>"
        "（例: モーター異常を「ブラシエラー」と表示してしまう）。", S_BODY))
    story.append(CondPageBreak(35 * mm))
    story.append(Paragraph("そこで方針を変更し、", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(callout(
        "DBに格納されている値を、変換せずそのまま画面に表示する方式にしました。",
        CW))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        "したがって<b>画面には EmergencyStop のような識別子がそのまま出ます。</b>"
        "<b>これは意図した挙動です。</b>"
        "<b>実データの分布を確認したうえで、必要になった時点で表示名を整えます。</b>",
        S_BODY))
    story.append(Paragraph(
        "<b>この変更により、貴社側で追加の対応が必要になることはありません。</b>"
        "むしろ、<b>貴社が後から値を追加された場合でも"
        "当方の画面が壊れなくなります。</b>", S_BODY))

    story.append(Paragraph(
        "1-4. Warning / Event の非格納も、そのまま前提にしました", S_H3))
    story.append(quote(
        "※Warning, Event はファイルに記録するけど, "
        "データベースに情報として格納しない予定.", CW))
    story.append(Spacer(1, 4))
    story.append(Paragraph(
        "とのご注記を承知しました。"
        "<b>当方は Fatal / Error の2つだけが読める前提で実装します。</b>"
        "<b>重要度の表示もこの2段階を前提に設計しています。</b>", S_BODY))
    story.append(Paragraph(
        "なお「内容をみて後から判断するかもしれない」とのことでしたので、"
        "<b>コード自体は4値すべてを受け取れる形にしてあります。</b>"
        "後から Warning が入るようになっても、当方の画面は壊れません。", S_BODY))

    story.append(CondPageBreak(50 * mm))
    story.append(Paragraph(
        "1-5. エラー種別についての照会は、当方での確認に切り替えました", S_H3))
    story.append(Paragraph(
        "第5回・第6回で、<b>ハンドルアップやブラシエラーが error_type の"
        "どの値に当たるか</b>をお伺いする予定にしておりました。", S_BODY))
    story.append(Paragraph(
        "しかし <b>1-3 のとおり、格納されている値をそのまま表示する方式に"
        "変更しました</b>ので、"
        "<b>分類の対応付けについてお伺いする必要がなくなりました。</b>", S_BODY))
    story.append(Paragraph(
        "あわせて、<b>参照用アカウントをいただきましたので、"
        "error_type に実際に入っている値は当方で確認できます。</b>", S_BODY))
    story.append(Paragraph(
        "したがって、<b>これらの照会は取り下げます。</b>", S_BODY))
    story.append(Paragraph(
        "<b>実データを確認して疑問が残った場合は、あらためてお伺いします。</b>"
        "その際は<b>具体的な件数や値を添えてお尋ねします</b>ので、"
        "ご判断いただきやすいかと存じます。", S_BODY))

    # ---------------------------------------------------------- 2. 接続先
    story.append(CondPageBreak(75 * mm))
    story.append(Paragraph(
        "2. 接続先（ホスト名・ポート）をお教えいただけますでしょうか — 最優先",
        S_H2))
    story.append(Paragraph("アカウントはいただきましたので、あとは", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(callout(
        "接続先のホスト名（または IP アドレス）と、ポート番号", CW))
    story.append(Spacer(1, 6))
    story.append(Paragraph("をお教えいただければ接続できます。", S_BODY))

    story.append(Paragraph("あわせて確認させてください", S_H3))
    story.append(Spacer(1, 3))
    story.append(table(CONN_ROWS, [10 * mm, 60 * mm, W - 70 * mm],
                       [S_CODE, S_CELL, S_CELL]))
    story.append(Spacer(1, 6))

    story.append(Paragraph("なぜ急いでお願いしているか", S_H3))
    story.extend(bull([
        "<b>9月中に PPIH 様向けにデモを実施する可能性があります。</b>",
        "<b>実データでお見せできるかどうかが、この接続情報で決まります。</b>",
        "サンプルデータでの動作確認は済んでおり、"
        "<b>画面自体は動いています</b>。"
        "実データに接続できれば、そのままデモに使えます。",
    ]))
    story.append(Paragraph(
        "<b>接続情報だけが、現時点で当方から埋められない部分です。</b>", S_BODY))

    story.append(CondPageBreak(70 * mm))
    story.append(Paragraph(
        "接続でき次第、当方で確認する内容を先に共有しておきます", S_H3))
    story.append(Paragraph(
        "<b>貴社への照会をこれ以上増やさないためです。</b>"
        "以下は実データを見れば当方で確認できますので、"
        "繋がった時点で照会が減ります。", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(table(AFTER_ROWS, [10 * mm, 104 * mm, W - 114 * mm],
                       [S_CODE, S_CELL, S_CELL]))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        "<b>これらは実データを見れば当方で確認できますので、"
        "貴社への照会を減らせます。</b>"
        "確認した結果は、こちらからご報告します。", S_BODY))

    # ---------------------------------------------------------- 3. DDL
    story.append(CondPageBreak(75 * mm))
    story.append(Paragraph(
        "3. 最新のDDLをいただけますでしょうか（第5回からの継続）", S_H2))
    story.append(Paragraph(
        "第4回で「<b>A-1 / A-3 / B-3b は対応済み</b>」との"
        "ご回答をいただきましたが、"
        "<b>受領済みの clean_watch_20260907.sql（2026-09-07 10:29 受領）には"
        "入っておりませんでした。</b>", S_BODY))
    story.append(Paragraph(
        "<b>3つの方法</b>（09-02版との全体差分 / "
        "各 CREATE TABLE ブロックからの項目抽出 / "
        "追加されるはずの制約名の全文検索）で照合し、いずれも同じ結果でした。"
        "<b>お願いしていた7項目のうち、反映されていたのは2項目です。</b>", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(table(DDL_ROWS, [14 * mm, 64 * mm, W - 78 * mm],
                       [S_CODE, S_CODE, S_CELL], head_color=NG))
    story.append(Spacer(1, 6))
    story.append(Paragraph("<b>反映いただいていた2件</b>は次の2つです。", S_LEAD))
    story.extend(bull([
        "customer_contacts.contact_type の型",
        "clean_task_reports の created_at / updated_at の DEFAULT",
    ]))
    story.append(Paragraph(
        "<b>当方のスキーマにも取り込み済みです。ありがとうございました。</b>",
        S_BODY))

    story.append(Paragraph(
        "参照用アカウントをいただいたので、当方でも確認できるようになりました",
        S_H3))
    story.append(Paragraph(
        "<b>prisma db pull でスキーマを取り込めば、"
        "DDL の再受領を待たずに現状を確認できます。</b>"
        "この点でも接続情報（第2部）が効いてきます。", S_BODY))
    story.append(Paragraph(
        "ただし、<b>取り込めるのは「今こうなっている」という状態だけで、"
        "それが意図された最終形かどうかは当方には判断できません。</b>"
        "そのため、まとまった段階で DDL をいただけると確実です。", S_BODY))
    story.append(Paragraph(
        "<b>受領しましたら、当方でその日のうちに照合してご報告します</b>"
        "（第5回と同じ運用です）。", S_BODY))
    story.append(Spacer(1, 2))
    story.append(quote(
        "<b>急かす意図はありません。</b>"
        "上記3件は<b>いずれも当方の開発を止めていません。</b>"
        "<b>状況が分かれば十分です。</b>", CW))

    # ---------------------------------------------------------- 4. 継続と保留
    story.append(CondPageBreak(60 * mm))
    story.append(Paragraph("4. 継続と保留", S_H2))

    story.append(Paragraph("継続: DB更新用アカウントの発行（第1回から）", S_H3))
    story.append(Paragraph(
        "robot_error_reports / control_error_histories の "
        "status / comment / responded_at / resolved_at "
        "<b>の4カラムのみ</b> UPDATE 可、という権限をお願いしております。", S_BODY))
    story.append(Paragraph(
        "「<b>後で必要なテーブルに更新権限を付与する</b>」との"
        "ご連絡をいただいており、承知しております。"
        "<b>恐れ入りますが、発行の見込みをお教えいただけますでしょうか。</b>",
        S_BODY))
    story.append(Paragraph(
        "<b>当方の作業は止まっておりません。</b>"
        "対応状況の更新機能は画面まで実装済みで、"
        "書き込み先を差し替えるだけの状態です。"
        "時期の目安さえ分かれば、そこに合わせて段取りします。", S_BODY))

    story.append(Paragraph("保留で了解しているもの: B-1 / B-2", S_H3))
    story.append(Paragraph(
        "外部キー3本とインデックスについて「後で登録する」との"
        "ご回答でしたので、<b>保留で了解しております。</b>"
        "<b>当方の開発は止まっていません。</b>"
        "登録された際に、DDL の再受領時にあわせて確認します。", S_BODY))

    story.append(Paragraph("データ保持ポリシーについて（催促ではありません）", S_H3))
    story.append(Paragraph(
        "過去データをどれだけ遡って保持するかについては、"
        "<b>当方の都合で後回しにしております。</b>"
        "画面の期間指定の上限に関わる話ですが、P1 の実装には影響しません。"
        "<b>貴社へのお願いではありませんので、ご対応は不要です。</b>", S_BODY))

    # ---------------------------------------------------------- 末尾
    story.append(CondPageBreak(60 * mm))
    story.append(Paragraph("おわりに", S_H2))
    story.append(Paragraph(
        "<b>今回は、お伺いすることを2件に絞りました。</b>"
        "優先度をつけるとすれば次の順です。", S_LEAD))
    story.append(Spacer(1, 3))
    story.append(table(PRIO_ROWS, [10 * mm, 48 * mm, W - 58 * mm],
                       [S_CODE, S_CELL, S_CELL]))
    story.append(Spacer(1, 6))
    story.append(Paragraph(
        "<b>1（接続先）が最優先です。"
        "これだけ先にいただければ、当方で確認できることが一気に増えます。</b>"
        "第2部の表に挙げた6件は、繋がった時点で当方で確認できますので、"
        "その分だけ貴社への照会が減ります。", S_BODY))
    story.append(Spacer(1, 8))
    story.append(Paragraph("引き続きよろしくお願いします。", S_NOTE))

    doc.build(story)
    print("生成:", OUT)


if __name__ == "__main__":
    build()
