[{"data":1,"prerenderedAt":647},["ShallowReactive",2],{"content-/cf-standards-viewer-enhancement":3,"all-pages-for-dir":645,"og-image-/cf-standards-viewer-enhancement":646},{"id":4,"title":5,"body":6,"category":624,"description":625,"extension":626,"meta":627,"navigation":628,"path":629,"project_name":630,"published":631,"publishedAt":632,"seo":633,"stem":634,"tags":635,"todo":642,"unpublished":631,"updatedAt":643,"__hash__":644},"pages/2026-04/2026-04-20/cf-standards-viewer-enhancement.md","CF基準条文ビューアの機能拡張 -- 解決率100%達成と2カラムHTML分割",{"type":7,"value":8,"toc":599},"minimark",[9,13,17,20,24,28,31,35,43,110,123,125,129,133,136,140,151,153,157,161,164,168,171,178,180,183,187,190,312,316,323,458,467,469,472,475,486,533,537,540,543,556,558,561,568,571,574,576,579,595],[10,11,12],"h2",{"id":12},"この日やったこと",[14,15,16],"p",{},"前日までに90%で止まっていた解決率のメーターを100%に振り切った。残り26件は全て「作成基準」への条文番号なし参照で、general provisionを追加して一気に解消した。そこから条文ビューアの機能拡張に入り、全条文を取得して2カラムHTMLに変換し、基準ごとに34ページに分割し、引用コンテキストとフィルター機能を載せて閉じた。",[18,19],"hr",{},[10,21,23],{"id":22},"phase-f-解決率-90-100","Phase F 解決率 90% → 100%",[25,26,27],"h3",{"id":27},"残り26件の正体",[14,29,30],{},"未解決26件をリストアップしたところ、全てが同じパターンだった。「連結キャッシュ・フロー計算書作成基準では...」のように基準名を挙げるだけで、条文番号を指定していない。実務書の文中で「この基準の趣旨として」と言及しているような箇所だ。",[25,32,34],{"id":33},"general-provision-で一括解決","general provision で一括解決",[14,36,37,38,42],{},"条文番号がない以上、特定の条項にマッピングしようがない。そこで各基準に",[39,40,41],"code",{},"general","エントリを追加する方針にした。provisionsファイルに以下のような構造を追加:",[44,45,50],"pre",{"className":46,"code":47,"language":48,"meta":49,"style":49},"language-python shiki shiki-themes vitesse-light vitesse-light","# 条文番号なし参照 → general provisionとして吸収\n{\"provision_number\": \"general\", \"text\": \"（基準全体への一般参照）\"}\n","python","",[39,51,52,61],{"__ignoreMap":49},[53,54,57],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"sxvE3","# 条文番号なし参照 → general provisionとして吸収\n",[53,62,64,68,72,76,78,81,84,86,88,91,93,96,98,100,102,105,107],{"class":55,"line":63},2,[53,65,67],{"class":66},"shFtX","{",[53,69,71],{"class":70},"sMJiu","\"",[53,73,75],{"class":74},"sdGka","provision_number",[53,77,71],{"class":70},[53,79,80],{"class":66},":",[53,82,83],{"class":70}," \"",[53,85,41],{"class":74},[53,87,71],{"class":70},[53,89,90],{"class":66},",",[53,92,83],{"class":70},[53,94,95],{"class":74},"text",[53,97,71],{"class":70},[53,99,80],{"class":66},[53,101,83],{"class":70},[53,103,104],{"class":74},"（基準全体への一般参照）",[53,106,71],{"class":70},[53,108,109],{"class":66},"}\n",[14,111,112,114,115,118,119,122],{},[39,113,75],{},"が",[39,116,117],{},"None","のケースを検出してgeneralにフォールバックさせるロジックを",[39,120,121],{},"resolver.py","に追加。ビルドし直すと26件全てがresolvedに変わり、261/261で100%に到達した。",[18,124],{},[10,126,128],{"id":127},"全条文の2カラムhtml化","全条文の2カラムHTML化",[25,130,132],{"id":131},"左条文テキスト右書籍での引用コンテキスト","左=条文テキスト、右=書籍での引用コンテキスト",[14,134,135],{},"条文ビューアのUIを2カラム構成にした。左カラムに条文の全文、右カラムにCF計算書の実務書での引用箇所（前後の文脈を含む）を表示する。条文だけ読んでも抽象的で理解しにくいが、実務書がどの文脈でその条文を引いているかが横に並ぶと、条文の「使われ方」が見える。",[25,137,139],{"id":138},"citationsjson-に引用コンテキストを追加","citations.json に引用コンテキストを追加",[14,141,142,143,146,147,150],{},"元々",[39,144,145],{},"citations.json","には引用箇所のページ番号と条文番号しか入っていなかった。引用の前後50文字を",[39,148,149],{},"context","フィールドとして追加するスクリプトを書いた。実務書のテキストから該当箇所を検索し、前後の文脈を切り出す処理だ。",[18,152],{},[10,154,156],{"id":155},"基準ごとに個別htmlファイルに分割","基準ごとに個別HTMLファイルに分割",[25,158,160],{"id":159},"_1ファイルに全基準を詰めたら重すぎた","1ファイルに全基準を詰めたら重すぎた",[14,162,163],{},"最初は全基準・全条文を1つのHTMLファイルに出力していた。ブラウザで開くとDOMノード数が膨大になり、スクロールがカクつく。Chrome DevToolsのPerformanceタブで計測したら、初回レンダリングに4秒以上かかっていた。",[25,165,167],{"id":166},"_34ページ分割で初回描画が05秒に","34ページ分割で初回描画が0.5秒に",[14,169,170],{},"基準ごとに個別のHTMLファイルを生成する方針に変更。サイドバーに基準一覧を置き、クリックでページ遷移する構成にした。1ページあたりのDOM量が激減し、初回レンダリングは0.5秒を切った。",[14,172,173,174,177],{},"生成スクリプトは",[39,175,176],{},"standards.json","のエントリ数でループし、基準ごとにHTML一式を吐く。テンプレートはJinja2で共通化した。",[18,179],{},[10,181,182],{"id":182},"フィルター機能とサイドバーハイライト",[25,184,186],{"id":185},"引用済みのみ表示フィルター","「引用済みのみ表示」フィルター",[14,188,189],{},"条文一覧には実務書で一度も引用されていない条項も含まれる。全部表示すると条文数が多すぎて目的の条項にたどり着けない。チェックボックスひとつで「引用実績のある条文のみ」にフィルターできるようにした。",[44,191,195],{"className":192,"code":193,"language":194,"meta":49,"style":49},"language-javascript shiki shiki-themes vitesse-light vitesse-light","// フィルターのトグル\nconst showOnlyCited = ref(false)\nconst visibleProvisions = computed(() =>\n  showOnlyCited.value\n    ? provisions.filter(p => p.citationCount > 0)\n    : provisions\n)\n","javascript",[39,196,197,202,229,248,260,298,307],{"__ignoreMap":49},[53,198,199],{"class":55,"line":56},[53,200,201],{"class":59},"// フィルターのトグル\n",[53,203,204,208,212,215,219,222,226],{"class":55,"line":63},[53,205,207],{"class":206},"stQ0i","const",[53,209,211],{"class":210},"s4oTP"," showOnlyCited",[53,213,214],{"class":66}," =",[53,216,218],{"class":217},"senZ8"," ref",[53,220,221],{"class":66},"(",[53,223,225],{"class":224},"sHkkW","false",[53,227,228],{"class":66},")\n",[53,230,232,234,237,239,242,245],{"class":55,"line":231},3,[53,233,207],{"class":206},[53,235,236],{"class":210}," visibleProvisions",[53,238,214],{"class":66},[53,240,241],{"class":217}," computed",[53,243,244],{"class":66},"(()",[53,246,247],{"class":66}," =>\n",[53,249,251,254,257],{"class":55,"line":250},4,[53,252,253],{"class":210},"  showOnlyCited",[53,255,256],{"class":66},".",[53,258,259],{"class":210},"value\n",[53,261,263,266,269,271,274,276,278,281,284,286,289,292,296],{"class":55,"line":262},5,[53,264,265],{"class":206},"    ?",[53,267,268],{"class":210}," provisions",[53,270,256],{"class":66},[53,272,273],{"class":217},"filter",[53,275,221],{"class":66},[53,277,14],{"class":210},[53,279,280],{"class":66}," =>",[53,282,283],{"class":210}," p",[53,285,256],{"class":66},[53,287,288],{"class":210},"citationCount",[53,290,291],{"class":66}," >",[53,293,295],{"class":294},"sM54T"," 0",[53,297,228],{"class":66},[53,299,301,304],{"class":55,"line":300},6,[53,302,303],{"class":206},"    :",[53,305,306],{"class":210}," provisions\n",[53,308,310],{"class":55,"line":309},7,[53,311,228],{"class":66},[25,313,315],{"id":314},"intersection-observer-でサイドバーハイライト","Intersection Observer でサイドバーハイライト",[14,317,318,319,322],{},"条文をスクロールしていくと、現在表示中の条文がサイドバーで自動ハイライトされる。Intersection Observerで各条文の",[39,320,321],{},"\u003Csection>","を監視し、viewportに入った条文のIDをサイドバーに伝える。",[44,324,326],{"className":192,"code":325,"language":194,"meta":49,"style":49},"const observer = new IntersectionObserver(entries => {\n  entries.forEach(entry => {\n    if (entry.isIntersecting) {\n      activeProvision.value = entry.target.dataset.provisionId\n    }\n  })\n}, { rootMargin: '-20% 0px -70% 0px' })\n",[39,327,328,353,372,392,422,427,432],{"__ignoreMap":49},[53,329,330,332,335,337,340,343,345,348,350],{"class":55,"line":56},[53,331,207],{"class":206},[53,333,334],{"class":210}," observer",[53,336,214],{"class":66},[53,338,339],{"class":206}," new",[53,341,342],{"class":217}," IntersectionObserver",[53,344,221],{"class":66},[53,346,347],{"class":210},"entries",[53,349,280],{"class":66},[53,351,352],{"class":66}," {\n",[53,354,355,358,360,363,365,368,370],{"class":55,"line":63},[53,356,357],{"class":210},"  entries",[53,359,256],{"class":66},[53,361,362],{"class":217},"forEach",[53,364,221],{"class":66},[53,366,367],{"class":210},"entry",[53,369,280],{"class":66},[53,371,352],{"class":66},[53,373,374,377,380,382,384,387,390],{"class":55,"line":231},[53,375,376],{"class":224},"    if",[53,378,379],{"class":66}," (",[53,381,367],{"class":210},[53,383,256],{"class":66},[53,385,386],{"class":210},"isIntersecting",[53,388,389],{"class":66},")",[53,391,352],{"class":66},[53,393,394,397,399,402,404,407,409,412,414,417,419],{"class":55,"line":250},[53,395,396],{"class":210},"      activeProvision",[53,398,256],{"class":66},[53,400,401],{"class":210},"value",[53,403,214],{"class":66},[53,405,406],{"class":210}," entry",[53,408,256],{"class":66},[53,410,411],{"class":210},"target",[53,413,256],{"class":66},[53,415,416],{"class":210},"dataset",[53,418,256],{"class":66},[53,420,421],{"class":210},"provisionId\n",[53,423,424],{"class":55,"line":262},[53,425,426],{"class":66},"    }\n",[53,428,429],{"class":55,"line":300},[53,430,431],{"class":66},"  })\n",[53,433,434,437,440,444,446,449,452,455],{"class":55,"line":309},[53,435,436],{"class":66},"},",[53,438,439],{"class":66}," {",[53,441,443],{"class":442},"sz8Xr"," rootMargin",[53,445,80],{"class":66},[53,447,448],{"class":70}," '",[53,450,451],{"class":74},"-20% 0px -70% 0px",[53,453,454],{"class":70},"'",[53,456,457],{"class":66}," })\n",[14,459,460,463,464,466],{},[39,461,462],{},"rootMargin","を",[39,465,451],{},"に設定し、画面上部20%〜30%の帯にセクションが入ったタイミングでアクティブ切り替え。スクロール方向に関わらず、「今読んでいる条文」が正しくハイライトされる。",[18,468],{},[10,470,471],{"id":471},"試行錯誤メモ",[25,473,474],{"id":474},"エンコーディング問題",[14,476,477,478,481,482,485],{},"e-Govから取得した条文XMLの一部がShift_JISで返ってきた。UTF-8前提でデコードしたため文字化けが発生し、「規」「則」が",[39,479,480],{},"U+FFFD","に化けた。前日Phase Fでも同じ問題に遭遇していたが、今回は取得スクリプト側で",[39,483,484],{},"response.encoding","を確認してからデコードする処理を入れた。",[44,487,489],{"className":46,"code":488,"language":48,"meta":49,"style":49},"# エンコーディング判定を先にやる\nif response.apparent_encoding:\n    response.encoding = response.apparent_encoding\n",[39,490,491,496,513],{"__ignoreMap":49},[53,492,493],{"class":55,"line":56},[53,494,495],{"class":59},"# エンコーディング判定を先にやる\n",[53,497,498,501,505,507,510],{"class":55,"line":63},[53,499,500],{"class":224},"if",[53,502,504],{"class":503},"sG7-3"," response",[53,506,256],{"class":66},[53,508,509],{"class":503},"apparent_encoding",[53,511,512],{"class":66},":\n",[53,514,515,518,520,523,526,528,530],{"class":55,"line":231},[53,516,517],{"class":503},"    response",[53,519,256],{"class":66},[53,521,522],{"class":503},"encoding ",[53,524,525],{"class":66},"=",[53,527,504],{"class":503},[53,529,256],{"class":66},[53,531,532],{"class":503},"apparent_encoding\n",[25,534,536],{"id":535},"provision_number-が-none-のケースへの対応方針","provision_number が None のケースへの対応方針",[14,538,539],{},"当初は「条文番号なしの引用はunresolvedのまま残す」方針だった。しかし90%→100%の間の26件を眺めると、全てが同じパターン（基準名だけの言及）であり、条文番号の特定が原理的に不可能なケースだった。",[14,541,542],{},"方針を転換して「general provisionとして吸収する」と決めた判断基準:",[544,545,546,550,553],"ol",{},[547,548,549],"li",{},"条文番号の特定が原理的に不可能（著者が特定条文を意図していない）",[547,551,552],{},"基準全体の趣旨を示す文脈で使われている",[547,554,555],{},"26件全てが同一パターンに該当する",[18,557],{},[10,559,560],{"id":560},"振り返り",[14,562,563,564,567],{},"100%に到達した瞬間、ターミナルに",[39,565,566],{},"261/261 resolved","と表示されて手が止まった。4月15日から始めたこの作業が、数字の上で一区切りついた。",[14,569,570],{},"ただし「general provisionとして吸収」は技術的な解決であって、意味的には「この参照は特定条文にマッピングしない」と認めたに過ぎない。実運用でgeneral参照をどうビューアに表示するかは、次のイテレーションで詰める。",[14,572,573],{},"34ファイルに分割したことで、1つの基準を編集しても他の基準のHTMLに影響が出ない。ビルドも差分だけ再生成できる余地が生まれた。最初から分割すべきだったが、全部入り1ファイルで動作確認してからのほうが分割の切り口が見えやすかった。",[18,575],{},[10,577,578],{"id":578},"関連記事",[580,581,582,589],"ul",{},[547,583,584],{},[585,586,588],"a",{"href":587},"/cf-standards-phase-f-resolution","会計基準条文取得 Phase F -- 解決率90%達成までの試行錯誤",[547,590,591],{},[585,592,594],{"href":593},"/cfws-html-viewer","CF精算表HTMLビューア構築",[596,597,598],"style",{},"html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .s4oTP, html code.shiki .s4oTP{--shiki-default:#B07D48;--shiki-dark:#B07D48}html pre.shiki code .senZ8, html code.shiki .senZ8{--shiki-default:#59873A;--shiki-dark:#59873A}html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .sz8Xr, html code.shiki .sz8Xr{--shiki-default:#998418;--shiki-dark:#998418}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}",{"title":49,"searchDepth":63,"depth":63,"links":600},[601,602,606,610,614,618,622,623],{"id":12,"depth":63,"text":12},{"id":22,"depth":63,"text":23,"children":603},[604,605],{"id":27,"depth":231,"text":27},{"id":33,"depth":231,"text":34},{"id":127,"depth":63,"text":128,"children":607},[608,609],{"id":131,"depth":231,"text":132},{"id":138,"depth":231,"text":139},{"id":155,"depth":63,"text":156,"children":611},[612,613],{"id":159,"depth":231,"text":160},{"id":166,"depth":231,"text":167},{"id":182,"depth":63,"text":182,"children":615},[616,617],{"id":185,"depth":231,"text":186},{"id":314,"depth":231,"text":315},{"id":471,"depth":63,"text":471,"children":619},[620,621],{"id":474,"depth":231,"text":474},{"id":535,"depth":231,"text":536},{"id":560,"depth":63,"text":560},{"id":578,"depth":63,"text":578},"dev","Phase Fの未解決26件をgeneral provision追加で100%解決し、全条文の2カラムHTML化・基準ごとの個別ファイル分割・引用コンテキスト追加・フィルター機能を実装した記録","md",{},true,"/cf-standards-viewer-enhancement","eurekapu-nuxt4",false,"2026-04-20T00:00:00.000Z",{"title":5,"description":625},"2026-04/2026-04-20/cf-standards-viewer-enhancement",[636,637,638,639,640,641],"会計基準","条文ビューア","HTML","Python","Intersection Observer","パフォーマンス","memo",null,"6O75CV-MyIczePypDJn99g1S_XK6oMr1YWaMCRTqBl4",[],"https://log.eurekapu.com/og/blog/cf-standards-viewer-enhancement.png?v=2026-04-20T00%3A00%3A00.000Z&title=CF%E5%9F%BA%E6%BA%96%E6%9D%A1%E6%96%87%E3%83%93%E3%83%A5%E3%83%BC%E3%82%A2%E3%81%AE%E6%A9%9F%E8%83%BD%E6%8B%A1%E5%BC%B5%20--%20%E8%A7%A3%E6%B1%BA%E7%8E%87100%25%E9%81%94%E6%88%90%E3%81%A82%E3%82%AB%E3%83%A9%E3%83%A0HTML%E5%88%86%E5%89%B2&author=Kei%20Komatsu&sig=d413b0938a6ec230",1780786053384]