aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/parse-sanpi-hyo.py
blob: 457cac0bd2c0523bf3b6a01b3a3490eccf04f0fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""賛否一覧PDF(新レイアウト)を読み、Markdown表を出力する。合計欄で検算する。

使い方: python .extract-final.py <pdf> "<定例会名>"
"""
import re
import sys

import pymupdf

sys.stdout.reconfigure(encoding="utf-8")

YES, NO, ABS = set("〇○◯"), set("×✕✖"), set("-—−-・")
VOTE = YES | NO | ABS
DIGITS = set("01234567890123456789")


def col_of(x, bounds):
    for i in range(len(bounds) - 1):
        if bounds[i] <= x < bounds[i + 1]:
            return i
    return None


def chars(pg):
    out = []
    for block in pg.get_text("rawdict")["blocks"]:
        for line in block.get("lines", []):
            for span in line["spans"]:
                for ch in span["chars"]:
                    if ch["c"].strip():
                        x0, y0, x1, y1 = ch["bbox"]
                        out.append({"c": ch["c"], "x": (x0 + x1) / 2, "y": (y0 + y1) / 2})
    return out


def main(path, label):
    doc = pymupdf.open(path)
    pg = doc[0]
    cs = chars(pg)

    # 会派(上部の2行、y 45〜70。文字は y,x の順に並べる)
    topcs = sorted([c for c in cs if 45 <= c["y"] <= 70], key=lambda c: (round(c["y"] / 6), c["x"]))
    top = "".join(c["c"] for c in topcs)
    parties = [(s, f.strip(), int(n)) for s, f, n in
               re.findall(r"([ぁ-んァ-ヶ一-龥A-Za-z]{2})[::]([^((]+?)[((](\d+)", top)]
    total = sum(n for _, _, n in parties)

    # 縦罫線グリッド
    xs = {}
    for d in pg.get_drawings():
        for it in d["items"]:
            if it[0] == "l":
                a, b = it[1], it[2]
                if abs(a.x - b.x) < 0.8:
                    k = round(a.x, 1)
                    info = xs.setdefault(k, {"maxy": 0})
                    info["maxy"] = max(info["maxy"], a.y, b.y)
    grid = sorted(x for x, i in xs.items() if i["maxy"] > 200 and 250 < x < 770)

    # 議員名の帯(y 88〜140、左の区分・番号・件名・議決結果を除く)
    namechars = [c for c in cs if 88 <= c["y"] <= 140 and c["x"] > 308]
    nx0, nx1 = min(c["x"] for c in namechars), max(c["x"] for c in namechars)
    left = [x for x in grid if x <= nx0]
    bounds = ([left[-1]] if left else [nx0 - 8]) + [x for x in grid if nx0 < x <= nx1 + 15]

    cols = {}
    for c in namechars:
        i = col_of(c["x"], bounds)
        if i is not None:
            cols.setdefault(i, []).append(c)
    members = [(i, "".join(x["c"] for x in sorted(cols[i], key=lambda c: c["y"]))) for i in sorted(cols)]

    # 会派ごとに区切る
    groups, k = [], 0
    for s, f, n in parties:
        groups.append((s, members[k:k + n]))
        k += n
    ngroups = len(members) - sum(n for _, _, n in parties)   # 末尾の合計欄(賛成/反対)
    member_only = members[:sum(n for _, _, n in parties)]

    # 賛否
    marks = [c for c in cs if c["c"] in VOTE and c["y"] > 145]
    rows = {}
    for c in marks:
        rows.setdefault(round(c["y"] / 10) * 10, []).append(c)

    # 合計欄(末尾の列)の数字
    nums = {}
    for c in cs:
        if c["c"] in DIGITS:
            i = col_of(c["x"], bounds)
            if i is not None and i >= len(member_only):
                nums.setdefault(round(c["y"] / 10) * 10, []).append((c["y"], c["x"], c["c"]))

    # 議案名(左側)
    left_txt = {}
    for c in cs:
        if c["x"] < 300 and c["y"] > 120:
            left_txt.setdefault(round(c["y"] / 10) * 10, []).append((c["x"], c["c"]))

    print(f"### {label}")
    print(f"会派: " + " / ".join(f"{s}({n})" for s, _, n in parties) + f" = {total}人")
    print(f"議員抽出数: {len(member_only)}(一致: {len(member_only) == total})")
    for s, ms in groups:
        print(f"  {s}: {', '.join(m[1] for m in ms)}")
    print("\n議案行の検算:")
    ok = True
    for y in sorted(rows):
        cells = {}
        for c in rows[y]:
            i = col_of(c["x"], bounds)
            if i is not None:
                cells.setdefault(i, []).append(c["c"])
        yes = sum(1 for i, _ in member_only if any(v in YES for v in cells.get(i, [])))
        no = sum(1 for i, _ in member_only if any(v in NO for v in cells.get(i, [])))
        if yes + no == 0:
            continue          # 賛否の記号がない行(全会一致など)
        pdfs = "".join(c for _, _, c in sorted(nums.get(y, [])))
        norm = pdfs.translate(str.maketrans("0123456789", "0123456789"))
        match = (str(yes) in norm and str(no) in norm)
        ok = ok and match
        print(f"  y={y}: 賛成{yes} 反対{no} / PDF={pdfs}  {'✓' if match else '✗ 要確認'}")
    print("  ⇒ 検算:", "全行一致" if ok else "不一致あり")
    doc.close()


main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "")