diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-23 09:27:06 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-23 09:27:06 +0900 |
| commit | b5b6471b5a5420d1bd410907ac12570b8436702e (patch) | |
| tree | ec04b6e36653e3f7aa02090f3936e45c0abac384 /src/utils | |
| parent | 09ab25113c299b8f3208a717cee9e9b8c7b07fe9 (diff) | |
sanpi: 集計・全期間一覧・前後リンクを修正し、非公開に戻す
Diffstat (limited to 'src/utils')
| -rw-r--r-- | src/utils/sanpiStats.ts | 112 |
1 files changed, 107 insertions, 5 deletions
diff --git a/src/utils/sanpiStats.ts b/src/utils/sanpiStats.ts index b2bfed3..33d5231 100644 --- a/src/utils/sanpiStats.ts +++ b/src/utils/sanpiStats.ts @@ -24,12 +24,13 @@ export interface SanpiChartRow { /** グラフの説明文。 */ export const SANPI_CHART_CAPTION = - "市が提出した議案(請願・意見書・決議・条例制定・動議などの議員提出議案を除く)に対する、会派ごとの議員の賛成・反対の割合です。安竹洋平が議員になってからの累計で、反対の割合が高い順に並べています。会派が分かれた場合は表の各列の票を1件として数えています。会派に合流している期間(中江美和議員のフォーラム、石津はるか議員の政和会)は、会派の票を個人に充当しています。件数が少ない会派は参考程度にご覧ください。"; + "市が提出した議案(請願・意見書・決議(附帯決議を含む)・条例制定・動議などの議員提出議案を除く)に対する、会派ごとの議員の賛成・反対の割合です。安竹洋平が議員になってからの累計で、反対の割合が高い順に並べています。会派が分かれた場合は表の各列の票を1件として数えています。会派に合流している期間(中江美和議員のフォーラム、石津はるか議員の政和会)は、会派の票を個人に充当しています。件数が少ない会派は参考程度にご覧ください。"; // グラフの表示名(議員名を添える)。 const DISPLAY_NAME: Record<string, string> = { れ新: "れ新(中倉)", 市民: "市民(水口)", + 橋本久雄: "橋本久雄(元議員)", }; const ME_NAME = "安竹洋平"; @@ -54,6 +55,10 @@ function groupLabel(headerText: string): string { const label = m[2]; // 会派(N人)は会派そのもの。 if (/^\d+人$/.test(label)) return name; + // 佐藤徹議員(公明)は、体調不良等による欠席・退席で列が分かれているだけで、 + // 所属は公明党。公明の列が別にあるため、この個人列は集計しない + // (同じ議案を二重に数えない)。 + if (name === "佐藤徹" && label === "公明") return ""; // 個人の(政和)は政和会に合算する(外山議員は常に、石津議員も一人でないときは政和会)。 if (label === "政和") return "政和"; // (一人)などは議員ごとに分ける。 @@ -67,11 +72,19 @@ function voteChar(cell: string): "〇" | "×" | null { return null; } +export type BillType = "city" | "member" | "petition"; + +// 議案の種別。グラフの集計と全期間一覧の分類で共通に使う。 +export function billType(cell: string): BillType { + if (cell.includes("sanpi-shugan")) return "petition"; + const text = stripTags(cell); + if (/意見書|附帯決議|付帯決議|決議|動議|制定/.test(text)) return "member"; + return "city"; +} + // 市が提出した議案か(請願・意見書・決議・条例制定・動議などの議員提出議案を除く)。 function isCityBill(cell: string): boolean { - if (cell.includes("sanpi-shugan")) return false; - const text = stripTags(cell); - return !/意見書|決議|動議|制定/.test(text); + return billType(cell) === "city"; } function eraKey(text: string): number | null { @@ -141,7 +154,7 @@ export function computeSanpiStats(): SanpiChartRow[] { for (const rule of MEMBER_RULES) { const fromKey = eraKey(rule.from); const untilKey = rule.until ? eraKey(rule.until) : Infinity; - if (fromKey == null || sessionKey < fromKey || sessionKey > untilKey) continue; + if (fromKey == null || untilKey == null || sessionKey < fromKey || sessionKey > untilKey) continue; if (groups.includes(rule.name)) continue; const gi = groups.indexOf(rule.group); if (gi < 0) continue; @@ -174,3 +187,92 @@ export function computeSanpiStats(): SanpiChartRow[] { }; }); } + +export interface SanpiBillRow { + /** 年度(例: 令和8年度)。 */ + year: string; + /** 年度別ページへのリンク。 */ + yearHref: string; + /** 定例会(例: 令和8年9月定例会)。 */ + session: string; + /** 議案名(HTMLを除いたもの)。 */ + title: string; + /** 議決結果(例: 可決、否決、採択、不採択)。 */ + result: string; + /** 安竹洋平の賛否。—は表決に加わらない(議長・欠席・退場)。 */ + me: "〇" | "×" | "—"; + /** 市側の議案・議員提出の議案・請願の別。 */ + type: BillType; +} + +const YEAR_LABELS: Record<string, string> = { + r1d: "令和元年度", + r2d: "令和2年度", + r3d: "令和3年度", + r4d: "令和4年度", + r5d: "令和5年度", + r6d: "令和6年度", + r7d: "令和7年度", + r8d: "令和8年度", +}; + +/** + * 全期間の議案を、市側の議案・議員提出の議案・請願に分類して返す。 + * 年度別ページの表をそのまま読み、安竹洋平の列だけを取り出す。 + */ +export function computeSanpiBills(): SanpiBillRow[] { + const sources = import.meta.glob("/src/content/docs/sanpi/r*.mdx", { + query: "?raw", + import: "default", + eager: true, + }) as Record<string, string>; + + const rows: { key: string; row: SanpiBillRow }[] = []; + + for (const [path, source] of Object.entries(sources)) { + const key = path.match(/(r\d+d)\.mdx$/)?.[1] ?? ""; + const year = YEAR_LABELS[key] ?? ""; + const yearHref = key ? `/sanpi/${key}/` : ""; + let session = ""; + let header: string[] | null = null; + let meIndex = -1; + + for (const line of source.split(/\r?\n/)) { + if (line.startsWith("## ")) { + session = line.slice(3).trim(); + header = null; + meIndex = -1; + continue; + } + if (!line.startsWith("|")) { + header = null; + meIndex = -1; + continue; + } + const cells = splitRow(line); + if (isSeparatorRow(cells)) continue; + if (header === null) { + header = cells; + meIndex = header.findIndex((h) => groupLabel(h) === ME_NAME); + continue; + } + if (meIndex < 0) continue; + const me = voteChar(cells[meIndex] ?? "") ?? "—"; + rows.push({ + key, + row: { + year, + yearHref, + session, + title: stripTags(cells[0] ?? "").trim(), + result: stripTags(cells[1] ?? "").trim(), + me, + type: billType(cells[0] ?? ""), + }, + }); + } + } + + rows.sort((a, b) => a.key.localeCompare(b.key)); + return rows.map(({ row }) => row); +} |
