diff --git a/.env.local.example b/.env.local.example index 5c215b9..56612df 100644 --- a/.env.local.example +++ b/.env.local.example @@ -18,3 +18,9 @@ YF_OAUTH_CALLBACK_URL=http://localhost:5050/api/auth/callback # Local: http://localhost:3000/api/v1 # Production: https://yorkfactory.buildcanada.com/api/v1 YORK_FACTORY_API_URL=http://localhost:3000/api/v1 + +# Bill data sources. +CIVICS_PROJECT_API_KEY= +OPENAI_API_KEY= + +BILLS_SLACK_WEBHOOK_URL= \ No newline at end of file diff --git a/.gitignore b/.gitignore index edcb7a8..b8acd1e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ # testing /coverage +# bills eval response cache +/src/app/bills/evals/.cache/ + # next.js /.next/ /out/ diff --git a/package.json b/package.json index d3f2944..56d259a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start": "next start -p 5050", "lint": "eslint && npm run lint:tokens", "lint:tokens": "rg --glob '*.tsx' --glob '*.ts' --glob '!*.d.ts' -e '[a-z]+-\\[var\\(--' src/ && echo 'FAIL: inline CSS vars in Tailwind found' && exit 1 || echo 'OK: no inline CSS vars'", - "seed:bills-admin": "tsx scripts/seed-bills-admin.ts" + "seed:bills-admin": "tsx scripts/seed-bills-admin.ts", + "eval:bills": "tsx --env-file-if-exists=.env.local --env-file-if-exists=.env src/app/bills/evals/run.ts" }, "dependencies": { "@base-ui/react": "^1.3.0", diff --git a/src/app/bills/README.md b/src/app/bills/README.md new file mode 100644 index 0000000..a137603 --- /dev/null +++ b/src/app/bills/README.md @@ -0,0 +1,144 @@ +# Builder MP (`/bills`) + +Builder MP is a builder-first analysis layer over Canadian federal legislation. +It fetches real bills from the current Parliament, uses an LLM to summarize each +one and evaluate it against Build Canada's pro-growth tenets, and renders a +plain-language verdict — align (`yes`), conflict (`no`), or `abstain` — alongside +a per-tenet breakdown and Question-Period-style questions an MP might actually +ask. The goal (from the in-app FAQ): _"so that Canadians could easily understand +parliamentary bills and how they align with a pro-growth stance."_ + +Bill data comes from [The Civics Project](https://civicsproject.org), which +mirrors the Government of Canada's open parliamentary feeds. The app currently +tracks **Parliament 45**. + +## The analysis model + +Every analyzed bill produces a `BillAnalysis` (see `services/billApi.ts`) with: + +- **`summary`** — 3–5 sentence plain-language summary (markdown, bulleted). +- **`tenet_evaluations`** — exactly 8 entries, one per Build Canada tenet, each + marked `aligns | conflicts | neutral` with a short explanation. +- **`final_judgment`** — `yes | no | abstain`. +- **`question_period_questions`** — exactly 3 critical MP-style questions (no + "Mr./Madam Speaker" prefix). +- **`rationale`**, `short_title`, `needs_more_info`, `missing_details`, and + `steel_man` (an editorially-maintained field, not LLM-generated — see below). + +### Build Canada's tenets + +The judgment is grounded in 8 tenets defined in `prompt/summary-and-vote-prompt.ts`: + +1. Canada should aim to be the world's most prosperous country. +2. Promote economic freedom, ambition, and breaking from bureaucratic inertia (reduce red tape). +3. Drive national productivity and global competitiveness, including removing interprovincial trade barriers and improving labour mobility (one country, one market). +4. Grow exports of Canadian products and resources, and move up the value chain by processing resources domestically rather than exporting them raw. +5. Encourage investment, innovation, and resource development. +6. Deliver better public services at lower cost (government efficiency). +7. Reform taxes to incentivize work, risk-taking, and innovation. +8. Focus on large-scale prosperity, not incrementalism. + +The prompt also carries explicit **align/conflict signals** (e.g. removing trade +barriers → align; protectionism, new red tape, or large redistributive spending +→ conflict) so borderline judgments stay consistent. These tenets and signals +track Build Canada's documented positions at [buildcanada.com](https://www.buildcanada.com). + +### The two LLM touchpoints + +- **`summarizeBillText`** (`services/billApi.ts`) — the main analysis pass + (`gpt-5`, high reasoning effort). Produces the full `BillAnalysis` above. +- **`socialIssueGrader`** (`services/social-issue-grader.ts`) — a small binary + classifier: is the bill *primarily* a social/rights/identity/culture issue? + When yes, the app treats the bill as out of the economic-tenet scope and + the judgment is `abstain`. + +Both functions degrade gracefully to deterministic fallback output when +`OPENAI_API_KEY` is unset (they never throw). + +> **Note on `steel_man`:** it is a persisted, human-editable field (admin edit +> page), **not** produced by the analysis prompt. `summarizeBillText` correctly +> leaves it empty; editors fill it in. The eval suite deliberately does not +> assert it. + +## Data flow + +``` +Civics Project API ──► getBillFromCivicsProjectApi ──► fetchBillMarkdown (xml→md) + │ + summarizeBillText + socialIssueGrader (LLM) + │ + onBillNotInDatabase → persist to MongoDB + │ + page.tsx / [id]/page.tsx ◄── getUnifiedBillById ◄── Bill model +``` + +- **List page** (`page.tsx` → `BillExplorer.tsx`) reads analyzed bills from the + DB and renders filterable cards. +- **Detail page** (`[id]/page.tsx`) renders the summary, per-tenet breakdown + (`components/BillDetail/BillTenets.tsx`), judgment badge + (`components/Judgement/`), and QP questions. +- Analyzed results are cached in **MongoDB** (`models/Bill.ts`) so a bill is only + sent to the LLM once (re-run explicitly via the reprocess route). + +## Directory map + +| Path | Responsibility | +|---|---| +| `page.tsx`, `BillExplorer.tsx` | List page + client-side filtering | +| `[id]/page.tsx`, `components/BillDetail/*` | Bill detail view | +| `[id]/edit/page.tsx` | Admin edit form (gated) | +| `prompt/summary-and-vote-prompt.ts` | Tenets, social-issue rules, judgment signals, output schema | +| `services/billApi.ts` | Civics fetch, `summarizeBillText`, markdown conversion, DB persistence | +| `services/social-issue-grader.ts` | Binary social-issue classifier | +| `server/*` | DB + Civics read helpers (`getUnifiedBillById`, etc.) | +| `models/*` | Mongoose `Bill` and `User` schemas | +| `api/[id]/route.ts`, `api/[id]/reprocess/route.ts` | Update / re-analyze a bill (gated) | +| `utils/xml-to-md/` | Deterministic bill-XML → markdown conversion | +| `evals/` | Manual LLM eval suite — see `evals/README.md` | + +## Auth + +Read access to the public pages is open. **Admin actions (edit, reprocess) are +gated** by `requireAuthenticatedUser` (`lib/auth-guards.ts`): Google sign-in via +NextAuth, and the signed-in email must exist in the `User` allowlist in the DB. + +For local development, set `BILLS_DEV_OPEN_ACCESS=true` **and** a non-production +`NODE_ENV` to bypass the allowlist entirely (both conditions are required so it +can never activate by accident in production — see `env.ts`). + +## Environment + +Configured in `env.ts`. Put these in `.env.local` for local dev. + +| Var | Purpose | +|---|---| +| `OPENAI_API_KEY` | LLM analysis + social grader (fallback output if unset) | +| `CIVICS_PROJECT_API_KEY` | Fetch bills from the Civics Project API | +| `CIVICS_PROJECT_BASE_URL` | Defaults to `https://api.civicsproject.org` | +| `MONGO_URI` (or `MONGODB_URI`) | Analyzed-bill + user store | +| `NEXTAUTH_URL`, `NEXTAUTH_SECRET` (or `AUTH_SECRET`) | NextAuth session | +| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Google OAuth provider | +| `NEXT_PUBLIC_APP_URL` | Absolute URL for OG/metadata | +| `BILLS_DEV_OPEN_ACCESS` | Dev-only admin bypass (see Auth) | +| `BILLS_SLACK_WEBHOOK_URL` | Slack incoming webhook for #builder-mp — posts each newly generated analysis (summary, overall vote, tenet breakdown). No posts if unset. | + +## Local development + +```bash +pnpm dev # Next.js dev server on :5050 — visit /bills +``` + +## Evaluating the LLM features + +The `evals/` directory holds a manual, token-conscious eval suite that runs the +real `summarizeBillText` and `socialIssueGrader` against committed, hand-labeled +**real Parliament-45 bills** (fixtures span align / conflict / abstain / +administrative). It gates on deterministic structural checks and reports +judgment + social-issue accuracy. + +```bash +pnpm eval:bills # run all fixtures (cached where possible) +pnpm eval:bills --refresh # bypass cache, re-call the API +``` + +See **`evals/README.md`** for the full guide (flags, caching, adding fixtures). diff --git a/src/app/bills/api/[id]/reprocess/route.ts b/src/app/bills/api/[id]/reprocess/route.ts index bfe20f3..4384ff9 100644 --- a/src/app/bills/api/[id]/reprocess/route.ts +++ b/src/app/bills/api/[id]/reprocess/route.ts @@ -11,6 +11,7 @@ import { getBillFromCivicsProjectApi, summarizeBillText, } from "@/app/bills/services/billApi"; +import { notifyNewBillAnalysis } from "@/app/bills/services/slack-notifier"; /** * Admin-only endpoint to refetch a bill from the Civics Project API and re-run @@ -138,5 +139,12 @@ export async function POST( { upsert: false }, ); + await notifyNewBillAnalysis({ + billId: id, + title: apiBill.title, + shortTitle: apiBill.shortTitle ?? existing.short_title, + analysis, + }); + return NextResponse.json({ ok: true }); } diff --git a/src/app/bills/env.ts b/src/app/bills/env.ts index f37dadd..2bd28c7 100644 --- a/src/app/bills/env.ts +++ b/src/app/bills/env.ts @@ -44,6 +44,10 @@ export const env = { "BILLS_DEV_OPEN_ACCESS", process.env.BILLS_DEV_OPEN_ACCESS, ), + BILLS_SLACK_WEBHOOK_URL: optional( + "BILLS_SLACK_WEBHOOK_URL", + process.env.BILLS_SLACK_WEBHOOK_URL, + ), }; /** diff --git a/src/app/bills/evals/README.md b/src/app/bills/evals/README.md new file mode 100644 index 0000000..c92a8f1 --- /dev/null +++ b/src/app/bills/evals/README.md @@ -0,0 +1,65 @@ +# /bills LLM eval suite + +Manual evals for the two LLM touchpoints in `/bills`: + +- `summarizeBillText` (`services/billApi.ts`) — summary, 8 tenet evaluations, `final_judgment`, 3 Question Period questions. +- `socialIssueGrader` (`services/social-issue-grader.ts`) — binary "is this primarily a social issue" classifier. + +The suite calls the **real** functions (full prompt → parse → normalize pipeline) against committed, hand-labeled bill fixtures. It is **run manually only** — it spends OpenAI tokens on a cache miss and is never wired into `build`, `lint`, or CI. + +## Running + +```bash +export OPENAI_API_KEY=sk-... # a real key is needed for a real eval +pnpm eval:bills # all fixtures (cached where possible) +pnpm eval:bills --refresh # bypass cache, re-call the API +pnpm eval:bills --only=social # only the social-issue classifier +pnpm eval:bills --only=analysis # only summarizeBillText +pnpm eval:bills --grep=tax # only fixtures whose id contains "tax" +pnpm eval:bills --fallback # force the no-key fallback path (0 tokens) +``` + +Exit code is non-zero when any **structural** check fails. Accuracy (judgment +vs label, social-issue confusion matrix) and cross-consistency are reported but +never gate the run — they are probabilistic. + +## What it checks + +- **Structural** (`checks/analysis-checks.ts`, deterministic, gates the run): + non-empty summary (steel_man is a human-editable field, not LLM-generated, so + it is not checked); exactly 8 tenets with ids 1–8 and valid + `aligns|conflicts|neutral`; valid `final_judgment`; exactly 3 non-empty QP + questions with no "Mr./Madam Speaker" prefix; no `Build Canada`/`we`/`our` + self-reference in prose; (soft warning) tenet text not quoted in the summary. +- **Judgment accuracy** — `final_judgment` vs the fixture's `finalJudgment` label. +- **Social-issue accuracy** — `socialIssueGrader` vs `isSocialIssue` label, with + a confusion matrix and precision/recall. +- **Cross-consistency warning** — flags when `summarizeBillText` abstains but + `socialIssueGrader` disagrees. This is expected: `summarizeBillText` ignores + its own `is_social_issue` field, and the app's stored `isSocialIssue` comes + from the separate grader. + +## Caching + +Each response is cached to `.cache/` (gitignored), keyed by a hash of the input +text **and** the prompt text. Editing a prompt invalidates its entries +automatically; for other changes (model, reasoning effort) bump `VERSION` in +`lib/cache.ts`. Re-runs read from disk, so iterating on checks/fixtures costs no +tokens. `--refresh` forces a re-call. A machine-readable `report.json` is written +to `.cache/` each run for diffing. + +## Adding a fixture + +1. Bootstrap from a real bill (optional, needs `CIVICS_PROJECT_API_KEY`): + ```bash + pnpm tsx src/app/bills/evals/fixtures/fetch-fixture.ts C-101 my-bill + ``` + This writes `fixtures/my-bill.md` using the same fetch + `xmlToMarkdown` path + the app uses. You can also just author a `.md` by hand. +2. Add a labeled record to `fixtures/bills.ts` (`id`, `name`, `markdownFile`, + and `expected.isSocialIssue` / optional `expected.finalJudgment`). Omit + `finalJudgment` when the correct call is genuinely ambiguous. + +The bundled fixtures are concise synthetic bills chosen to span the decision +space (clear social issue, pro-tenet economic, red-tape, administrative). Swap +in real bills for higher-signal accuracy numbers. diff --git a/src/app/bills/evals/checks/analysis-checks.ts b/src/app/bills/evals/checks/analysis-checks.ts new file mode 100644 index 0000000..09150bc --- /dev/null +++ b/src/app/bills/evals/checks/analysis-checks.ts @@ -0,0 +1,147 @@ +import type { BillAnalysis } from "@/app/bills/services/billApi"; +import { TENETS } from "@/app/bills/prompt/summary-and-vote-prompt"; + +export type CheckResult = { + name: string; + /** true = pass. `severity: "warn"` results never fail the run. */ + pass: boolean; + severity: "error" | "warn"; + message: string; +}; + +const ALIGNMENTS = new Set(["aligns", "conflicts", "neutral"]); +const JUDGMENTS = new Set(["yes", "no", "abstain"]); +const SPEAKER_PREFIX = /\b(mr\.?|madam)\s+speaker\b/i; +// Self-reference the prompt explicitly forbids ("use 'Builders' instead"). +const SELF_REF = /\bbuild canada\b|\bwe\b|\bwe're\b|\bwe've\b|\bour\b|\bours\b/i; + +function ok(name: string, message = "ok"): CheckResult { + return { name, pass: true, severity: "error", message }; +} +function fail(name: string, message: string): CheckResult { + return { name, pass: false, severity: "error", message }; +} +function warn(name: string, pass: boolean, message: string): CheckResult { + return { name, pass, severity: "warn", message }; +} + +/** + * Deterministic assertions derived from SUMMARY_AND_VOTE_PROMPT. No LLM calls — + * these grade the structure of a (possibly cached) BillAnalysis object. + */ +export function checkAnalysis(a: BillAnalysis): CheckResult[] { + const results: CheckResult[] = []; + + // summary non-empty. NOTE: steel_man is intentionally NOT checked here — it + // is a human-editable editorial field (admin edit page), not produced by + // SUMMARY_AND_VOTE_PROMPT, so summarizeBillText correctly leaves it "". + results.push( + typeof a.summary === "string" && a.summary.trim().length > 0 + ? ok("summary-present") + : fail("summary-present", "summary is empty or not a string"), + ); + + // tenet evaluations: exactly 8, ids 1-8, valid alignments + const tenets = Array.isArray(a.tenet_evaluations) ? a.tenet_evaluations : []; + if (tenets.length !== 8) { + results.push( + fail("tenets-count", `expected 8 tenet_evaluations, got ${tenets.length}`), + ); + } else { + const ids = new Set(tenets.map((t) => t.id)); + const missing = [1, 2, 3, 4, 5, 6, 7, 8].filter((id) => !ids.has(id)); + results.push( + missing.length === 0 + ? ok("tenets-ids") + : fail("tenets-ids", `missing tenet ids: ${missing.join(", ")}`), + ); + const badAlign = tenets.filter((t) => !ALIGNMENTS.has(t.alignment)); + results.push( + badAlign.length === 0 + ? ok("tenets-alignment") + : fail( + "tenets-alignment", + `invalid alignment values: ${badAlign + .map((t) => `#${t.id}=${JSON.stringify(t.alignment)}`) + .join(", ")}`, + ), + ); + results.push( + tenets.every((t) => typeof t.explanation === "string" && t.explanation.trim()) + ? ok("tenets-explanations") + : fail("tenets-explanations", "one or more tenet explanations are empty"), + ); + } + + // final judgment enum + results.push( + JUDGMENTS.has(a.final_judgment) + ? ok("final-judgment-enum") + : fail( + "final-judgment-enum", + `final_judgment=${JSON.stringify(a.final_judgment)} not in yes|no|abstain`, + ), + ); + + // question period questions: exactly 3, non-empty, no speaker prefix + const qs = Array.isArray(a.question_period_questions) + ? a.question_period_questions + : []; + results.push( + qs.length === 3 + ? ok("qp-count") + : fail("qp-count", `expected 3 question_period_questions, got ${qs.length}`), + ); + const emptyQ = qs.filter((q) => !q?.question || !q.question.trim()); + results.push( + emptyQ.length === 0 + ? ok("qp-nonempty") + : fail("qp-nonempty", `${emptyQ.length} question(s) are empty`), + ); + const speakerQ = qs.filter((q) => SPEAKER_PREFIX.test(q?.question || "")); + results.push( + speakerQ.length === 0 + ? ok("qp-no-speaker-prefix") + : fail( + "qp-no-speaker-prefix", + `${speakerQ.length} question(s) contain a 'Mr./Madam Speaker' reference`, + ), + ); + + // no self-reference across summary / questions / rationale + const proseFields: Array<[string, string]> = [ + ["summary", a.summary || ""], + ["rationale", a.rationale || ""], + ...qs.map((q, i) => [`question[${i}]`, q?.question || ""] as [string, string]), + ]; + const selfRefHits = proseFields.filter(([, v]) => SELF_REF.test(v)); + results.push( + selfRefHits.length === 0 + ? ok("no-self-reference") + : fail( + "no-self-reference", + `forbidden self-reference (Build Canada / we / our) in: ${selfRefHits + .map(([f]) => f) + .join(", ")}`, + ), + ); + + // soft: tenet titles should not be quoted verbatim in the summary + const summaryLower = (a.summary || "").toLowerCase(); + const leakedTenets = Object.values(TENETS).filter((title) => { + // compare on a trimmed core to avoid trailing-punctuation misses + const core = title.replace(/[.().,]/g, "").toLowerCase().trim(); + return core.length > 12 && summaryLower.includes(core); + }); + results.push( + warn( + "summary-no-tenet-verbatim", + leakedTenets.length === 0, + leakedTenets.length === 0 + ? "ok" + : `summary appears to quote tenet text verbatim (${leakedTenets.length} match)`, + ), + ); + + return results; +} diff --git a/src/app/bills/evals/fixtures/arab-heritage-month.md b/src/app/bills/evals/fixtures/arab-heritage-month.md new file mode 100644 index 0000000..7d6d69a --- /dev/null +++ b/src/app/bills/evals/fixtures/arab-heritage-month.md @@ -0,0 +1,32 @@ +# S-227 — An Act respecting Arab Heritage Month +**Introduced:** 2026-06-18 + +## Summary + +This enactment designates the month of April as “Arab Heritage Month”. + +## Preamble + +Whereas the first persons of Arab origin arrived in Canada in 1882, in the early years after Confederation; + +Whereas the population of Arab Canadians has since grown to over one million and continues to flourish, with young and dynamic Arab Canadian communities thriving throughout Canada; + +Whereas Arab Canadians from all walks of life have made important contributions to Canada’s social, economic and political life; + +Whereas Arab Canadians have made rich contributions to the cultural fabric of Canada, including through literature, music, food and fashion; + +And whereas Parliament wishes to recognize and celebrate the historic mark that Arab Canadians have made and continue to make in building Canadian society; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the *Arab Heritage Month Act*. + +# Arab Heritage Month + +### 2. Arab Heritage Month + +Throughout Canada, in each year, the month of April is to be known as “Arab Heritage Month”. diff --git a/src/app/bills/evals/fixtures/basic-income-framework.md b/src/app/bills/evals/fixtures/basic-income-framework.md new file mode 100644 index 0000000..c25f8d3 --- /dev/null +++ b/src/app/bills/evals/fixtures/basic-income-framework.md @@ -0,0 +1,58 @@ +# C-253 — An Act to develop a national framework for a guaranteed livable basic income +**Sponsor:** MS. IDLOUT +**Introduced:** 2025-10-29 + +## Summary + +This enactment requires the Minister of Finance to develop a national framework to provide all persons over the age of 17 in Canada with access to a guaranteed livable basic income. It also provides for reporting requirements with respect to the framework. + +## Preamble + +Whereas every person should have access to a livable basic income; + +Whereas the provision of a guaranteed livable basic income would go a long way toward eradicating poverty and improving income equality, health conditions and educational outcomes; + +Whereas the provision of a guaranteed livable basic income would benefit individuals, families and communities and protect those who are made most vulnerable in society, while facilitating the transition to an economy that responds to the climate crisis and other current major challenges; + +And whereas a guaranteed livable basic income program implemented through a national framework would ensure the respect, dignity and security of all persons in Canada; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _National Framework for a Guaranteed Livable Basic Income Act_. + +# Interpretation + +### 2. Definitions + +The following definitions apply in this Act. + +Indigenous governing body means a council, government or other entity that is authorized to act on behalf of an Indigenous group, community or people that holds rights recognized and affirmed by section 35 of the _Constitution Act, 1982_. (corps dirigeant autochtone)Minister means the Minister of Finance. (ministre) +# National Framework + +### 3. Development + +**(1)** The Minister must develop a national framework for the implementation of a guaranteed livable basic income program throughout Canada for any person over the age of 17, including temporary workers, permanent residents and refugee claimants. + +**(2) - Consultation** In developing the framework, the Minister must consult with the Minister of Health, the ministers responsible for employment, social development and disability, representatives of the provincial governments responsible for health, disability, education and social development, Indigenous elders, Indigenous governing bodies and other relevant stakeholders, including policy developers and political decision-makers, as well as experts in other guaranteed livable basic income programs. + +**(3) - Content** The framework must include measures +- **(a)** to determine what constitutes a livable basic income for each region in Canada, taking into account the goods and services that are necessary to ensure that individuals can lead a dignified and healthy life, as well as the cost of those goods and services in accessible markets; +- **(b)** to create national standards for health and social supports that complement a guaranteed livable basic income program and guide the implementation of such a program in every province; +- **(c)** to ensure that participation in education, training or the labour market is not required in order to qualify for a guaranteed livable basic income; and +- **(d)** to ensure that the implementation of a guaranteed livable basic income program does not result in a de-crease in services or benefits meant to meet an individual’s exceptional needs related to health or disability. + +# Reports to Parliament + +### 4. Tabling of framework + +**(1)** Within one year after the day on which this Act comes into force, the Minister must prepare a report setting out the framework, including any social, health and economic conclusions and recommendations related to its development, and cause the report to be tabled in each House of Parliament on any of the first 15 days on which that House is sitting after it is completed. + +**(2) - Publication** The Minister must publish the report on the website of the Department of Finance within 10 days after the day on which the report has been tabled in both Houses of Parliament. + +### 5. Report + +Within two years after the day on which the report referred to in section 4 has been tabled in both Houses of Parliament, and every year after that, the Minister must, in consultation with the parties referred to in subsection 3(2), undertake a review of the effectiveness of the framework, prepare a report setting out the social, health and economic findings and recommendations related to the implementation and effectiveness of the framework, and cause the report to be tabled in each House of Parliament on any of the first 15 days on which that House is sitting after it is completed. diff --git a/src/app/bills/evals/fixtures/bills.ts b/src/app/bills/evals/fixtures/bills.ts new file mode 100644 index 0000000..cb042e9 --- /dev/null +++ b/src/app/bills/evals/fixtures/bills.ts @@ -0,0 +1,119 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +export type Judgment = "yes" | "no" | "abstain"; + +export type FixtureLabel = { + /** Hand-labeled ground truth for the social-issue classifier. */ + isSocialIssue: boolean; + /** + * Expected final_judgment. Omit where the correct call is genuinely + * ambiguous — structural checks still run, accuracy just skips the case. + */ + finalJudgment?: Judgment; +}; + +export type Fixture = { + id: string; + name: string; + markdownFile: string; + expected: FixtureLabel; +}; + +/** + * Real Parliament-45 bills spanning the decision space, fetched via + * fetch-fixture.ts. Labels are grounded in Build Canada's documented positions + * (pro-growth, internal free trade + labour mobility, red-tape reduction, + * resource/energy development, pro-reinvestment tax reform, government + * efficiency; social/rights/identity issues are out of scope → abstain). + * + * Add a fixture by fetching a bill (`pnpm tsx fixtures/fetch-fixture.ts `) + * and appending a labeled record below. Omit `finalJudgment` where the bill + * genuinely pulls in two directions — accuracy just skips it, structural + * checks still run. + */ +export const FIXTURES: Fixture[] = [ + // --- Social / rights / identity → abstain ------------------------------- + { + id: "arab-heritage-month", + name: "S-227 — Arab Heritage Month (heritage month)", + markdownFile: "arab-heritage-month.md", + expected: { isSocialIssue: true, finalJudgment: "abstain" }, + }, + { + id: "national-bird-canada-jay", + name: "S-221 — Canada jay as national bird (national symbol)", + markdownFile: "national-bird-canada-jay.md", + expected: { isSocialIssue: true, finalJudgment: "abstain" }, + }, + { + id: "criminal-hate-propaganda", + name: "C-9 — Criminal Code hate propaganda / hate crime (rights/identity)", + markdownFile: "criminal-hate-propaganda.md", + expected: { isSocialIssue: true, finalJudgment: "abstain" }, + }, + + // --- Clear alignment with the tenets → yes ------------------------------ + { + id: "free-trade-labour-mobility", + name: "C-5 — Free Trade and Labour Mobility + Building Canada Act (internal trade, major projects)", + markdownFile: "free-trade-labour-mobility.md", + // Research-confirmed flagship alignment: removes internal trade barriers, + // labour mobility, single-authorization major-project permitting. + expected: { isSocialIssue: false, finalJudgment: "yes" }, + }, + { + id: "skilled-trades-labour-mobility", + name: "C-266 — National framework for skilled trades & labour mobility", + markdownFile: "skilled-trades-labour-mobility.md", + expected: { isSocialIssue: false, finalJudgment: "yes" }, + }, + + // --- Clear conflict with the tenets → no -------------------------------- + { + id: "labour-code-replacement-workers", + name: "C-284 — Canada Labour Code, ban on replacement workers (labour-market rigidity)", + markdownFile: "labour-code-replacement-workers.md", + expected: { isSocialIssue: false, finalJudgment: "no" }, + }, + { + id: "supply-management-protection", + name: "C-202 — Entrench supply management in trade policy (protectionism)", + markdownFile: "supply-management-protection.md", + // Locks dairy/poultry/egg tariff-rate quotas out of trade negotiations — + // directly conflicts with the free-trade / exports / competitiveness tenets. + expected: { isSocialIssue: false, finalJudgment: "no" }, + }, + { + id: "basic-income-framework", + name: "C-253 — National framework for a guaranteed livable basic income (redistribution/fiscal expansion)", + markdownFile: "basic-income-framework.md", + // Fiscal/redistributive (not rights/identity → not a social-issue abstain); + // conflicts with government efficiency and "wealth creation before + // redistribution". + expected: { isSocialIssue: false, finalJudgment: "no" }, + }, + + // --- Genuinely two-sided / administrative → no judgment label ----------- + { + id: "industry-act-small-business", + name: "C-291 — Mandatory small-business impact assessment on all legislation", + markdownFile: "industry-act-small-business.md", + // Pro-small-business intent, but adds a mandatory assessment gate to every + // legislative initiative (red tape). Genuinely ambiguous — structural only. + expected: { isSocialIssue: false }, + }, + { + id: "financial-administration-amendment", + name: "C-230 — Public registry of forgiven Crown debts (administrative transparency)", + markdownFile: "financial-administration-amendment.md", + expected: { isSocialIssue: false }, + }, +]; + +const FIXTURE_DIR = dirname(fileURLToPath(import.meta.url)); + +export function loadFixtureText(f: Fixture): string { + return readFileSync(join(FIXTURE_DIR, f.markdownFile), "utf8"); +} diff --git a/src/app/bills/evals/fixtures/criminal-hate-propaganda.md b/src/app/bills/evals/fixtures/criminal-hate-propaganda.md new file mode 100644 index 0000000..4fb3b80 --- /dev/null +++ b/src/app/bills/evals/fixtures/criminal-hate-propaganda.md @@ -0,0 +1,142 @@ +# C-9 — An Act to amend the Criminal Code (hate propaganda, hate crime and access to religious or cultural places) +**Introduced:** 2026-03-25 + +## Summary + +This enactment amends the _Criminal Code_ to, among other things, + +His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _Combatting Hate Act_. + +# _Criminal Code_— (R.S., c. C-46) + +### 2 + +Paragraph (a) of the definition offence in section 183 of the _Criminal Code_ is amended by adding the following after subparagraph (lxxi.1): + + - **(lxxi.2)** section 423.3 (intimidation — building used for religious worship, etc.), + +### 3 + +Subsection 318(3) of the Act is replaced by the following: + +**(3) - Consent** No proceeding shall be instituted under this section without the consent of the Attorney General. + +### 4 + +**(1)** Section 319 of the Act is amended by adding the following after subsection (2.1): + +**(1.1)** Paragraph 319(3)(b) of the Act is repealed. + +**(1.2)** Paragraph 319(3.1)(b) of the Act is repealed. + +**(2)** Subsections 319(4) to (6) of the Act are replaced by the following: + +**(3)** Subsection 319(7) of the Act is amended by adding the following in alphabetical order: + +### 5 + +The Act is amended by adding the following after section 320.1: + +## Hate Crime + +### 320.1001. Offence motivated by hatred + +**(1)** Everyone who commits an offence — referred to in this section as the “included offence” — under this Act or any other Act of Parliament, if the commission of the included offence is motivated by hatred based on race, national or ethnic origin, language, colour, religion, sex, age, mental or physical disability, sexual orientation or gender identity or expression, is +- **(a)** guilty of an indictable offence and liable to the punishment provided for in subsection (5); or +- **(b)** guilty of an offence punishable on summary conviction. + +**(2) - Definition of hatred** In this section, hatred has the same meaning as in subsection 319(7). + +**(3) - Clarification** For greater certainty, the commission of an offence under this Act or any other Act of Parliament is not, for the purposes of this section, motivated by hatred based on any of the factors mentioned in subsection (1) solely because it discredits, humiliates, hurts or offends. + +**(4) - Limitation** No proceedings shall be commenced under subsection (1) by way of indictment if the included offence may be prosecuted only by way of summary conviction proceedings. + +**(5) - Maximum penalty** Everyone who is found guilty of an indictable offence under subsection (1) is liable to a term of imprisonment of not more than +- **(a)** five years, if the maximum term of imprisonment for the included offence is two years or more but less than five years; +- **(b)** 10 years, if the maximum term of imprisonment for the included offence is five years or more but less than 10 years; +- **(c)** 14 years, if the maximum term of imprisonment for the included offence is 10 years or more but less than 14 years; or +- **(d)** life, if the maximum term of imprisonment for the included offence is 14 years or more and up to imprisonment for life. + +**(6) - Applicable provisions** Subject to paragraphs (1)(a) and (b) and subsections (4) and (5), any provision of this Act or any other Act of Parliament — including one in respect of procedure, orders or consequences — that would have been applicable in relation to the included offence applies in relation to an offence under subsection (1). + +### 6 + +The Act is amended by adding the following after section 423.2: + +### 423.3. Intimidation — building used for religious worship, etc. + +**(1)** Every person commits an offence who engages in any conduct with the intent to provoke a state of fear in a person in order to impede their access to +- **(a)** a building or structure, or part of a building or structure, that is primarily used + - **(i)** for religious worship, or + - **(ii)** by an identifiable group, as defined in subsection 318(4), +- **(b)** a cemetery. + +**(2) - Obstruction or interference with access** Every person commits an offence who, without lawful authority, intentionally obstructs or interferes with another person’s lawful access to a building or structure, or part of a building or structure, referred to in paragraph (1)(a) or to a cemetery. + +**(3) - Punishment** Every person who commits an offence under subsection (1) or (2) is +- **(a)** guilty of an indictable offence and liable to imprisonment for a term of not more than 10 years; or +- **(b)** guilty of an offence punishable on summary conviction. + +**(4) - Exception** No person is guilty of an offence under subsection (2) by reason only that they attend at or near, or approach, a building or structure referred to in paragraph (1)(a) or a cemetery for the purpose only of obtaining or communicating information. + +### 8 + +Paragraph (c) of the definition secondary designated offence in section 487.04 of the Act is amended by adding the following after subparagraph (xi.01): + +- **(xi.02)** subsection 423.3(1) (intimidation — building used for religious worship, etc.), + +### 9 + +**(1)** Subsection 515(4.1) of the Act is amended by adding the following after paragraph (b.11): + +**(2)** Paragraph 515(4.3)(b) of the Act is replaced by the following: + +### 10 + +Section 662 of the Act is amended by adding the following after subsection (6): + +**(7) - Offence under subsection 320.1001(1) charged** For greater certainty, if a count charges an offence under subsection 320.1001(1) and the evidence does not prove that offence but proves an included offence, the accused may be found guilty of the offence that is proved. + +### 11 + +The Act is amended by adding the following after section 726.2: + +### 726.21. Endorsement — offence under subsection 320.1001(1) + +When an offender is found guilty of an offence under subsection 320.1001(1), the court shall endorse, on the information or indictment, as the case may be, the included offence that has been proved by the evidence and, in the absence of evidence to the contrary, the endorsement is proof of that fact. + +# Clarification + +### 11.1. Clarification — subsections 319(2) and (2.2) + +**(1)** For greater certainty, nothing in +subsection 319(2) or (2.2) of the _Criminal Code_ +shall be construed as prohibiting a person from +communicating a statement on a matter of public +interest, including an educational, religious, +political or scientific statement made in the course +of a discussion, publication or debate, if they do +not wilfully promote hatred against an identifiable +group by communicating the statement. + +**(2) - Clarification — subsection 319(2.1)** For greater certainty, nothing in subsection +319(2.1) of the _Criminal Code_ shall be construed +as prohibiting a person from communicating a +statement on a matter of public interest, including +an educational, religious, political or scientific +statement made in the course of a discussion, +publication or debate, if they do not wilfully +promote antisemitism by condoning, denying or +downplaying the Holocaust. + +# Coming into Force + +### 12. 30th day after royal assent + +This Act comes into force on the 30th day after the day on which it receives royal assent. diff --git a/src/app/bills/evals/fixtures/fetch-fixture.ts b/src/app/bills/evals/fixtures/fetch-fixture.ts new file mode 100644 index 0000000..a0e711b --- /dev/null +++ b/src/app/bills/evals/fixtures/fetch-fixture.ts @@ -0,0 +1,64 @@ +/** + * Bootstrap a fixture from a real Canadian bill. + * + * pnpm tsx src/app/bills/evals/fixtures/fetch-fixture.ts [outId] + * + * Fetches the bill via the existing Civics Project fetchers, renders its text + * to markdown with the same deterministic util the app uses, and writes it to + * `.md` in this directory. Then hand-label it by adding a record to + * bills.ts. Requires CIVICS_PROJECT_API_KEY. This is a one-off authoring + * convenience — it is NOT part of the eval run (the committed .md files are). + */ +import { writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + getBillFromCivicsProjectApi, + fetchBillMarkdown, +} from "@/app/bills/services/billApi"; + +async function main() { + const billId = process.argv[2]; + const outId = process.argv[3] || billId?.toLowerCase(); + if (!billId) { + console.error( + "Usage: pnpm tsx src/app/bills/evals/fixtures/fetch-fixture.ts [outId]", + ); + process.exit(1); + } + if (!process.env.CIVICS_PROJECT_API_KEY) { + console.error("CIVICS_PROJECT_API_KEY is required to fetch a real bill."); + process.exit(1); + } + + const bill = await getBillFromCivicsProjectApi(billId); + if (!bill) { + console.error(`No bill found for id "${billId}".`); + process.exit(1); + } + + const source = + bill.source || (bill.billTexts?.[0] as { url?: string } | undefined)?.url; + if (!source) { + console.error(`Bill "${billId}" has no text source URL to render.`); + process.exit(1); + } + + const markdown = await fetchBillMarkdown(source); + if (!markdown) { + console.error(`Failed to fetch/convert bill text from ${source}.`); + process.exit(1); + } + + const outPath = join(dirname(fileURLToPath(import.meta.url)), `${outId}.md`); + writeFileSync(outPath, markdown); + console.log(`Wrote ${outPath} (${markdown.length} chars).`); + console.log( + `Now add a labeled record for "${outId}" to fixtures/bills.ts (title: ${bill.title}).`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app/bills/evals/fixtures/financial-administration-amendment.md b/src/app/bills/evals/fixtures/financial-administration-amendment.md new file mode 100644 index 0000000..4255931 --- /dev/null +++ b/src/app/bills/evals/fixtures/financial-administration-amendment.md @@ -0,0 +1,97 @@ +# C-230 — An Act to amend the Financial Administration Act and to make consequential amendments to other Acts (debt forgiveness registry) +**Introduced:** 2026-05-07 + +## Summary + +This enactment amends the _Financial Administration Act_ to require that the President of the Treasury Board establish and maintain a public registry of large debts and obligations owed by certain entities to His Majesty, as well as claims by His Majesty against such entities, that have been remitted, forgiven, written off or waived. It also makes consequential amendments to other Acts. + +His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Financial Administration Act— (R.S., c. F-11) + +### 1 + +The _Financial Administration Act_ is amended by adding the following after section 25: + +### 25.1. Public registry + +**(1)** The President of the Treasury Board must establish and maintain a public registry in the form of an online, searchable database containing information relating to any debt or obligation owed by a corporation, trust company or partnership to His Majesty — and to any claim by His Majesty against a corporation, trust company or partnership — that was, in whole or in part, remitted, forgiven, written off or waived, under this or any other Act of Parliament, in an amount of $2,000,000 or more. + +**(2) - Contents of registry** The registry must include the following information in relation to each debt, obligation or claim: +- **(a)** the name, as well as any business name, of the corporation, trust company or partnership; +- **(b)** the amount that was remitted, forgiven, written off or waived; +- **(c)** an indication of whether the debt, obligation or claim was remitted, forgiven, written off or waived; +- **(d)** the fiscal year in which the debt, obligation or claim was remitted, forgiven, written off or waived; +- **(e)** the Act of Parliament or agreement, arrangement, contract or other instrument or act under which the debt, obligation or claim arose; +- **(f)** the Act of Parliament under which the debt, obligation or claim was remitted, forgiven, written off or waived; and +- **(g)** any other information that the President of the Treasury Board considers appropriate. + +**(3) - Exception** Despite subsection (2), the President of the Treasury Board may exclude from the registry any information that, in their opinion, would be inappropriate to make publicly accessible for reasons related to the protection of confidential, personal or sensitive information. + +**(3.1) - Reasons for exclusion** If any information is excluded from the registry under subsection (3), the President of the Treasury Board must identify in the registry whether the information was excluded for reasons relating to the protection of confidential information, personal information or sensitive information. + +**(4) - Establishment of registry** The registry must be established within 18 months after the day on which this section comes into force and, on the day that it is established, must include information relating to each debt, obligation or claim referred to in subsection (1) that was remitted, forgiven, written off or waived — after the day on which this section comes into force — during the fiscal year to which the Public Accounts most recently tabled in the House of Commons relate. + +**(5) - Maintenance of registry** Within 90 days after the day on which the Public Accounts are tabled in the House of Commons, the President of the Treasury Board must update the registry to include information relating to each debt, obligation or claim referred to in subsection (1) that was remitted, forgiven, written off or waived during the fiscal year to which the tabled Public Accounts relate. + +**(6) - For greater certainty** For greater certainty, for the purposes of this section, a debt or obligation owed to a Crown corporation is not a debt or obligation owed to His Majesty and a claim by a Crown corporation is not a claim by His Majesty. + +# Consequential Amendments + +## Income Tax Act— (R.S., c. 1 (5th Supp.)) + +### 2 + +Paragraph 241(4)(f) of the _Income Tax Act_ is replaced by the following: + +- **(f)** provide taxpayer information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +## Excise Tax Act— (R.S., c. E-15) + +### 3 + +Paragraph 295(5)(e) of the _Excise Tax Act_ is replaced by the following: + +- **(e)** provide confidential information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +## Excise Act, 2001— (2002, c. 22) + +### 4 + +Paragraph 211(6)(f) of the _Excise Act, 2001_ is replaced by the following: + +- **(f)** provide confidential information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +## Softwood Lumber Products Export Charge Act, 2006— (2006, c. 13) + +### 5 + +Paragraph 84(6)(f) of the _Softwood Lumber Products Export Charge Act, 2006_ is replaced by the following: + +- **(f)** provide confidential information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +## Digital Services Tax Act— (2024, c. 15, s. 96) + +### 6 + +Paragraph 108(6)(f) of the _Digital Services Tax Act_ is replaced by the following: + +- **(f)** provide confidential information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +## Global Minimum Tax Act— (2024, c. 17, s. 81) + +### 7 + +Paragraph 123(6)(f) of the _Global Minimum Tax Act_ is replaced by the following: + +- **(f)** provide confidential information solely for the purposes of sections 23 to 25.1 of the _Financial Administration Act_; + +# Coordinating Amendments + +### 8. Bill C-15 + +**(1)** Subsections (2) and (3) apply if Bill C-15, introduced in the 1st session of the 45th Parliament and entitled _Budget 2025 Implementation Act, No. 1_ (in this section referred to as the “other Act”), receives royal assent. + +**(2)** If subsection 126(2) of the other Act comes into force before section 6 of this Act, then that section 6 is deemed never to have come into force and is repealed. + +**(3)** If subsection 126(2) of the other Act comes into force on the same day as section 6 of this Act, then that section 6 is deemed never to have come into force and is repealed. diff --git a/src/app/bills/evals/fixtures/free-trade-labour-mobility.md b/src/app/bills/evals/fixtures/free-trade-labour-mobility.md new file mode 100644 index 0000000..dfee3e0 --- /dev/null +++ b/src/app/bills/evals/fixtures/free-trade-labour-mobility.md @@ -0,0 +1,565 @@ +# C-5 — An Act to enact the Free Trade and Labour Mobility in Canada Act and the Building Canada Act +**Introduced:** 2025-06-26 + +RECOMMENDATIONHer Excellency the Governor General recommends to the House of Commons the appropriation of public revenue under the circumstances, in the manner and for the purposes set out in a measure entitled “*An Act to enact the Free Trade and Labour Mobility in Canada Act and the Building Canada Act*”. + +## Summary + +Part 1 enacts the _Free Trade and Labour Mobility in Canada Act_, which establishes a statutory framework to remove federal barriers to the interprovincial trade of goods and services and to improve labour mobility within Canada. In the case of goods and services, that Act provides that a good or service that meets provincial or territorial requirements is considered to meet comparable federal requirements that pertain to the interprovincial movement of the good or provision of the service. In the case of workers, it provides for the recognition of provincial and territorial authorizations to practise occupations and for the issuance of comparable federal authorizations to holders of such provincial and territorial authorizations. It also provides the Governor in Council with the power to make regulations respecting federal barriers to the interprovincial movement of goods and provision of services and to the movement of labour within Canada. + +Part 2 enacts the _Building Canada Act_, which, among other things, + +authorizes the Governor in Council to add the name of a project and a brief description of it to a schedule to that Act if the Governor in Council is of the opinion, having regard to certain factors, that the project is in the national interest; + +provides that determinations and findings that have to be made and opinions that have to be formed under certain Acts of Parliament and regulations for an authorization to be granted in respect of a project that is named in Schedule 1 to that Act are deemed to have been made or formed, as the case may be, in favour of permitting the project to be carried out in whole or in part; + +requires the minister who is designated under that Act to issue to the proponent of a project, if certain conditions are met, a document that sets out conditions that apply in respect of the project and that is deemed to be the authorizations, required under certain Acts of Parliament and regulations, that are specified in the document; and + +requires that minister, each year, to cause an independent review to be conducted of the status of each national interest project. + +# TABLE OF PROVISIONS + +# An Act to enact the Free Trade and Labour Mobility in Canada Act and the Building Canada Act + +# Short Title + +*1*_One Canadian Economy Act_ +# _Free Trade and Labour Mobility in Canada Act_ + +## Enactment of Act + +*2*Enactment +# An Act to promote free trade and labour mobility in Canada + +# Short Title + +1_Free Trade and Labour Mobility in Canada Act_ +# Interpretation + +2Definitions3Act and regulations prevail +# Purpose of Act + +4Purpose +# His Majesty + +5Binding on His Majesty +# Designation of Minister + +6Order +# Removal of Barriers + +## Goods and Services + +7Application8Goods9Services +## Labour Mobility + +10Recognition +# Regulations + +11Governor in Council +# Limitation of Liability + +12Acts done in good faith +# Review of Act + +13Review and report +## Coming into Force + +*3*Order in council +# _Building Canada Act_ + +## Enactment of Act + +*4*Enactment +# An Act respecting national interest projects + +# Short Title + +1_Building Canada Act_ +# Definitions + +2Definitions +# Designation of Minister + +3Order +# Purpose of Act + +4Purpose +# National Interest Projects + +4.1National interest5Power of Governor in Council5.1Public Registry6Deeming — favourable determinations, findings and opinions7Duty to issue document8Power to amend conditions8.1Information available to public9_Canada–Newfoundland and Labrador Atlantic Accord Implementation and Offshore Renewable Energy Management Act_ — subsection 7(1)10_Canada–Nova Scotia Offshore Petroleum Resources Accord Implementation and Offshore Renewable Energy Management Act_ — subsection 7(1) +# _Nuclear Safety and Control Act_ + +11Consultation — subsection 7(1)12Consultation — subsections 8(1) and (2)13Limit — subsection 7(1)14Limit — subsections 8(1) and (2) +# _Canadian Energy Regulator Act_ + +15Consultation — subsection 7(1)16Consultation — subsections 8(1) and (2)17Limit — subsection 7(1)18Limit — subsections 8(1) and (2) +# _Impact Assessment Act_ + +19Non-application of certain provisions +# Office + +20Role +# Amendment to Schedule 2 + +21Add, amend or delete +# Regulations + +22Regulations — enactment23Regulations — this Act +# Annual Report + +23.1Review: national interest project +# Review of Act + +24Review by Parliamentary Review Committee +##### SCHEDULE + +His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _One Canadian Economy Act_. + +# _Free Trade and Labour Mobility in Canada Act_ + +## Enactment of Act + +### 2. Enactment + +The _Free Trade and Labour Mobility in Canada Act_ is enacted as follows: + +An Act to promote free trade and labour mobility in Canada +## Preamble + +Whereas the Government of Canada intends to remove federal exceptions under the _Canadian Free Trade Agreement_; + +Whereas the Government of Canada wishes to continue to work with provinces and territories towards establishing a national system of mutual recognition in which a good, service or worker that meets the requirements of one Canadian jurisdiction would be recognized as meeting the requirements of all; + +And whereas Parliament is committed to strengthening the Canadian economy by + +improving labour mobility within Canada, and + +making it easier for businesses and Canadians to buy Canadian goods and services through the removal of federal barriers to the interprovincial movement of goods and provision of services, while continuing to protect the health, safety and security of Canadians, their social and economic well-being and the environment; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _Free Trade and Labour Mobility in Canada Act_. + +# Interpretation + +### 2. Definitions + +The following definitions apply in this Act. + +federal regulatory body means- **(a)** in relation to a good or service, + - **(i)** a body that is empowered under an Act of Parliament to regulate the good or service, or + - **(ii)** a body designated by the regulations that regulates the good or service; and +- **(b)** in relation to an occupation, + - **(i)** a body that is empowered under an Act of Parliament to issue authorizations to practise the occupation, or + - **(ii)** a body designated by the regulations that issues authorizations to practise the occupation. (organisme de réglementation fédéral) +federal requirement means a requirement established under an Act of Parliament or by a federal regulatory body. (exigence fédérale)Minister means the member of the King’s Privy Council for Canada designated under section 6. (ministre)provincial or territorial regulatory body means- **(a)** in relation to a good or service, + - **(i)** a body that is empowered under an Act of the legislature of a province or territory to regulate the good or service, or + - **(ii)** a body designated by the regulations that regulates the good or service; and +- **(b)** in relation to an occupation, + - **(i)** a body that is empowered under an Act of the legislature of a province or territory to issue authorizations to practise the occupation, or + - **(ii)** a body designated by the regulations that issues authorizations to practise the occupation. (organisme de réglementation provincial ou territorial) +provincial or territorial requirement means a requirement established under an Act of the legislature of a province or territory or by a provincial or territorial regulatory body. (exigence provinciale ou territoriale) +### 3. Act and regulations prevail + +The provisions of this Act and the regulations made under it prevail over the provisions of any other Act of Parliament and any regulations made under any other Act of Parliament to the extent of any conflict between them. + +# Purpose of Act + +### 4. Purpose + +The purpose of this Act is to promote free trade and labour mobility by removing federal barriers to the interprovincial movement of goods and provision of services and to the movement of labour within Canada while continuing to protect the health, safety and security of Canadians, their social and economic well-being and the environment. + +# His Majesty + +### 5. Binding on His Majesty + +This Act is binding on His Majesty in right of Canada. + +# Designation of Minister + +### 6. Order + +The Governor in Council may, by order, designate a member of the King’s Privy Council for Canada as the Minister for the purposes of this Act. + +# Removal of Barriers + +## Goods and Services + +### 7. Application + +Sections 8 and 9 apply in respect of a federal requirement only if the federal requirement pertains to + +- **(a)** a good or service that is also subject to a provincial or territorial requirement; and +- **(b)** the interprovincial movement of the good or provision of the service. + +### 8. Goods + +**(1)** Subject to the regulations, a good produced, used or distributed in accordance with a provincial or territorial requirement is considered to meet any comparable federal requirement. + +**(2) - Comparable requirements** For the purposes of subsection (1), a provincial or territorial requirement is considered to be comparable to a federal requirement only if +- **(a)** the requirements are in respect of the same aspect or element of the good; +- **(b)** the requirements are intended to achieve a similar objective; and +- **(c)** any conditions set out in the regulations are met. + +**(3) - Decision** The federal regulatory body responsible for the administration and enforcement of a federal requirement may decide, in accordance with subsection (2), whether a provincial or territorial requirement is comparable to the federal requirement. + +### 9. Services + +**(1)** Subject to the regulations, a service provided in accordance with a provincial or territorial requirement is considered to meet any comparable federal requirement so long as the provincial or territorial requirement continues to apply to the service provider. + +**(2) - Comparable requirements** For the purposes of subsection (1), a provincial or territorial requirement is considered to be comparable to a federal requirement only if +- **(a)** the requirements are in respect of the same aspect or element of the service; +- **(b)** the requirements are intended to achieve a similar objective; and +- **(c)** any conditions set out in the regulations are met. + +**(3) - Decision** The federal regulatory body responsible for the administration and enforcement of a federal requirement may decide, in accordance with subsection (2), whether a provincial or territorial requirement is comparable to the federal requirement. + +## Labour Mobility + +### 10. Recognition + +Subject to the regulations, a federal regulatory body must + +- **(a)** recognize an authorization to practise an occupation issued by a provincial or territorial regulatory body as comparable to an authorization that the federal regulatory body may issue to practise that occupation; and +- **(b)** on application by the holder of such a provincial or territorial authorization, issue them an authorization to practise that occupation. + +# Regulations + +### 11. Governor in Council + +**(1)** The Governor in Council may, on the recommendation of the Minister, make regulations respecting federal barriers to the interprovincial movement of goods and provision of services and to the movement of labour within Canada, including regulations +- **(a)** providing for exceptions to subsection 8(1) or 9(1) or section 10; +- **(b)** imposing obligations, prohibitions, conditions and restrictions for the purposes of any of sections 8 to 10; +- **(c)** respecting, for the purposes of subsections 8(2) and 9(2), the meaning of the expressions “same aspect or element” and “achieve a similar objective” or any term used in those expressions; +- **(d)** respecting the meaning of the term “authorization” for the purposes of this Act; +- **(e)** respecting any transitional matters arising from the coming into force of this Act or of any amendments to it; and +- **(f)** respecting anything that by this Act is to be provided for by the regulations. + +**(2) - Consultation** Before recommending a regulation to the Governor in Council under paragraph (1)(a) in relation to a federal requirement or authorization, the Minister must consult the federal regulatory body responsible for the administration and enforcement of the federal requirement or for the issuance of the authorization. + +# Limitation of Liability + +### 12. Acts done in good faith + +**(1)** Despite any other Act of Parliament, no civil action lies against His Majesty, a servant or agent of the Crown or a federal regulatory body in respect of anything done or omitted to be done, or purported to be done or omitted to be done, in good faith in the course of applying section 8, 9 or 10 or any regulations made for the purposes of any of those sections, including anything in relation to whether provincial or territorial requirements are comparable to federal requirements and the recognition and issuance of authorizations to practise an occupation. + +**(2) - For greater certainty** For greater certainty, subsection (1) does not apply in respect of applications for judicial review or to proceedings under Chapter Ten of the _Canadian Free Trade Agreement_. + +# Review of Act + +### 13. Review and report + +Within five years after the day on which this Act comes into force, the Minister must complete a review of this Act and its operation and cause a report on the review to be laid before each House of Parliament. + +## Coming into Force + +### 3. Order in council + +The _Free Trade and Labour Mobility in Canada Act_ comes into force on a day to be fixed by order of the Governor in Council. + +# _Building Canada Act_ + +## Enactment of Act + +### 4. Enactment + +The _Building Canada Act_, whose text is as follows and whose Schedules 1 and 2 are set out in the schedule to this Act, is enacted: + +An Act respecting national interest projects +## Preamble + +Whereas Parliament recognizes that it is in the interests of Canada’s economy, sovereignty and security, including its energy security, to urgently advance projects throughout Canada, including in the North, that are in the national interest, including projects that + +Whereas the Government of Canada is committed to working in partnership with provincial, territorial and Indigenous governments and Indigenous peoples; + +Whereas the Government of Canada is committed to respecting the rights of Indigenous peoples recognized and affirmed by section 35 of the _Constitution Act, 1982_ and the rights set out in the United Nations Declaration on the Rights of Indigenous Peoples; + +Whereas the Government of Canada is committed to upholding rigorous standards with respect to environmental protection; + +And whereas Parliament affirms the need for projects that are in the national interest to be advanced through an accelerated process that enhances regulatory certainty and investor confidence; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _Building Canada Act_. + +# Definitions + +### 2. Definitions + +The following definitions apply in this Act. + +authorization means, in respect of a national interest project, an approval or other decision, or a permit, licence, regulation or other document or instrument, that is required, by a provision of an enactment or, if a portion of an enactment is listed in column 2 of Part 1 or Part 2 of Schedule 2, by that portion of the enactment, to permit the project to be carried out, in whole or in part. (autorisation)enactment means an Act of Parliament listed in column 1 of Part 1 of Schedule 2 or a regulation listed in column 1 of Part 2 of that Schedule. (texte législatif)Indigenous peoples has the meaning assigned by the definition aboriginal peoples of Canada in subsection 35(2) of the _Constitution Act, 1982_. (peuples autochtones)Minister means the member of the King’s Privy Council for Canada designated under section 3. (ministre)national interest project means a project named in Schedule 1. (projet d’intérêt national)Parliamentary Review Committee means the committee referred to in subsection 62(1) of the _Emergencies Act_. Its chair or joint chair, on the part of the House of Commons, shall be a member of that House who is not a member of the government party. (comité d’examen parlementaire) +# Designation of Minister + +### 3. Order + +The Governor in Council may, by order, designate a member of the King’s Privy Council for Canada as the Minister for the purposes of this Act. + +# Purpose of Act + +### 4. Purpose + +The purpose of this Act is to enhance Canada’s prosperity, national security, economic security, national defence and national autonomy by ensuring that projects that are in the national interest are advanced through an accelerated process that enhances regulatory certainty and investor confidence, while protecting the environment and respecting the rights of Indigenous peoples. + +# National Interest Projects + +### 4.1. National interest + +**(1)** The Governor in Council may, by order, for the purposes of section 5, define national interest. + +**(2) - Criteria** In order to promote transparency and predictability, an order made under subsection (1) must set out specific criteria that must be met by the proponent of a project in order for the project to be found to be in the national interest. + +**(3) - Report** If an order is not made within 15 days after the day on which this Act comes into force, the Minister must, within five sitting days of the end of that period, cause to be tabled in each House of Parliament a report that sets out the reasons for the delay and the expected timeline for the making of the order. + +### 5. Power of Governor in Council + +**(1)** If the Governor in Council is of the opinion that a project is in the national interest, the Governor in Council may, on the recommendation of the Minister, by order, amend Schedule 1 to add the name of the project and a detailed description of it, including the location where it is to be carried out. + +**(1.1) - Publication and consent of province or territory** Before adding the name of a project to Schedule 1, the Governor in Council must cause a notice of 30 days, that includes the name and description of the project, to be published in the _Canada Gazette_ and must consult with the government of the province or territory in which the project will be carried out, and obtain its written consent if the project falls within areas of exclusive provincial or territorial jurisdiction. + +**(2) - Limit** The Governor in Council is not authorized to make an order under subsection (1) while Parliament is prorogued or dissolved or after the fifth anniversary of the day on which this section comes into force. + +**(3) - Amendment** The Governor in Council may, on the recommendation of the Minister, by order, amend Schedule 1 to amend the name or the description of a national interest project. + +**(4) - Deletion** If the Governor in Council is of the opinion that a project named in Schedule 1 is no longer in the national interest, the Governor in Council may, on the recommendation of the Minister, by order, amend that Schedule to delete the name and the description of the project. + +**(5) - Limit** The Governor in Council is not authorized to make an order under subsection (4) in respect of a national interest project after a document is issued in respect of the project under subsection 7(1). + +**(6) - Factors** In deciding whether to make an order under subsection (1) or (4) in respect of a project, the Governor in Council may consider any factor that the Governor in Council considers relevant, including the extent to which the project can +- **(a)** strengthen Canada’s autonomy, resilience and security; +- **(b)** provide economic or other benefits to Canada; +- **(c)** have a high likelihood of successful execution; +- **(d)** advance the interests of Indigenous peoples; and +- **(e)** contribute to clean growth and to meeting Canada’s objectives with respect to climate change. + +**(6.1) - Conditions — conflict of interest** Before recommending that an order be made under subsection (1), the Minister must be satisfied that +- **(a)** the proponent of the project, or any director, officer or significant shareholder of the proponent, has not been found to have committed a violation under the _Conflict of Interest Act_ and is not the subject of an ongoing proceeding in respect of a violation under that Act; and +- **(b)** every reporting public office holder, as defined in section 2 of that Act, who could be in a conflict of interest in relation to the proponent of the project has recused themselves under that Act to avoid the conflict. + +**(7) - Consultation** Before recommending that an order be made under any of subsections (1), (3) and (4), the Minister must consult with any other federal minister and any provincial or territorial government that the Minister considers appropriate and with Indigenous peoples whose rights recognized and affirmed by section 35 of the _Constitution Act, 1982_ may be adversely affected by the carrying out of the project to which the order relates. + +**(8) - _Statutory Instruments Act_** The _Statutory Instruments Act_ does not apply to an order made under subsection (1), (3) or (4). + +**(9) - Publication in _Canada Gazette_** An order made under subsection (1), (3) or (4), and the reasons for it, must be published in the _Canada Gazette_ as soon as feasible after it is made. + +**(10) - Publication in registry** Within 30 days after an order is made under subsection (1), details of the project in respect of which the order is made must be published in the registry established under section 5.1. + +### 5.1. Public registry + +**(1)** The Minister must establish and maintain a public registry of national interest projects that is made accessible to the public through the Internet. + +**(2) - Content of registry** The Minister must include in the registry in respect of each project +- **(a)** a detailed description of the project and the reasons why it is in the national interest; +- **(b)** the extent to which the project is expected to meet the outcomes set out in paragraphs 5(6)(a) to (d); +- **(c)** detailed cost estimates that do not include private sector commercially sensitive financial information; and +- **(d)** the estimated timelines for completion of the project. + +### 6. Deeming — favourable determinations, findings and opinions + +**(1)** Every determination and finding that has to be made and every opinion that has to be formed in order for an authorization to be granted in respect of a national interest project is deemed to be made or formed, as the case may be, in favour of permitting the project to be carried out in whole or in part. + +**(2) - Clarification** Subsection (1) does not exempt the proponent of a project from the requirement to take all measures that they are required to take, under an enactment, in respect of an authorization. + +**(3) - Limit** An authorization is not to be granted solely on the basis of the deeming provision in subsection (1). + +### 7. Duty to issue document + +**(1)** The Minister must issue to the proponent of a national interest project a document that is deemed to be each authorization that is specified in the document in respect of the project. + +**(2) - Conditions to issuing document** Before a document is issued under subsection (1), +- **(a)** the Minister must be satisfied that the proponent has taken all measures, including providing any information and paying any fees, that the proponent is required to take in respect of each authorization that is specified in the document; +- **(b)** the Minister must consult the minister who is responsible for the enactment under which each authorization is required with respect to the conditions that should be set out in the document; +- **(b.1)** the Minister must undertake a national security review for all state-owned or foreign investments from hostile countries in any national interest project; +- **(c)** Indigenous peoples whose rights recognized and affirmed by section 35 of the _Constitution Act, 1982_ may be adversely affected by the carrying out of the project to which the document relates must be consulted; and +- **(d)** the Minister must be satisfied that, with regard to any foreign investments in the project, all necessary measures have been taken to protect national security interests. + +**(2.1) - Participation of Indigenous peoples and report** For the purposes of consultations required under paragraph (2)(c), the Minister must ensure that a process is established that allows for the active and meaningful participation of the affected Indigenous peoples and that a report of the consultation process and results is made available to the public within 60 days after the day on which a document is issued under subsection (1). + +**(3) - Deeming** With respect to each authorization that is specified in it, the document is deemed to be the authorization issued under the enactment under which the authorization is required and to meet all of the requirements, under any enactment, that relate to the issuance of the authorization. + +**(4) - For greater certainty** For greater certainty, any powers that may be exercised and any duties and functions that may be performed in relation to an authorization that is specified in the document may be exercised or performed in relation to an authorization that is deemed to be issued in accordance with subsection (3). + +**(5) - Conditions** The document must set out the conditions that apply with respect to each authorization that is specified in it. The conditions set out in the document with respect to each authorization are deemed to be conditions imposed under the enactment under which the authorization is required. + +**(6) - Subject matter of conditions** The conditions set out in the document with respect to each authorization must be conditions that could have been imposed under the enactment under which the authorization is required, taking into account subsection 6(1). + +**(7) - _Statutory Instruments Act_** The _Statutory Instruments Act_ does not apply to the document. + +**(8) - Document available to public** The document, including any amendments to it, must be made available to the public in the manner determined by the Minister. + +**(9) - Documents and information to be made public** All documents and information used to issue the document must also be made public. + +**(10) - Expiry** If the national interest project has not been substantially started within five years of the issuance of the document, the document expires. + +### 8. Power to amend conditions + +**(1)** The Minister may amend any condition that is set out in a document issued under subsection 7(1). + +**(2) - Power to add authorizations and conditions** The Minister may amend a document issued under subsection 7(1) to specify additional authorizations and set out conditions in respect of each additional authorization, in accordance with section 7. + +**(3) - Consultation** Before amending a condition or document under subsection (1) or (2), the Minister must consult with +- **(a)** the minister who is responsible for the enactment under which each authorization to which the amendment relates is required; and +- **(b)** Indigenous peoples whose rights recognized and affirmed by section 35 of the _Constitution Act, 1982_ may be adversely affected by the amendment. + +**(4) - Limit** The Minister is not authorized to amend a condition under subsection (1) while Parliament is prorogued or dissolved or after the fifth anniversary of the day on which this section comes into force. + +### 8.1. Information available to public + +**(1)** When the Minister establishes the conditions for issuing the document that is deemed to be each authorization that is specified in the document in respect of a national interest project under section 7, the Minister must make public +- **(a)** all the conditions that apply to the project; +- **(b)** the full contents of the studies and impact assessments conducted regarding the project; +- **(c)** all the recommendations received from federal departments and agencies regarding the project; +- **(d)** in an accessible written document, the reasons some of the recommendations were not accepted; and +- **(e)** a description of the normal regulatory process that would have been followed if the project had not been designated as a national interest project. + +**(2) - Content — document under paragraph (1)(d)** The document referred to in paragraph (1)(d) must include +- **(a)** a comparative analysis of the conditions imposed and the recommendations received; +- **(b)** an assessment of the risks of disregarding the recommendations that were not accepted; and +- **(c)** any alternative mitigation measures implemented. + +**(3) - 30 days to make information public** The Minister must, not later than 30 days before the document referred to in section 7 is issued, make public the information set out in paragraphs (1)(a) to (e). + +**(4) - Report** The Minister must cause to be tabled a report containing the information set out in paragraphs (1)(a) to (e) in each House of Parliament and, at the request of 10 or more members of that House, must appear, to explain the Minister's decisions in establishing the conditions, before the committee of Parliament designated or established for that purpose. + +### 9. _Canada–Newfoundland and Labrador Atlantic Accord Implementation and Offshore Renewable Energy Management Act_ — subsection 7(1) + +**(1)** Before issuing a document under subsection 7(1) in respect of a project to which the _Canada–Newfoundland and Labrador Atlantic Accord Implementation and Offshore Renewable Energy Management Act_ applies, the Minister must consult with the Canada–Newfoundland and Labrador Offshore Energy Regulator with respect to the conditions that should be set out in the document. + +**(2) - Subsections 8(1) and (2)** Before amending a condition or document under subsection 8(1) or (2) in respect of a project to which the _Canada–Newfoundland and Labrador Atlantic Accord Implementation and Offshore Renewable Energy Management Act_ applies, the Minister must consult with the Canada–Newfoundland and Labrador Offshore Energy Regulator with respect to the amendment. + +### 10. _Canada–Nova Scotia Offshore Petroleum Resources Accord Implementation and Offshore Renewable Energy Management Act_ — subsection 7(1) + +**(1)** Before issuing a document under subsection 7(1) in respect of a project to which the _Canada–Nova Scotia Offshore Petroleum Resources Accord Implementation and Offshore Renewable Energy Management Act_ applies, the Minister must consult with the Canada–Nova Scotia Offshore Energy Regulator with respect to the conditions that should be set out in the document. + +**(2) - Subsections 8(1) and (2)** Before amending a condition or document under subsection 8(1) or (2) in respect of a project to which the _Canada–Nova Scotia Offshore Petroleum Resources Accord Implementation and Offshore Renewable Energy Management Act_ applies, the Minister must consult with the Canada–Nova Scotia Offshore Energy Regulator with respect to the amendment. + +# _Nuclear Safety and Control Act_ + +### 11. Consultation — subsection 7(1) + +Before issuing a document under subsection 7(1) in respect of a project to which the _Nuclear Safety and Control Act_ applies, the Minister must consult with the Canadian Nuclear Safety Commission with respect to the conditions that should be set out in the document. + +### 12. Consultation — subsections 8(1) and (2) + +Before amending a condition or document under subsection 8(1) or (2) in respect of a project to which the _Nuclear Safety and Control Act_ applies, the Minister must consult with the Canadian Nuclear Safety Commission with respect to the amendment. + +### 13. Limit — subsection 7(1) + +The Minister is not authorized to issue a document under subsection 7(1) in respect of a project to which the _Nuclear Safety and Control Act_ applies unless the Minister receives confirmation from the Canadian Nuclear Safety Commission that it is satisfied that issuing the document will not compromise the health or safety of persons, national security or the implementation of international obligations to which Canada has agreed. + +### 14. Limit — subsections 8(1) and (2) + +The Minister is not authorized to amend a condition or document under subsection 8(1) or (2) in respect of a project to which the _Nuclear Safety and Control Act_ applies unless the Minister receives confirmation from the Canadian Nuclear Safety Commission that it is satisfied that the amendment will not compromise the health or safety of persons, national security or the implementation of international obligations to which Canada has agreed. + +# _Canadian Energy Regulator Act_ + +### 15. Consultation — subsection 7(1) + +Before issuing a document under subsection 7(1) in respect of a project to which the _Canadian Energy Regulator Act_ applies, the Minister must consult with the Commission of the Canadian Energy Regulator with respect to the conditions that should be set out in the document. + +### 16. Consultation — subsections 8(1) and (2) + +Before amending a condition or document under subsection 8(1) or (2) in respect of a project to which the _Canadian Energy Regulator Act_ applies, the Minister must consult with the Commission of the Canadian Energy Regulator with respect to the amendment. + +### 17. Limit — subsection 7(1) + +The Minister is not authorized to issue a document under subsection 7(1) in respect of a project to which the _Canadian Energy Regulator Act_ applies unless the Minister receives confirmation from the Commission of the Canadian Energy Regulator that it is satisfied that issuing the document will not compromise the safety or security of persons or regulated facilities, as defined in section 2 of that Act. + +### 18. Limit — subsections 8(1) and (2) + +The Minister is not authorized to amend a condition or document under subsection 8(1) or (2) in respect of a project to which the _Canadian Energy Regulator Act_ applies unless the Minister receives confirmation from the Commission of the Canadian Energy Regulator that it is satisfied that the amendment will not compromise the safety or security of persons or regulated facilities, as defined in section 2 of that Act. + +# _Impact Assessment Act_ + +### 19. Non-application of certain provisions + +If a national interest project is also a designated project, as defined in section 2 of the _Impact Assessment Act_, sections 9 to 17 and subsections 18(3) to (6) of that Act do not apply in respect of the project and, for the purposes of section 18 of that Act, + +- **(a)** the Impact Assessment Agency of Canada is deemed to have decided that an impact assessment, as defined in section 2 of that Act, of the project is required; and +- **(b)** the time limit set out in subsection 18(1) of that Act does not apply in respect of the project. + +# Office + +### 20. Role + +An office may be established to coordinate the exercise of powers and the performance of duties and functions under this Act and the enactments with respect to projects that are in the national interest and to serve as a source of information and point of contact for the proponents of those projects. If an office is established, the Minister is responsible for it. + +# Amendment to Schedule 2 + +### 21. Add, amend or delete + +**(1)** Subject to subsection (2), the Governor in Council may, by order, amend Schedule 2 to add, amend or delete the name of an Act of Parliament or a regulation or the reference to a portion of an Act of Parliament or a regulation. + +**(2) - Exceptions** The Governor in Council is not authorized to amend Schedule 2 to add the name of any of the following Acts of Parliament or of any regulation made under any of those Acts, or a reference to a portion of any of those Acts or regulations: +- **(a)** the _Access to Information Act_; +- **(b)** the _Canada Elections Act_; +- **(b.1)** the _Canada Labour Code_; +- **(c)** the _Conflict of Interest Act_; +- **(d)** the _Criminal Code_; +- **(e)** the _Foreign Influence Transparency and Accountability Act_; +- **(f)** the _Investment Canada Act_; +- **(g)** the _Lobbying Act_; +- **(h)** the _Official Languages Act_; +- **(i)** the _Use of French in Federally Regulated Private Businesses Act_; +- **(j)** the _Indian Act_; +- **(k)** the _Auditor General Act_; +- **(l)** the _Extractive Sector Transparency Measures Act_; +- **(m)** the _Railway Safety Act_; +- **(n)** the _Trade Unions Act_; +- **(o)** the _Explosives Act_; and +- **(p)** the _Hazardous Products Act_. + +**(3) - Limit** The Governor in Council is not authorized to make an order under subsection (1) while Parliament is prorogued or dissolved or after the fifth anniversary of the day on which this section comes into force. + +# Regulations + +### 22. Regulations — enactment + +**(1)** The Governor in Council may, on the recommendation of the minister responsible for an enactment, make regulations +- **(a)** exempting one or more national interest projects from the application of any provision of that enactment or any provision of regulations made under that enactment; and +- **(b)** varying the application of any provision referred to in paragraph (a) in relation to one or more national interest projects. + +**(2) - Limit** The Governor in Council is not authorized to make regulations under subsection (1) while Parliament is prorogued or dissolved or after the fifth anniversary of the day on which this section comes into force. + +### 23. Regulations — this Act + +**(1)** The Governor in Council may make regulations generally for carrying out the purposes and provisions of this Act. + +**(2) - Limit** The Governor in Council is not authorized to make regulations under subsection (1) while Parliament is prorogued or dissolved or after the fifth anniversary of the day on which this section comes into force. + +# Annual Report + +### 23.1. Review: national interest project + +**(1)** Within 90 days after the end of each financial year, the Minister must cause an independent review to be conducted of the status of each national interest project that, in respect of each project, provides an assessment of the progress made on measurable outcomes, including in relation to timelines and budgets. + +**(2) - Report on review** The Minister must cause a report of the review to be tabled in each House of Parliament on any of the first 15 days on which that House is sitting after the review is completed. + +**(3) - Publication** The Minister must publish the report on an Internet site that is available to the public within 10 days after the day on which it has been tabled in both Houses of Parliament. + +# Review of Act + +### 24. Review by Parliamentary Review Committee + +**(1)** The Parliamentary Review Committee is to review the Governor in Council’s and the Minister’s exercise of their powers and performance of their duties and functions under this Act and to report to each House of Parliament the results of its review at least once every 180 days while Parliament is neither prorogued nor dissolved. + +**(2) - Review by Minister and report** Within five years after the day on which this Act comes into force, the Minister must complete a review of the provisions and operation of this Act and must cause a report on the review to be laid before the Parliamentary Review Committee and each House of Parliament. + +**(3) - Common good of Canada** The review is to be based on the common good of Canada, assured in part by the pursuit of the objectives set out in section 4 relating to shared jurisdiction, public safety, national and international security, the quality of the environment, public health, transparency, public participation and the protection of the rights of Indigenous peoples and linguistic communities. + +SCHEDULE*(Section 4)*SCHEDULE 1(Section 2 and subsections 5(1), (3) and (4))National Interest ProjectsColumn 1Column 2ItemName of ProjectDescription of ProjectSCHEDULE 2(Sections 2 and 21)PART 1Acts of ParliamentColumn 1Column 2ItemAct of ParliamentPortion1_Fisheries Act_2_International River Improvements Act_3_National Capital Act_4_Canadian Navigable Waters Act_5_Dominion Water Power Act_6_Migratory Birds Convention Act, 1994_7_Canada Transportation Act_section 988_Canada Marine Act_9_Canadian Environmental Protection Act, 1999_Division 3 of Part 710_Species at Risk Act_11_Canadian Energy Regulator Act_subsection 186(1)paragraph 262(1)(c)12_Impact Assessment Act_PART 2RegulationsColumn 1Column 2ItemRegulationsPortion1_Migratory Bird Sanctuary Regulations_2_Dominion Water Power Regulations_3_Wildlife Area Regulations_4_Marine Mammal Regulations_5_Port Authorities Operations Regulations_sections 25 and 276_Metal and Diamond Mining Effluent Regulations_7_Migratory Birds Regulations, 2022_ diff --git a/src/app/bills/evals/fixtures/industry-act-small-business.md b/src/app/bills/evals/fixtures/industry-act-small-business.md new file mode 100644 index 0000000..1457965 --- /dev/null +++ b/src/app/bills/evals/fixtures/industry-act-small-business.md @@ -0,0 +1,56 @@ +# C-291 — An Act to amend the Department of Industry Act (small businesses) +**Sponsor:** MS. MAY +**Introduced:** 2026-06-17 + +## Summary + +This enactment amends the _Department of Industry Act_ to require that a small business impact assessment be made for eve­ry legislative initiative that could have a significant effect on Canadian small businesses. + +## Preamble + +Whereas a Canadian economy that is prosperous, sustainable and resilient depends on healthy local economies and thriving small and medium-sized Canadian enterprises; + +Whereas small businesses employ more than 60% of all working Canadians and are responsible for more than 50% of Canada’s economic activity; + +Whereas small businesses are the lifeblood of small and rural communities and, according to the Canadian Federation of Independent Business, owners of small businesses are more likely to reduce their own salaries during periods of economic recession than reduce the number of their employees; + +And whereas the Government of Canada must protect and promote the role of small businesses in the Canadian economy by ensuring that the potential impacts of any new legislation on the success of small businesses are carefully considered; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Department of Industry Act— (1995, c. 1) + +### 1 + +Section 5 of the _Department of Industry Act_ is amended by striking out “and” at the end of paragraph (h), by adding “and” at the end of paragraph (i) and by adding the following after paragraph (i): + +- **(j)** protect and promote the role of small businesses within the Canadian economy. + +### 2 + +Section 6 of the Act is amended by striking out “and” at the end of paragraph (d), by adding “and” at the end of paragraph (e) and by adding the following after paragraph (e): + +- **(f)** conduct, in accordance with any regulations made under section 6.2, a small business impact assessment of legislative initiatives referred to in subsection 6.1(1) based on the following considerations: + - **(i)** the need to ensure that meaningful measures continue to be in place for facilitating the access of small businesses to financial assistance, + - **(ii)** the benefits of improving conditions for investment in and by small businesses, + - **(iii)** the need to ensure that the legislative initiative does not inhibit, directly or indirectly, the partic­ipation of small businesses in an efficient and competitive marketplace, and + - **(iv)** the recognition and application of the principle of net positive impact — as it pertains to the sustainability of small businesses — in determining whether or not the legislative initiative is beneficial to those businesses. + +### 3 + +The Act is amended by adding the following after section 6: + +### 6.1. Small business impact assessment + +**(1)** The Minister shall examine every bill introduced in or presented to either House of Parliament by a minister of the Crown and every regulation that is required to be tabled before either of those Houses in order to ascertain whether any of the provisions of those legislative initiatives could have a significant effect on Canadian small businesses, and, if applicable, the Minister shall cause to be tabled before that House at the first convenient opportunity a small business impact assessment. + +**(2) - _Canada Gazette_** If the legislative initiative is a proposed regulation, the small business impact assessment must be published with the proposed regulation in the _Canada Gazette_. + +### 6.2. Regulations + +The Governor in Council may, on the recommendation of the Minister, make regulations respecting + +- **(a)** the considerations to apply in deciding if a legislative initiative referred to in subsection 6.1(1) would reasonably be expected to have a significant effect on the successful operation of Canadian small businesses; +- **(b)** the process to be followed in preparing and submitting a small business impact assessment; +- **(c)** the matters that must be addressed in each small business impact assessment, including a summary description of the legislative initiative’s quantitative and qualitative costs and benefits as they can reasonably be expected to impact Canadian businesses with less than 50 employees; and +- **(d)** the meaning and application of the principle of net positive impact for the purposes of subparagraph 6(f)(iv). diff --git a/src/app/bills/evals/fixtures/labour-code-replacement-workers.md b/src/app/bills/evals/fixtures/labour-code-replacement-workers.md new file mode 100644 index 0000000..c861c88 --- /dev/null +++ b/src/app/bills/evals/fixtures/labour-code-replacement-workers.md @@ -0,0 +1,17 @@ +# C-284 — An Act to amend the Canada Labour Code (replacement workers) +**Sponsor:** MR. DAVIES *(Vancouver Kingsway)* +**Introduced:** 2026-06-08 + +## Summary + +This enactment amends the _Canada Labour Code_ to provide that, during a strike or lockout, an employer cannot use the services of any person who performs management functions or who is employed in a confidential capacity in matters related to industrial relations in another workplace to perform the duties of an employee who is in the bargaining unit on strike or locked out. + +His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# _Canada Labour Code_— (R.S., c. L-2) + +### 1 + +Paragraph 94(4)(c) of the _Canada Labour Code_ is replaced by the following: + +- **(c)** any employee or any person who performs management functions or who is employed in a confidential capacity in matters related to industrial relations whose normal workplace is a workplace other than that at which the strike or lockout is taking place or who was transferred to the workplace at which the strike or lockout is taking place after the day on which notice to bargain collectively is given; diff --git a/src/app/bills/evals/fixtures/national-bird-canada-jay.md b/src/app/bills/evals/fixtures/national-bird-canada-jay.md new file mode 100644 index 0000000..e4fd030 --- /dev/null +++ b/src/app/bills/evals/fixtures/national-bird-canada-jay.md @@ -0,0 +1,36 @@ +# S-221 — An Act to provide for the recognition of the Canada jay as the national bird of Canada +**Introduced:** 2026-06-11 + +## Summary + +This enactment recognizes the Canada jay as the national bird of Canada. + +## Preamble + +Whereas, unlike every Canadian province and territory and over 100 countries around the world, Canada does not have an official bird; + +Whereas the aptly named Canada jay (Perisoreus canadensis) is highly representative of Canada, being a year-round resident adapted to inhabiting very cold environments and breeding in every one of Canada’s ten provinces and three territories; + +Whereas the Canada jay was first known to Canada’s First Peoples as a curious and friendly bird with many names, including “grey jay”, “camp robber” and, in particular, *wîskicahk* in Cree, with this name likely later evolving into its popular English vernacular name, “whiskey jack”; + +Whereas the Canada jay was the first bird — and perhaps the only bird — to greet thousands of explorers, fur trappers, loggers, prospectors, settlers and First Peoples in their camps in the dead of winter; + +Whereas today the Canada jay delights not only Canadians, but visitors coming from all over the world to Canada’s extensive boreal forest and to its numerous scenic national and provincial parks; + +Whereas the choice of the Canada jay as the national bird of Canada has the enthusiastic support of naturalists, birdwatchers and ornithologists from across the country; + +Whereas the Canada jay is not an endangered, hunted or nuisance species and indeed displays admirable traits all Canadians can appreciate — intelligence, resourcefulness, trust and curiosity, and great energy despite its small size — as well as charming plumage with a modest colouring; + +Whereas the Canada jay is not already recognized as an official species for any country, province, territory or state; + +And whereas the Government of Canada wishes to recognize the unique place of the Canada jay in the cultural and natural history of Canada; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +### 1. Short title + +This Act may be cited as the *National Bird of Canada Act*. + +### 2. National bird + +The bird known as the Canada jay (Perisoreus canadensis) is hereby recognized and declared to be the national bird of Canada. diff --git a/src/app/bills/evals/fixtures/skilled-trades-labour-mobility.md b/src/app/bills/evals/fixtures/skilled-trades-labour-mobility.md new file mode 100644 index 0000000..74a627a --- /dev/null +++ b/src/app/bills/evals/fixtures/skilled-trades-labour-mobility.md @@ -0,0 +1,86 @@ +# C-266 — An Act to establish a national framework respecting skilled trades and labour mobility +**Sponsor:** MR. BAINS +**Introduced:** 2026-03-11 + +## Summary + +This enactment provides for the development of a national framework for the recognition of skilled trades, harmonization of credential recognition and mobility of skilled trades workers in Canada. + +## Preamble + +Whereas the movement of skilled labour across Canada is essential to the country’s economic development, productivity, infrastructure delivery and competitiveness; + +Whereas regulatory barriers, as well as inconsistencies between certification processes across provinces and territories, hinder the mobility of skilled trades workers and delay national development priorities, including housing construction, transportation infrastructure, energy projects and innovation capacity; + +Whereas Parliament recognizes the importance of modernizing, harmonizing and streamlining credential recognition for skilled trades across the country while respecting provincial and territorial jurisdiction over training, certification and labour market regulation; + +And whereas Parliament believes that strengthening recognition and portability in skilled trades will build Canada’s workforce capacity and contribute to a stronger and more resilient national economy; + +Now, therefore, His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + +# Short Title + +### 1. Short title + +This Act may be cited as the _National Framework on Skilled Trades and Labour Mobility Act_. + +# Interpretation + +### 2. Definitions + +The following definitions apply in this Act. + +Minister means the Minister of Employment and Social Development. (ministre)skilled trade means any trade that is recognized as a certified or otherwise regulated trade by any province, including any trade for which an endorsement can be obtained under the Interprovincial Standards Red Seal Program or a certification for Achievement in Business Competencies can be obtained under a Blue Seal program. (métier spécialisé) +# National Framework Respecting Skilled Trades and Labour Mobility + +### 3. Development of national framework + +**(1)** The Minister must develop a national framework respecting skilled trades and labour mobility to reduce barriers that prevent skilled trades workers from working interprovincially by modernizing, streamlining and harmonizing skilled trades certification processes and credentials across Canada. + +**(2) - Consultations** In developing the national framework, the Minister must hold consultations for at least nine months with relevant stakeholders, including representatives of +- **(a)** provincial governments; +- **(b)** skilled trades regulatory bodies; +- **(c)** industry and employer associations; +- **(d)** labour unions and apprenticeship organizations; +- **(e)** Indigenous governing bodies and Indigenous organizations; and +- **(f)** training and education institutions, such as colleges. + +**(3) - Contents** The national framework must +- **(a)** include a complete list of skilled trades across Canada; +- **(b)** compare, assess and map equivalencies between provincial standards and credentials for skilled trades; and +- **(c)** include measures to + - **(i)** harmonize those standards and credentials across Canada, + - **(ii)** reduce duplication and streamline regulatory processes in relation to obtaining skilled trade credentials or having them recognized from one province to another, + - **(iii)** support the modernization of certification processes to reflect new technologies, industry standards and emerging trades, + - **(iv)** support ongoing collaboration with provinces, Indigenous communities, unions, employers, training institutions and regulatory bodies, and + - **(v)** promote public awareness of and respect for skilled trades and their economic importance. + +# Reports to Parliament + +### 4. Tabling of national framework + +**(1)** Within one year after the day on which this Act comes into force, the Minister must prepare a report setting out the national framework and cause it to be tabled in each House of Parliament on any of the first 15 days on which that House is sitting after the report is completed. + +**(2) - Publication** The Minister must publish the report on the website of the Department of Employment and Social Development within 30 days after the day on which it is tabled in both Houses of Parliament. + +### 5. Progress reports + +**(1)** Within one year after the day on which the report is tabled under subsection 4(1), and every year after that, the Minister must prepare a progress report and cause it to be tabled in each House of Parliament on any of the first 15 days on which that House is sitting after the report is completed. + +**(2) - Contents** Each progress report must contain +- **(a)** information on the progress made towards + - **(i)** implementing the national framework, + - **(ii)** improving skilled trades mobility throughout Canada, and + - **(iii)** harmonizing, from one province to another, and modernizing skilled trades certification processes and credentials, as well as improving recognition for skilled trades; +- **(b)** outcomes of collaboration and consultations with provincial governments; and +- **(c)** a review of the effectiveness of the framework, including the Minister’s conclusions and recommendations in respect of any updates or revisions to the national framework that the Minister considers appropriate. + +**(3) - Publication** The Minister must publish each progress report on the website of the Department of Employment and Social Development within 30 days after the day on which it is tabled in both Houses of Parliament. + +# Review of the National Framework + +### 6. Parliamentary review + +**(1)** Within five years after the day on which this Act comes into force, a comprehensive review of the national framework must be undertaken by the committee of the Senate, of the House of Commons or of both Houses of Parliament that is designated or established for that purpose. + +**(2) - Report** The committee must, within a year after the review is undertaken — or within any further period that the Senate, the House of Commons or both Houses of Parliament, as the case may be, authorize — submit a report on the review to the appropriate House, or in the case of a committee of both Houses, to each House, that includes a statement of any changes to the national framework that the committee recommends. diff --git a/src/app/bills/evals/fixtures/supply-management-protection.md b/src/app/bills/evals/fixtures/supply-management-protection.md new file mode 100644 index 0000000..8cfbc65 --- /dev/null +++ b/src/app/bills/evals/fixtures/supply-management-protection.md @@ -0,0 +1,34 @@ +# C-202 — An Act to amend the Department of Foreign Affairs, Trade and Development Act (supply management) +**Introduced:** 2025-06-26 + + + + +## Summary + + + This enactment amends the _Department of Foreign Affairs, Trade and Development Act_ so that the Minister of Foreign Affairs cannot make certain commitments with respect to international trade regarding certain goods. + + + + + His Majesty, by and with the advice and consent of the Senate and House of Commons of Canada, enacts as follows: + + + + + +# Department of Foreign Affairs, Trade and Development Act— (2013, c. 33, s. 174) + + +### 1 + +Section 10 of the _Department of Foreign Affairs, Trade and Development Act_ is amended by adding the following after subsection (2): + + + + + + **(2.1) - Supply management** In exercising and performing the powers, duties and functions set out in subsection (2), the Minister must not make any commitment on behalf of the Government of Canada, by international trade treaty or agreement, that would have the effect of +- **(a)** increasing the tariff rate quota, within the meaning of subsection 2(1) of the *Customs Tariff*, applicable to dairy products, poultry or eggs; or +- **(b)** reducing the tariff applicable to those goods when they are imported in excess of the applicable tariff rate quota. diff --git a/src/app/bills/evals/lib/cache.ts b/src/app/bills/evals/lib/cache.ts new file mode 100644 index 0000000..7400db4 --- /dev/null +++ b/src/app/bills/evals/lib/cache.ts @@ -0,0 +1,57 @@ +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** + * Bump this to invalidate every cached response regardless of prompt/input. + * The prompt text is also part of the cache key (see runCached), so editing a + * prompt invalidates its cache automatically — this is for other changes (e.g. + * model, reasoning effort) that the key would otherwise miss. + */ +const VERSION = "1"; + +const CACHE_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", ".cache"); + +function sha256(value: string): string { + return createHash("sha256").update(value).digest("hex"); +} + +function keyFor(kind: string, input: string, promptText: string): string { + return sha256( + `${kind}:${VERSION}:${sha256(promptText)}:${sha256(input)}`, + ).slice(0, 32); +} + +export type CacheStats = { hits: number; misses: number }; + +/** + * Run `fn` (which performs the real, token-spending LLM call) unless a cached + * result exists for this (kind, input, prompt) triple. The full + * prompt→parse→normalize pipeline runs on a miss; re-runs read from disk. + * + * @param refresh when true, always call `fn` and overwrite the cache entry. + */ +export async function runCached( + kind: string, + input: string, + promptText: string, + fn: () => Promise, + opts: { refresh: boolean; stats?: CacheStats }, +): Promise<{ value: T; cached: boolean }> { + if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true }); + const path = join(CACHE_DIR, `${kind}-${keyFor(kind, input, promptText)}.json`); + + if (!opts.refresh && existsSync(path)) { + const value = JSON.parse(readFileSync(path, "utf8")) as T; + if (opts.stats) opts.stats.hits++; + return { value, cached: true }; + } + + const value = await fn(); + writeFileSync(path, JSON.stringify(value, null, 2)); + if (opts.stats) opts.stats.misses++; + return { value, cached: false }; +} + +export { CACHE_DIR }; diff --git a/src/app/bills/evals/lib/report.ts b/src/app/bills/evals/lib/report.ts new file mode 100644 index 0000000..d5b6ce4 --- /dev/null +++ b/src/app/bills/evals/lib/report.ts @@ -0,0 +1,163 @@ +import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { CACHE_DIR } from "./cache"; +import type { CheckResult } from "../checks/analysis-checks"; + +/* ------------------------------ tiny ansi ------------------------------ */ +const c = { + green: (s: string) => `\x1b[32m${s}\x1b[0m`, + red: (s: string) => `\x1b[31m${s}\x1b[0m`, + yellow: (s: string) => `\x1b[33m${s}\x1b[0m`, + dim: (s: string) => `\x1b[2m${s}\x1b[0m`, + bold: (s: string) => `\x1b[1m${s}\x1b[0m`, +}; + +export type FixtureReport = { + id: string; + name: string; + checks: CheckResult[]; + judgment?: { actual: string; expected?: string; match?: boolean }; + social?: { actual: boolean; expected: boolean; match: boolean }; + /** actual abstain-vs-grader agreement, for the cross-consistency flag */ + consistency?: { analysisAbstain: boolean; socialIssue: boolean; agree: boolean }; + cached: boolean; + /** true when produced via the no-key fallback path (--fallback), not the API */ + fallback?: boolean; +}; + +/* ---------------------------- confusion math --------------------------- */ +type Confusion = { tp: number; fp: number; tn: number; fn: number }; + +function socialMetrics(reports: FixtureReport[]) { + const m: Confusion = { tp: 0, fp: 0, tn: 0, fn: 0 }; + for (const r of reports) { + if (!r.social) continue; + const { actual, expected } = r.social; + if (expected && actual) m.tp++; + else if (!expected && actual) m.fp++; + else if (!expected && !actual) m.tn++; + else m.fn++; + } + const total = m.tp + m.fp + m.tn + m.fn; + const acc = total ? (m.tp + m.tn) / total : NaN; + const precision = m.tp + m.fp ? m.tp / (m.tp + m.fp) : NaN; + const recall = m.tp + m.fn ? m.tp / (m.tp + m.fn) : NaN; + return { ...m, total, acc, precision, recall }; +} + +function pct(x: number): string { + return Number.isNaN(x) ? "n/a" : `${(x * 100).toFixed(1)}%`; +} + +/* ------------------------------- printing ------------------------------ */ +export function printReport(reports: FixtureReport[]): { errorFailures: number } { + let errorFailures = 0; + + console.log(c.bold("\n=== Per-fixture checks ===\n")); + for (const r of reports) { + const errs = r.checks.filter((x) => x.severity === "error" && !x.pass); + const warns = r.checks.filter((x) => x.severity === "warn" && !x.pass); + errorFailures += errs.length; + + const status = errs.length === 0 ? c.green("PASS") : c.red("FAIL"); + const cacheTag = r.fallback + ? c.dim(" (fallback)") + : r.cached + ? c.dim(" (cached)") + : c.dim(" (live)"); + console.log(`${status} ${c.bold(r.id)} — ${r.name}${cacheTag}`); + + for (const e of errs) console.log(` ${c.red("✗")} ${e.name}: ${e.message}`); + for (const w of warns) + console.log(` ${c.yellow("⚠")} ${w.name}: ${w.message}`); + + if (r.judgment) { + const j = r.judgment; + if (j.expected == null) { + console.log(` ${c.dim("·")} judgment=${j.actual} ${c.dim("(no label)")}`); + } else { + const mark = j.match ? c.green("✓") : c.red("✗"); + console.log( + ` ${mark} judgment=${j.actual} expected=${j.expected}`, + ); + } + } + if (r.social) { + const mark = r.social.match ? c.green("✓") : c.red("✗"); + console.log( + ` ${mark} social_issue=${r.social.actual} expected=${r.social.expected}`, + ); + } + if (r.consistency && !r.consistency.agree) { + console.log( + ` ${c.yellow("⚠")} consistency: analysis abstain=${r.consistency.analysisAbstain} but grader social_issue=${r.consistency.socialIssue}`, + ); + } + } + + /* ---- structural summary ---- */ + const totalErrChecks = reports.reduce( + (n, r) => n + r.checks.filter((x) => x.severity === "error").length, + 0, + ); + console.log(c.bold("\n=== Structural checks ===\n")); + console.log( + ` ${totalErrChecks - errorFailures}/${totalErrChecks} passed` + + (errorFailures ? c.red(` (${errorFailures} failed)`) : c.green(" ✓")), + ); + + /* ---- judgment accuracy ---- */ + const judged = reports.filter((r) => r.judgment?.expected != null); + if (judged.length) { + const correct = judged.filter((r) => r.judgment!.match).length; + console.log(c.bold("\n=== Judgment accuracy (vs label) ===\n")); + console.log( + ` ${correct}/${judged.length} correct (${pct(correct / judged.length)})`, + ); + } + + /* ---- social-issue accuracy ---- */ + const hasSocial = reports.some((r) => r.social); + if (hasSocial) { + const s = socialMetrics(reports); + console.log(c.bold("\n=== Social-issue classifier ===\n")); + console.log( + ` accuracy=${pct(s.acc)} precision=${pct(s.precision)} recall=${pct(s.recall)}`, + ); + console.log( + c.dim(` confusion: TP=${s.tp} FP=${s.fp} TN=${s.tn} FN=${s.fn} (n=${s.total})`), + ); + } + + /* ---- consistency warnings ---- */ + const disagreements = reports.filter((r) => r.consistency && !r.consistency.agree); + if (disagreements.length) { + console.log(c.bold("\n=== Cross-consistency warnings ===\n")); + console.log( + c.yellow( + ` ${disagreements.length} fixture(s): summarizeBillText abstain disagrees with socialIssueGrader`, + ), + ); + console.log( + c.dim( + " (expected: summarizeBillText ignores its own is_social_issue field; abstain is model-driven)", + ), + ); + } + + return { errorFailures }; +} + +export function writeJsonReport(reports: FixtureReport[]): string { + if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true }); + const path = join(CACHE_DIR, "report.json"); + writeFileSync( + path, + JSON.stringify( + { generatedFrom: "eval:bills", social: socialMetrics(reports), reports }, + null, + 2, + ), + ); + return path; +} diff --git a/src/app/bills/evals/run.ts b/src/app/bills/evals/run.ts new file mode 100644 index 0000000..ea661a8 --- /dev/null +++ b/src/app/bills/evals/run.ts @@ -0,0 +1,148 @@ +/** + * Manual eval suite for the /bills LLM features. Run explicitly — it spends + * OpenAI tokens on a cache miss. + * + * pnpm eval:bills # run all fixtures (cached where possible) + * pnpm eval:bills --refresh # bypass cache, re-call the API + * pnpm eval:bills --only=social # only the social-issue classifier + * pnpm eval:bills --only=analysis # only summarizeBillText + * pnpm eval:bills --grep=tax # only fixtures whose id includes "tax" + * pnpm eval:bills --fallback # force no-key fallback path (0 tokens) + * + * Exit code is non-zero when any structural check fails. Accuracy and + * consistency are reported but never gate the run (they are probabilistic). + */ +import { summarizeBillText } from "@/app/bills/services/billApi"; +import { + socialIssueGrader, + SOCIAL_ISSUE_GRADER_PROMPT, +} from "@/app/bills/services/social-issue-grader"; +import { SUMMARY_AND_VOTE_PROMPT } from "@/app/bills/prompt/summary-and-vote-prompt"; +import { FIXTURES, loadFixtureText } from "./fixtures/bills"; +import { checkAnalysis } from "./checks/analysis-checks"; +import { runCached, type CacheStats } from "./lib/cache"; +import { printReport, writeJsonReport, type FixtureReport } from "./lib/report"; + +function parseArgs(argv: string[]) { + const get = (name: string) => + argv.find((a) => a.startsWith(`--${name}=`))?.split("=")[1]; + return { + refresh: argv.includes("--refresh"), + fallback: argv.includes("--fallback"), + only: get("only") as "social" | "analysis" | undefined, + grep: get("grep"), + }; +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + + if (args.fallback) { + // Force every internal `if (!OPENAI_API_KEY)` fallback branch. + delete process.env.OPENAI_API_KEY; + console.log("Running in --fallback mode: no API calls, exercising fallbacks."); + } else if (!process.env.OPENAI_API_KEY) { + console.log( + "\nOPENAI_API_KEY is not set. The functions will return fallback output.", + ); + console.log( + "Set the key for a real eval, or pass --fallback to exercise fallbacks intentionally.\n", + ); + } + + const runAnalysis = args.only !== "social"; + const runSocial = args.only !== "analysis"; + + const fixtures = FIXTURES.filter( + (f) => !args.grep || f.id.includes(args.grep), + ); + if (fixtures.length === 0) { + console.error(`No fixtures match --grep=${args.grep}`); + process.exit(1); + } + + const stats: CacheStats = { hits: 0, misses: 0 }; + const reports: FixtureReport[] = []; + + for (const f of fixtures) { + const text = loadFixtureText(f); + const report: FixtureReport = { + id: f.id, + name: f.name, + checks: [], + cached: false, + fallback: args.fallback, + }; + let analysisAbstain: boolean | undefined; + let socialResult: boolean | undefined; + + if (runAnalysis) { + // In fallback mode the result is deterministic and free — skip the cache. + const { value: analysis, cached } = args.fallback + ? { value: await summarizeBillText(text), cached: false } + : await runCached( + "analysis", + text, + SUMMARY_AND_VOTE_PROMPT, + () => summarizeBillText(text), + { refresh: args.refresh, stats }, + ); + report.cached = cached; + report.checks = checkAnalysis(analysis); + analysisAbstain = analysis.final_judgment === "abstain"; + report.judgment = { + actual: analysis.final_judgment, + expected: f.expected.finalJudgment, + match: f.expected.finalJudgment + ? analysis.final_judgment === f.expected.finalJudgment + : undefined, + }; + } + + if (runSocial) { + const { value: social } = args.fallback + ? { value: await socialIssueGrader(text) } + : await runCached( + "social", + text, + SOCIAL_ISSUE_GRADER_PROMPT, + () => socialIssueGrader(text), + { refresh: args.refresh, stats }, + ); + socialResult = social; + report.social = { + actual: social, + expected: f.expected.isSocialIssue, + match: social === f.expected.isSocialIssue, + }; + } + + if (analysisAbstain !== undefined && socialResult !== undefined) { + report.consistency = { + analysisAbstain, + socialIssue: socialResult, + agree: analysisAbstain === socialResult, + }; + } + + reports.push(report); + } + + const { errorFailures } = printReport(reports); + const jsonPath = writeJsonReport(reports); + + console.log( + `\ncache: ${stats.hits} hit(s), ${stats.misses} miss(es). report: ${jsonPath}`, + ); + + if (errorFailures > 0) { + console.log(`\n${errorFailures} structural check(s) failed.`); + process.exit(1); + } + console.log("\nAll structural checks passed."); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app/bills/prompt/summary-and-vote-prompt.ts b/src/app/bills/prompt/summary-and-vote-prompt.ts index 7a3ad9a..8a5353d 100644 --- a/src/app/bills/prompt/summary-and-vote-prompt.ts +++ b/src/app/bills/prompt/summary-and-vote-prompt.ts @@ -1,8 +1,8 @@ export const TENETS = { 1: "Canada should aim to be the world's most prosperous country.", 2: "Promote economic freedom, ambition, and breaking from bureaucratic inertia (reduce red tape).", - 3: "Drive national productivity and global competitiveness.", - 4: "Grow exports of Canadian products and resources.", + 3: "Drive national productivity and global competitiveness, including removing interprovincial trade barriers and improving labour mobility (one country, one market).", + 4: "Grow exports of Canadian products and resources, and move up the value chain by processing resources domestically rather than exporting them raw.", 5: "Encourage investment, innovation, and resource development.", 6: "Deliver better public services at lower cost (government efficiency).", 7: "Reform taxes to incentivize work, risk-taking, and innovation.", @@ -46,6 +46,30 @@ You are analyzing Canadian legislation. You must assess whether the bill aligns ${SOCIAL_ISSUE_GRADING} +## Judgment Signals + + Apply these only after ruling out a social issue (social issues → abstain). + Weigh a bill by its primary economic effect on prosperity and productivity. + + Strong ALIGN signals ("yes"): + - Removing interprovincial/internal trade barriers, or mutual recognition of credentials, goods, or services. + - Improving labour mobility across provinces. + - Reducing regulatory burden, permitting time, or business-formation friction (e.g., regulatory sunset clauses, single-window approvals). + - Streamlining or fast-tracking major infrastructure, energy, or resource projects. + - Lowering taxes on productive investment, reinvestment, or entrepreneurship. + - Expanding resource/energy development or domestic value-added processing of resources. + + Strong CONFLICT signals ("no"): + - Adding red tape, new mandatory processes, or reporting burdens on businesses or individuals without a net reduction elsewhere. + - Protectionism that entrenches barriers to trade or shields sectors from competition (e.g., supply-management carve-outs). + - Raising taxes on investment, capital, or entrepreneurship. + - Large new redistributive or spending programs justified on redistribution rather than growth/productivity (wealth must be created before it can be redistributed). + - Restricting labour-market flexibility or resource/energy development. + + When a bill mixes align and conflict elements, choose the dominant direction by + primary economic effect. Use neutral/unclear only when genuinely balanced or + purely administrative. + ## General Guidelines For general guidelines: diff --git a/src/app/bills/services/slack-notifier.ts b/src/app/bills/services/slack-notifier.ts new file mode 100644 index 0000000..67d3146 --- /dev/null +++ b/src/app/bills/services/slack-notifier.ts @@ -0,0 +1,130 @@ +import type { BillAnalysis } from "@/app/bills/services/billApi"; +import { env } from "@/app/bills/env"; + +/** + * Posts a freshly generated bill analysis to Slack (#builder-mp) via an + * incoming webhook. The webhook URL is pinned to the channel when it is + * created in Slack, so no channel is specified here. + * + * Fire-and-forget: never throws, so a Slack outage can't break bill + * processing. No-op when BILLS_SLACK_WEBHOOK_URL is unset. + */ + +const JUDGMENT_DISPLAY: Record< + BillAnalysis["final_judgment"], + { emoji: string; label: string } +> = { + yes: { emoji: "✅", label: "Vote Yes" }, + no: { emoji: "❌", label: "Vote No" }, + abstain: { emoji: "⚪", label: "Abstain" }, +}; + +const ALIGNMENT_EMOJI: Record = { + aligns: "✅", + conflicts: "❌", + neutral: "➖", +}; + +// Slack section blocks cap mrkdwn text at 3000 chars. +function truncate(text: string, max: number): string { + return text.length <= max ? text : `${text.slice(0, max - 1)}…`; +} + +// The analysis summary is standard markdown; Slack uses mrkdwn. +function toSlackMrkdwn(markdown: string): string { + return markdown + .replace(/\*\*(.+?)\*\*/g, "*$1*") + .replace(/^[-*] /gm, "• "); +} + +export async function notifyNewBillAnalysis(params: { + billId: string; + title: string; + shortTitle?: string; + analysis: BillAnalysis; +}): Promise { + const webhookUrl = env.BILLS_SLACK_WEBHOOK_URL; + if (!webhookUrl) return; + + const { billId, title, shortTitle, analysis } = params; + + try { + const judgment = + JUDGMENT_DISPLAY[analysis.final_judgment] ?? JUDGMENT_DISPLAY.abstain; + const displayTitle = shortTitle || analysis.short_title || title; + const billUrl = env.NEXT_PUBLIC_APP_URL + ? `${env.NEXT_PUBLIC_APP_URL.replace(/\/$/, "")}/bills/${billId}` + : undefined; + + const tenetLines = (analysis.tenet_evaluations ?? []).map((tenet) => { + const emoji = ALIGNMENT_EMOJI[tenet.alignment] ?? "➖"; + const explanation = tenet.explanation + ? ` — ${truncate(tenet.explanation, 200)}` + : ""; + return `${emoji} *${tenet.title}*${explanation}`; + }); + + const blocks: unknown[] = [ + { + type: "header", + text: { + type: "plain_text", + text: truncate(`New analysis: ${billId} — ${displayTitle}`, 150), + emoji: true, + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: `*Overall vote:* ${judgment.emoji} ${judgment.label}`, + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: truncate( + `*Summary*\n${toSlackMrkdwn(analysis.summary || "_No summary available._")}`, + 3000, + ), + }, + }, + { + type: "section", + text: { + type: "mrkdwn", + text: truncate(`*Tenet evaluations*\n${tenetLines.join("\n")}`, 3000), + }, + }, + ]; + + if (billUrl) { + blocks.push({ + type: "context", + elements: [{ type: "mrkdwn", text: `<${billUrl}|View full analysis>` }], + }); + } + + const response = await fetch(webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + // Fallback text for notifications and clients that don't render blocks. + text: `New analysis: ${billId} — ${displayTitle} (${judgment.label})`, + blocks, + }), + }); + + if (!response.ok) { + const body = await response.text().catch(() => ""); + console.error("Slack notification failed:", { + billId, + status: response.status, + body: body.slice(0, 500), + }); + } + } catch (error) { + console.error("Error sending Slack notification:", { billId, error }); + } +} diff --git a/src/app/bills/services/social-issue-grader.ts b/src/app/bills/services/social-issue-grader.ts index b75e80e..c3939b1 100644 --- a/src/app/bills/services/social-issue-grader.ts +++ b/src/app/bills/services/social-issue-grader.ts @@ -1,6 +1,6 @@ import { OpenAI } from "openai"; -const SOCIAL_ISSUE_GRADER_PROMPT = ` +export const SOCIAL_ISSUE_GRADER_PROMPT = ` You are a policy classifier. Your sole task is to decide whether a bill is primarily a social issue. Definition — Social Issue (for this classifier): diff --git a/src/app/bills/utils/billConverters.ts b/src/app/bills/utils/billConverters.ts index 54056a5..16cb121 100644 --- a/src/app/bills/utils/billConverters.ts +++ b/src/app/bills/utils/billConverters.ts @@ -7,6 +7,7 @@ import { type BillAnalysis, } from "@/app/bills/services/billApi"; import { socialIssueGrader } from "@/app/bills/services/social-issue-grader"; +import { notifyNewBillAnalysis } from "@/app/bills/services/slack-notifier"; // Unified bill data structure export interface UnifiedBill { @@ -233,9 +234,11 @@ export async function fromCivicsProjectApiBill( // Continue with regeneration if DB check fails } + let generatedNewAnalysis = false; if (!analysis?.rationale && billMarkdown) { console.log(`Regenerating analysis for ${bill.billID} (source changed)`); analysis = await summarizeBillText(billMarkdown); + generatedNewAnalysis = true; } // Only classify if missing (new bill or classification absent). Avoid calling otherwise. @@ -262,6 +265,15 @@ export async function fromCivicsProjectApiBill( isSocialIssue: isSocialIssueFinal, }); + if (generatedNewAnalysis) { + await notifyNewBillAnalysis({ + billId: bill.billID, + title: bill.title, + shortTitle: bill.shortTitle, + analysis, + }); + } + return { billId: bill.billID, title: bill.title,