0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

台帳 GIS・20 万レコードの「検収」自動化をめざす — 分析で見つけた不整合を次の検査項目に昇華する品質検査ツール(Python/geopandas)

0
Posted at

はじめに

上下水道業界で管路台帳システム(GIS)の運用に携わっている実務者です。

前回、管種と施工年度を突き合わせて「施工年度 1955 年の塩ビ管」を見つけたとき、記事の終盤でこう書きました — これを台帳品質検査に足す価値がありそうです、と。

今回はその宿題を回収します。台帳 8 レイヤ・204,085 レコードを一括検査して、Excel の検収(納品物の受け入れ検査)レポートを出すスクリプトに、連載で見つかった不整合を検査項目として組み込みました。半田市のオープンデータに掛けた結果は NG 24 項目・要確認 17 項目。そして書きながら確信したのは、検査項目には由来があるということです。良い検査項目は仕様書からだけでは出てきません。データを実際に使って痛い目を見た経験が、次の検査項目になります。

お断り: 本記事の NG・要確認は「入力に疑義がある値の検出」であり、半田市の台帳品質の評価ではありません。長年運用された台帳はどこでもこの種の値を抱えますし、こうした検証ができるのは、管路データをダウンロード可能な形で公開している数少ない自治体だからです。半田市のデータは誰かの納品物でもありません(公開データを検査のデモ対象として使わせていただいています)。また、本記事の検査基準(閾値・判定)は筆者が定めたものであり、公的な検収基準ではありません。

「検収」はデータ品質の最前線

台帳データは、多くの場面で「受け取るもの」です。図面更新の委託成果、調査結果の取り込み、システム更新に伴うデータ移行 — いずれも外部から納品されたデータを台帳に迎え入れます。このとき納品物が仕様どおりかを受け取り側が確かめる工程が検収です。

問題は物量です。今回の 8 レイヤで 20 万レコード。目視で全件確認できる量ではないので、現実には「サンプルを開いて眺める」「件数だけ合っていることを確かめる」に落ちがちです。そして検収をすり抜けた誤りは、次の委託・次の移行まで台帳の中で生き残ります。

この工程の重要性は、これから増します。令和 9 年度以降、改築で交付金の対象となる管路施設は、施設情報や維持管理情報を「地理情報システムを基盤としたデータベースシステム」で管理していることが要件化される(国交省「下水道事業における事業マネジメント実施に関するガイドライン -2024 年版-」p.14 表 2-4 脚注 2)ためです。台帳の電子化・GIS 化が進むほど、データの受け渡しは増え、受け渡しのたびに検収が発生します。

作ったもの

ledger_qa.py — 8 レイヤを一括検査し、Excel レポート(サマリ/全検査結果/NG 一覧の 3 シート)を出力するスクリプトです。もともと単項目の検査(欠損・範囲・重複)を実装していたもので、今回、連載で見つかった不整合 2 つをクロス検査として足しました。

先に範囲をはっきりさせておくと、このツールが自動化するのは検収のうち机上で機械判定できる論理検査だけです。位置の精度、図面との照合、「もっともらしいが実は違う値」の確認は、引き続き人の仕事として残ります。

検査項目は 18 種類。大きく 3 層に分かれます。

層 検査項目 何を見ているか
ファイル 座標系定義(.prj)、属性定義 XML データを開く前に躓く欠落
ジオメトリ・ID 空ジオメトリ、延長ほぼゼロの線、SAUID 重複、表示様式の混在 図形と識別子の健全性
属性 年度・口径・土被りの欠損/範囲、管種×年代の整合、業務キーの一意性 値の中身

属性名が SAFIELD000 のような連番でも検査対象を指定できるのは、第 3 回で属性辞書を XML から復元してあるからです(下水_管渠なら SAFIELD000 = 施工年度、SAFIELD002 = 管径)。表中の用語も少しだけ: SAUID はエクスポートの各レコードに振られている一意 ID、SASTYLEID は施設をどの様式(地図記号)で描くかを表すコード、土被り(どかぶり)は管の上端から地表までの深さです。「表示様式の混在」は、1 つのレイヤに複数の様式コードが存在することを指します(それ自体は誤りではなく、「分析の前に分類が要る」という要確認シグナルです)。

設計上こだわったのは、検査対象と閾値をレイヤごとに宣言し、閾値には根拠を書くことです。検収は受注者・発注者の協議の場で使われるので、「なぜその閾値か」を必ず聞かれます。根拠を言えない検査項目は、その場で崩れます。

# --- inspection thresholds (with rationale) ---
# 0.01m: below survey precision for pipe segments -> digitizing artifact
MIN_LINE_LENGTH = 0.01
# 5%: missing rate above which attribute completeness needs investigation
MISSING_RATE_WARN = 0.05
# 1900: modern water/sewer works in Japan start late Meiji; earlier = suspect
YEAR_MIN = 1900
# 0-15m: cover depth beyond deep-shield sewers is implausible; negative invalid
COVER_RANGE_M = (0.0, 15.0)

@dataclass
class LayerSpec:
    rel: str
    label: str
    year_col: str | None = None
    diameter_col: str | None = None
    cover_cols: list[str] = field(default_factory=list)
    diameter_range: tuple[int, int] = (10, 3500)
    material_col: str | None = None      # enables material-vs-year cross check
    key_col: str | None = None           # business key (uniqueness check)
    key_label: str = "業務キー"
    key_scope: str | None = None         # column whose value scopes the key

判定は NG / 要確認 / OK の 3 値にしました。機械で白黒つけられないものを無理に NG にしない、というのがもう 1 つのこだわりです。NG は「このままでは成立しない、と機械が言い切れるもの」 — ファイルの欠落、負の土被りのような物理的にあり得ない値、施設を特定するキーの空欄など。要確認は「数字は出せるが、白黒には人の判断が要るもの」です。

たとえば上水_弁栓(バルブ類のレイヤ)は年度の欠損率が 86.8%(8,865 件)ですが、これは NG ではなく要確認です。古い施設の設置年度が台帳整備時点で既に不明、というのは普通にあることだからです。同じ欠損でも人孔キー(マンホールの施設番号)の空欄を NG にしているのは、施設を特定するキーが無いと照合も引き継ぎも成立せず、「不明のまま運用する」という選択肢が無いからです。そして NG・要確認のどちらに振られたものも、「直ちに修正せよ」ではなく「説明がつくかを協議で確認する対象」という運用を想定しています。

結果 — NG 24 項目・要確認 17 項目

qa_matrix.png

検査結果は延べ 102 行(レコード数の参考行 8 を含む)で、判定は NG 24・要確認 17・OK 53 でした。この数字は検査項目の数で、1 つの項目には複数のレコードがぶら下がります(「人孔キーの欠損」1 項目に 1,726 レコード、のように)。以下、主な検出を挙げます。表の件数はレコード数です(マトリクス図では、土被りの上流・下流 2 項目を 1 セルに合算して表示しています)。

検出 件数 中身
座標系定義(.prj)の欠落 全 8 レイヤ 第 1 回で座標系を推定するハメになった、あの欠落
SAUID の重複 5 レイヤ・延べ 210 レコード 同一 ID のレコードが複数存在(給水管 30・取付管 96・桝 66 など)
年度の範囲外 2 件 中身は「0000」。未入力をゼロ埋めした値が範囲チェックに掛かった
口径の下限未満 14 件 下水_管渠 8(実値 50〜75mm)・取付管 3・桝 3。本管に給水管サイズの値が紛れ込んだ疑い。レイヤごとに宣言した下限との不一致なので NG に置きつつ、圧送式下水道(ポンプで押し流す方式で、小口径が成立します)など実在の可能性も残るため、検査項目名は「口径の下限未満(入力疑義)」— 説明がつけば協議で OK に倒す想定
土被りの異常値 330 件 負値が 217 件、15m 超が 113 件(最大は 60,557 — m 単位の列としては桁か単位の取り違えを疑う水準)
桝の口径 0 2,252 件 桝(ます: 各戸の排水を下水道に取り込む小さな施設)は空欄(欠損)が 1 件だけなのに 0 が 2,252 件 — 「空欄にせず 0 を入れる」運用の痕跡

.prj の欠落は連載読者にはお馴染みですが、既知の欠落も検査項目として明文化することに意味があると考えています。担当者の頭の中にある「このデータは座標系が入っていないから注意」は、異動したら消えます。検査項目に書いてあれば、次の納品でも、次の担当者でも、機械が指摘してくれます。

「0000」も検収らしい検出です。欠損チェック(空欄を数える)には掛かりません。0 は値として入っているからです。範囲チェック(1900 年〜今年)まで掛けて、はじめてプレースホルダが浮かび上がります。桝の口径 0 が 2,252 件というのも同じ構図で、「欠損ゼロ」と「入力済み」は別物だと教えてくれます。

ここまでは単項目の検査です。ここからが今回の本題になります。

連載が育てた検査項目①: 管種×年代の整合

前回見つけた「施工年度 1955 年の硬質塩ビ管」を思い出してください。下水道用塩ビ管の規格 JSWAS K-1 の制定は 1974 年なので、1955 年は規格以前どころか、塩ビ管が工業用途で使われ始めた頃です。そしてこの 1 本は、年度の範囲チェック(1900 年〜今年)を普通に通過します。1955 年は単体では何もおかしくないからです。おかしいと分かるのは、管種と突き合わせたときだけです。

これをツールの検査項目にしました。管種の系統ごとに「規格の制定年」を辞書で持ち、それより前の施工年度を検出します。

# Material families whose Japanese sewer standard has a known first year.
# A construction year BEFORE the standard existed marks the row as suspect.
# 塩ビ系: JSWAS K-1 制定 1974-06-25
MATERIAL_MIN_YEAR = {
    "塩ビ": (1974, "JSWAS K-1 制定(1974)より前の施工年度"),
}

hit = mat.str.contains(family, na=False) & years.notna() & (years < min_year)

実行すると下水_管渠で 1 件 — 例の 1955 年の管が検出されます。当たり前に見えますが、この当たり前が大事です。前回は集計の途中で偶然見つけた 1 本が、今回からはボタン一発で見つかります。 辞書に他の管種の規格制定年を足していけば、検査はそのまま広がります。

前回、この 1 本は「要確認レコード」と呼びました。本ツールの 3 値では、判定を NG に置いています。1955 年という年度と硬質塩ビ管という管種は、ペアとしては両立しないと機械が言い切れるからです(少なくとも「このままの読みでは説明が必要」ということまでは確定します)。もっとも、このレコードをどう扱うか — どちらの項目が違うのか、それとも別の意味を持つ値なのか — の判断が台帳を知る側に残る点は、前回書いたとおりです。

連載が育てた検査項目②: 業務キーの一意性は「範囲」を持つ

もう 1 つは第 5 回の追記から来ています。マンホールの施設番号(人孔キー)について「欠損 1,726・重複 2,507」と書いたところ、実務者の方から「人孔キーの重複 2,507 は内訳を分解すべき(ダミー値の集中なら対処が全く変わる)」というご指摘をいただき、分解した結果は予想を裏切るものでした。重複キー 2,505 種は、すべて汚水系の人孔と雨水系の人孔にまたがっていました(うち 2,503 キーは 1 対 1 のペア、残る 2 キーは雨水側に 2 基ある 3 基組です)。半田市のデータでは、汚れた水を運ぶ汚水の管路網と雨水を運ぶ雨水の管路網が別の系統として記録されており、この区分を排水系統と呼びます。つまり人孔キーは排水系統の中でだけ一意になる採番で、系統内の真の重複は、いまの 3 基組の雨水側の 2 キーだけだったのです。

これは検査ツールの設計に直結する教訓でした。生の重複数だけを報告する検査は、「2,507 基の個別修正が必要」という誤った見積りを誘発します。実際に必要な対処は「採番規則の変換 1 件+個別対応 2 件+空欄 1,726 基の付番」で、規模がまったく違います。そこで検査を 3 段に分けました。

key = gdf[spec.key_col].astype("string")
n_blank = int(key.isna().sum())                  # (1) 欠損 -> NG
nn = key.dropna()
n_dup = int(nn.duplicated().sum())               # (2) 全体重複 -> 要確認
scope = gdf.loc[nn.index, "SASTYLEID"].astype(str).str[:3]
scoped = scope + "|" + nn                        # 系統コードをキーに前置
n_dup_scoped = int(scoped.duplicated().sum())    # (3) 系統内の真の重複 -> NG

系統の判定に SASTYLEID の先頭 3 桁を使っているのは、半田市のマンホールの表示様式が 240(汚水人孔の様式)と 242(雨水人孔の様式)の 2 系列に分かれているためで、第 5 回追記の分析もこの区分で系統を判定しています。

出力は「欠損 1,726(NG)/全体重複 2,507(要確認)/系統内の真の重複 2(NG)」となり、備考欄には「全体重複 2507 のうち系統内の真の重複は 2。残りは系統別採番(キーは系統内でのみ一意)によるもの」と書かれます。検査ツールの仕事は数えることではなく、対処の規模が正しく見える形で数えることです。

おまけに面白い符合もありました。真の重複 2 件の中身を開くと、1 組は同一 SAUID のレコードが二重登録されたもので、SAUID 重複検査(下水_マンホールで 2 レコード)が指しているのと同じレコードでした。もう 1 組は別の施設が同じキーを持つケースです。二重登録なら両方の検査に掛かるのは当然ですが、別々の検査が同じレコードを指してくれたおかげで、重複キーの片方の中身が二重登録らしいと開く前に見当がつきました。

qa_cycle.png

なぜ Excel で出すのか

出力を GeoPackage でも HTML でもなく Excel にしているのは、検収の現場が受発注の協議の場だからです。発注者と受注者が同じ表を見て、1 件ずつ「これは仕様どおり」「これは修正」「これは次回対応」と潰していく。その場の共通言語は、良くも悪くも Excel です。

シートは 3 枚に分けました。サマリ(件数のみ。報告書の頭に貼る用)、全検査結果(102 行。OK も含めて「何を検査したか」の証跡)、NG 一覧(24 行。協議はこれだけ持っていけば始められます)。OK の行を残すのは検収では大事で、「検査して問題なかった」と「検査していない」を区別できるのは全検査結果シートだけです。

実務でどう使えるか

  • 委託成果の検収(発注側): 納品データを受け取ったその日に NG 一覧が出ます。機械で判定できる項目に限れば、目視サンプリングでは物量的に不可能だった全件検査ができます(本ツールは半田市のエクスポート形式前提なので、他のデータに使うには LayerSpec の書き換えが必要です)。
  • 納品前の自主検査(受注側): 同じ検査を納品前に掛けて検出分を潰しておけば、少なくともこの検査項目について、検収の場で初めて指摘を知るという事態は無くなります。理想は検査基準を仕様書に添付して事前合意しておくことです。
  • システム移行の前後比較: 移行前と移行後のデータに同じ検査を掛ければ、移行処理が壊した項目が差分として見えます。
  • 検査項目を育てる運用: 分析や日常運用で見つけた不整合を、見つけたつど検査項目に取り込みます。管種×年代の辞書はその受け皿の一例で、規格制定年を足すだけで検査が広がります。

まとめ

  • 台帳 8 レイヤ・204,085 レコードを一括検査する検収ツールを作り、半田市オープンデータで NG 24 項目・要確認 17 項目を検出しました。コードはリポジトリにあります。
  • 単項目の検査(欠損・範囲・重複)だけでは捕まらない不整合があります。管種×年度の整合は単項目チェックを全部通過する 1955 年の塩ビ管を捕まえ、スコープ付きの一意性検査は「重複 2,507」を「対処 3 件+付番 1,726 基」という正しい規模に分解しました。
  • 検査項目には由来があります。第 5 回の読者指摘も、前回の偶然の発見も、いまは検査項目としてツールの中にいます。記事を書くたびに検収が強くなる — この循環が、今回いちばん伝えたかったことです。

関連記事: 上下水道×データの記事は一覧からどうぞ。コードは GitHub で公開しています。

免責・出典

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?