• #会計基準
  • #条文ビューア
  • #HTML
  • #Python
  • #Intersection Observer
  • #パフォーマンス
開発eurekapu-nuxt4メモ

この日やったこと

前日までに90%で止まっていた解決率のメーターを100%に振り切った。残り26件は全て「作成基準」への条文番号なし参照で、general provisionを追加して一気に解消した。そこから条文ビューアの機能拡張に入り、全条文を取得して2カラムHTMLに変換し、基準ごとに34ページに分割し、引用コンテキストとフィルター機能を載せて閉じた。


Phase F 解決率 90% → 100%

残り26件の正体

未解決26件をリストアップしたところ、全てが同じパターンだった。「連結キャッシュ・フロー計算書作成基準では...」のように基準名を挙げるだけで、条文番号を指定していない。実務書の文中で「この基準の趣旨として」と言及しているような箇所だ。

general provision で一括解決

条文番号がない以上、特定の条項にマッピングしようがない。そこで各基準にgeneralエントリを追加する方針にした。provisionsファイルに以下のような構造を追加:

# 条文番号なし参照 → general provisionとして吸収
{"provision_number": "general", "text": "(基準全体への一般参照)"}

provision_numberNoneのケースを検出してgeneralにフォールバックさせるロジックをresolver.pyに追加。ビルドし直すと26件全てがresolvedに変わり、261/261で100%に到達した。


全条文の2カラムHTML化

左=条文テキスト、右=書籍での引用コンテキスト

条文ビューアのUIを2カラム構成にした。左カラムに条文の全文、右カラムにCF計算書の実務書での引用箇所(前後の文脈を含む)を表示する。条文だけ読んでも抽象的で理解しにくいが、実務書がどの文脈でその条文を引いているかが横に並ぶと、条文の「使われ方」が見える。

citations.json に引用コンテキストを追加

元々citations.jsonには引用箇所のページ番号と条文番号しか入っていなかった。引用の前後50文字をcontextフィールドとして追加するスクリプトを書いた。実務書のテキストから該当箇所を検索し、前後の文脈を切り出す処理だ。


基準ごとに個別HTMLファイルに分割

1ファイルに全基準を詰めたら重すぎた

最初は全基準・全条文を1つのHTMLファイルに出力していた。ブラウザで開くとDOMノード数が膨大になり、スクロールがカクつく。Chrome DevToolsのPerformanceタブで計測したら、初回レンダリングに4秒以上かかっていた。

34ページ分割で初回描画が0.5秒に

基準ごとに個別のHTMLファイルを生成する方針に変更。サイドバーに基準一覧を置き、クリックでページ遷移する構成にした。1ページあたりのDOM量が激減し、初回レンダリングは0.5秒を切った。

生成スクリプトはstandards.jsonのエントリ数でループし、基準ごとにHTML一式を吐く。テンプレートはJinja2で共通化した。


フィルター機能とサイドバーハイライト

「引用済みのみ表示」フィルター

条文一覧には実務書で一度も引用されていない条項も含まれる。全部表示すると条文数が多すぎて目的の条項にたどり着けない。チェックボックスひとつで「引用実績のある条文のみ」にフィルターできるようにした。

// フィルターのトグル
const showOnlyCited = ref(false)
const visibleProvisions = computed(() =>
  showOnlyCited.value
    ? provisions.filter(p => p.citationCount > 0)
    : provisions
)

Intersection Observer でサイドバーハイライト

条文をスクロールしていくと、現在表示中の条文がサイドバーで自動ハイライトされる。Intersection Observerで各条文の<section>を監視し、viewportに入った条文のIDをサイドバーに伝える。

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      activeProvision.value = entry.target.dataset.provisionId
    }
  })
}, { rootMargin: '-20% 0px -70% 0px' })

rootMargin-20% 0px -70% 0pxに設定し、画面上部20%〜30%の帯にセクションが入ったタイミングでアクティブ切り替え。スクロール方向に関わらず、「今読んでいる条文」が正しくハイライトされる。


試行錯誤メモ

エンコーディング問題

e-Govから取得した条文XMLの一部がShift_JISで返ってきた。UTF-8前提でデコードしたため文字化けが発生し、「規」「則」がU+FFFDに化けた。前日Phase Fでも同じ問題に遭遇していたが、今回は取得スクリプト側でresponse.encodingを確認してからデコードする処理を入れた。

# エンコーディング判定を先にやる
if response.apparent_encoding:
    response.encoding = response.apparent_encoding

provision_number が None のケースへの対応方針

当初は「条文番号なしの引用はunresolvedのまま残す」方針だった。しかし90%→100%の間の26件を眺めると、全てが同じパターン(基準名だけの言及)であり、条文番号の特定が原理的に不可能なケースだった。

方針を転換して「general provisionとして吸収する」と決めた判断基準:

  1. 条文番号の特定が原理的に不可能(著者が特定条文を意図していない)
  2. 基準全体の趣旨を示す文脈で使われている
  3. 26件全てが同一パターンに該当する

振り返り

100%に到達した瞬間、ターミナルに261/261 resolvedと表示されて手が止まった。4月15日から始めたこの作業が、数字の上で一区切りついた。

ただし「general provisionとして吸収」は技術的な解決であって、意味的には「この参照は特定条文にマッピングしない」と認めたに過ぎない。実運用でgeneral参照をどうビューアに表示するかは、次のイテレーションで詰める。

34ファイルに分割したことで、1つの基準を編集しても他の基準のHTMLに影響が出ない。ビルドも差分だけ再生成できる余地が生まれた。最初から分割すべきだったが、全部入り1ファイルで動作確認してからのほうが分割の切り口が見えやすかった。


関連記事