CIU/PSAC union corpus: by-laws suite, PSAC Constitution, VSSAs, guides, 15 decisions; weekly watch extended
Browse filesNew doc_type 'union' (bannered: bargaining-agent documents, not employer
policy): CIU National By-Laws (EN+FR), CIU Regulations, Branch Rules, Local
Modifications, Branch Elections Kit, PSAC Constitution (Nov 2024), the
grievance/EE/transition guides, and all 25 published site VSSAs -- 635
chunks via canlex.ciu. The union's Important Decisions list ingested in
full: 12 FPSLREB/PSLRB decisions (Bartlett scheduling through Guay-Bassett)
plus Jane Doe 2018 FCA 183, Marentette 2024 FC 676 and Fraser 2020 SCC 28
(124 decisions total). refresh.py check_union() diffs upstream
Last-Modified for all 35 union documents; PDI and IRB-guideline currency
rows added; legislation drift advice now includes the French twins; the
weekly SKILL.md rewritten to match (and its wrong error-semantics line
fixed). 40,994 chunks / 44,302 vectors. Gate: legacy 0.77/0.94/0.98/0.99
MRR 0.85; union slice 1.00 Hit@5; holdout 1.00 Hit@3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README.md +6 -0
- RUNBOOK.md +1 -0
- canlex/caselaw.py +46 -0
- canlex/ciu.py +239 -0
- canlex/refresh.py +68 -1
- canlex/server.py +33 -4
- data/eval/questions.json +48 -0
- data/processed/caselaw.json +2 -2
- data/processed/ciu.json +0 -0
- data/processed/embeddings.npz +2 -2
- webapp/app.py +1 -1
|
@@ -39,6 +39,12 @@ The server also publishes an MCP prompt (`us_record_screening`, the guided
|
|
| 39 |
three-step screening flow) and resources (the corpus inventory and the
|
| 40 |
conviction/equivalency methodology texts) that any MCP client can use.
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
Retrieval runs fully locally inside the container and needs no API key. The
|
| 43 |
optional case citator reads a CanLII key from the `CANLII_API_KEY` environment
|
| 44 |
variable. A weekly scheduled staleness check watches upstream sources for
|
|
|
|
| 39 |
three-step screening flow) and resources (the corpus inventory and the
|
| 40 |
conviction/equivalency methodology texts) that any MCP client can use.
|
| 41 |
|
| 42 |
+
The labour side now carries the full union stack alongside the FB collective
|
| 43 |
+
agreement: the CIU National By-Laws (EN/FR), CIU Regulations, Branch Rules
|
| 44 |
+
and local modifications, the Branch Elections Kit, the PSAC Constitution,
|
| 45 |
+
the union's member guides, and every published site VSSA — all bannered as
|
| 46 |
+
bargaining-agent documents, distinct from employer policy.
|
| 47 |
+
|
| 48 |
Retrieval runs fully locally inside the container and needs no API key. The
|
| 49 |
optional case citator reads a CanLII key from the `CANLII_API_KEY` environment
|
| 50 |
variable. A weekly scheduled staleness check watches upstream sources for
|
|
@@ -10,6 +10,7 @@ Order matters — chunks feed embeddings feed the eval:
|
|
| 10 |
py -m canlex.ingest [--force] [CODES] # Justice Laws XML -> per-Act JSON (incl. Schedules)
|
| 11 |
py -m canlex.ingest --fr # parallel French corpus (same schema; SOR->DORS URLs)
|
| 12 |
py -m canlex.pdi # scoped IRCC PDI pages (PR card / Tran)
|
|
|
|
| 13 |
py -m canlex.dmemo # CBSA D-Memoranda
|
| 14 |
py -m canlex.enf # ENF operational manuals (canada.ca PDFs)
|
| 15 |
py -m canlex.amps # AMPS Master Penalty Document
|
|
|
|
| 10 |
py -m canlex.ingest [--force] [CODES] # Justice Laws XML -> per-Act JSON (incl. Schedules)
|
| 11 |
py -m canlex.ingest --fr # parallel French corpus (same schema; SOR->DORS URLs)
|
| 12 |
py -m canlex.pdi # scoped IRCC PDI pages (PR card / Tran)
|
| 13 |
+
py -m canlex.ciu # CIU/PSAC union documents + site VSSAs
|
| 14 |
py -m canlex.dmemo # CBSA D-Memoranda
|
| 15 |
py -m canlex.enf # ENF operational manuals (canada.ca PDFs)
|
| 16 |
py -m canlex.amps # AMPS Master Penalty Document
|
|
@@ -70,6 +70,10 @@ CASES = [
|
|
| 70 |
{"court": "scc", "id": 20081, "short": "Mason",
|
| 71 |
"topic": "Inadmissibility under IRPA s. 34(1)(e) for acts of violence "
|
| 72 |
"endangering safety in Canada; reasonableness review"},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
{"court": "scc", "id": 16146, "short": "Igloo Vikski",
|
| 74 |
"topic": "Customs tariff classification methodology; how the General "
|
| 75 |
"Rules for the Interpretation of the Harmonized System apply "
|
|
@@ -205,6 +209,14 @@ CASES = [
|
|
| 205 |
"reliability of the evidence before committal"},
|
| 206 |
|
| 207 |
# --- Federal Court of Appeal ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
{"court": "fca", "id": 33278, "short": "Cha",
|
| 209 |
"topic": "Scope of s. 44(1) inadmissibility reports and Minister's "
|
| 210 |
"delegate removal orders at the port of entry; officers and "
|
|
@@ -331,6 +343,40 @@ CASES = [
|
|
| 331 |
"where only part is shown to be of legitimate origin"},
|
| 332 |
|
| 333 |
# --- Federal Public Sector Labour Relations and Employment Board ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
{"court": "fpslreb", "id": 520990, "short": "Menzies",
|
| 335 |
"topic": "Progressive discipline of a CBSA border services officer; the "
|
| 336 |
"lock-step approach to discipline rejected"},
|
|
|
|
| 70 |
{"court": "scc", "id": 20081, "short": "Mason",
|
| 71 |
"topic": "Inadmissibility under IRPA s. 34(1)(e) for acts of violence "
|
| 72 |
"endangering safety in Canada; reasonableness review"},
|
| 73 |
+
{"court": "scc", "id": 18510, "short": "Fraser",
|
| 74 |
+
"topic": "Adverse-effect discrimination under Charter s. 15(1); the "
|
| 75 |
+
"RCMP job-sharing pension rules' disproportionate impact on "
|
| 76 |
+
"women; substantive equality methodology"},
|
| 77 |
{"court": "scc", "id": 16146, "short": "Igloo Vikski",
|
| 78 |
"topic": "Customs tariff classification methodology; how the General "
|
| 79 |
"Rules for the Interpretation of the Harmonized System apply "
|
|
|
|
| 209 |
"reliability of the evidence before committal"},
|
| 210 |
|
| 211 |
# --- Federal Court of Appeal ---
|
| 212 |
+
{"court": "fca", "id": 345651, "short": "Jane Doe",
|
| 213 |
+
"topic": "Judicial review of 2017 PSLREB 55; compensation for a "
|
| 214 |
+
"victim of workplace sexual assault in the federal public "
|
| 215 |
+
"service; remedial authority of the Board"},
|
| 216 |
+
{"court": "fc", "id": 525537, "short": "Marentette",
|
| 217 |
+
"topic": "Workplace violence and harassment investigations under the "
|
| 218 |
+
"Canada Labour Code; procedural fairness owed to the "
|
| 219 |
+
"complainant in the investigation process"},
|
| 220 |
{"court": "fca", "id": 33278, "short": "Cha",
|
| 221 |
"topic": "Scope of s. 44(1) inadmissibility reports and Minister's "
|
| 222 |
"delegate removal orders at the port of entry; officers and "
|
|
|
|
| 343 |
"where only part is shown to be of legitimate origin"},
|
| 344 |
|
| 345 |
# --- Federal Public Sector Labour Relations and Employment Board ---
|
| 346 |
+
# The CIU's published "Important Decisions" list (ciu-sdi.ca), ingested
|
| 347 |
+
# 2026-07-23; topics adapted from the union's own subject lines.
|
| 348 |
+
{"court": "fpslreb", "id": 358837, "short": "Bartlett",
|
| 349 |
+
"topic": "Hours of work and variable shift scheduling under the FB "
|
| 350 |
+
"collective agreement (Border Services officers)"},
|
| 351 |
+
{"court": "fpslreb", "id": 359582, "short": "Legros",
|
| 352 |
+
"topic": "Age discrimination grievance against the CBSA"},
|
| 353 |
+
{"court": "fpslreb", "id": 359605, "short": "Bétournay",
|
| 354 |
+
"topic": "Breach of trust, disguised disciplinary action and "
|
| 355 |
+
"termination (CRA)"},
|
| 356 |
+
{"court": "fpslreb", "id": 359635, "short": "Frezza",
|
| 357 |
+
"topic": "Bad faith and disguised discipline in the federal public "
|
| 358 |
+
"service (DND)"},
|
| 359 |
+
{"court": "fpslreb", "id": 360491, "short": "PSAC v TB (CBSA) 2018-66",
|
| 360 |
+
"topic": "Policy grievance: interpretation of the FB collective "
|
| 361 |
+
"agreement (PSAC v Treasury Board / CBSA)"},
|
| 362 |
+
{"court": "fpslreb", "id": 361700, "short": "PSAC v TB (CBSA) 2018-82",
|
| 363 |
+
"topic": "Policy grievance: interpretation of the FB collective "
|
| 364 |
+
"agreement (PSAC v Treasury Board / CBSA)"},
|
| 365 |
+
{"court": "fpslreb", "id": 361701, "short": "Michaud",
|
| 366 |
+
"topic": "Suspension grievance in the federal public service (CRA)"},
|
| 367 |
+
{"court": "fpslreb", "id": 407654, "short": "Hyslop",
|
| 368 |
+
"topic": "Suspension grievance of a CBSA employee"},
|
| 369 |
+
{"court": "fpslreb", "id": 407686, "short": "Girard",
|
| 370 |
+
"topic": "Suspension and the effective date of termination (CRA)"},
|
| 371 |
+
{"court": "fpslreb", "id": 420718, "short": "Edwards",
|
| 372 |
+
"topic": "Sick leave, personal-needs leave and disability "
|
| 373 |
+
"discrimination under the FB collective agreement (CBSA)"},
|
| 374 |
+
{"court": "fpslreb", "id": 420308, "short": "Touchette",
|
| 375 |
+
"topic": "Proportionality of discipline; suspension of a CBSA "
|
| 376 |
+
"employee reduced"},
|
| 377 |
+
{"court": "fpslreb", "id": 521306, "short": "Guay-Bassett",
|
| 378 |
+
"topic": "Scheduling, designated paid holidays and sick leave under "
|
| 379 |
+
"the FB collective agreement (CBSA)"},
|
| 380 |
{"court": "fpslreb", "id": 520990, "short": "Menzies",
|
| 381 |
"topic": "Progressive discipline of a CBSA border services officer; the "
|
| 382 |
"lock-step approach to discipline rejected"},
|
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ingest CIU and PSAC union documents (doc_type='union').
|
| 2 |
+
|
| 3 |
+
The Customs and Immigration Union is the PSAC component representing the FB
|
| 4 |
+
bargaining unit; its governance instruments and published reference material
|
| 5 |
+
are the union-side counterpart to the FB collective agreement already in the
|
| 6 |
+
corpus. Ingested here, all as public documents from ciu-sdi.ca / psacunion.ca:
|
| 7 |
+
|
| 8 |
+
- the CIU National By-Laws (EN + FR), CIU Regulations, Branch Rules and the
|
| 9 |
+
Local Modifications to Branch Rules -- the union's internal law;
|
| 10 |
+
- the PSAC Constitution and Regulations (the parent instrument the by-laws
|
| 11 |
+
operate under: dues, discipline and appeals bottom out there);
|
| 12 |
+
- the Branch Elections Kit and the members' guides (grievance handling,
|
| 13 |
+
employment equity, workers in transition);
|
| 14 |
+
- every site-specific Variable Shift Schedule Agreement (VSSA) the union
|
| 15 |
+
publishes -- the locally negotiated schedule grids for ports of entry.
|
| 16 |
+
|
| 17 |
+
None of this is employer policy or part of the collective agreement itself;
|
| 18 |
+
_format_section banners doc_type='union' accordingly.
|
| 19 |
+
|
| 20 |
+
python -m canlex.ciu
|
| 21 |
+
"""
|
| 22 |
+
import json
|
| 23 |
+
import re
|
| 24 |
+
import urllib.request
|
| 25 |
+
|
| 26 |
+
from ._common import fetch_cached, norm_ws as _norm, split_lines, uniquify_ids
|
| 27 |
+
from .config import PROCESSED_DIR, RAW_DIR
|
| 28 |
+
|
| 29 |
+
RAW = RAW_DIR / "ciu"
|
| 30 |
+
VSSA_PAGE = "https://ciu-sdi.ca/en/vssa/"
|
| 31 |
+
_DOCS_BASE = "https://www.ciu-sdi.ca/wp-content/uploads/documents/"
|
| 32 |
+
|
| 33 |
+
# (code, act_short, act_name, url, lang)
|
| 34 |
+
DOCS = [
|
| 35 |
+
("ciu-bylaws", "CIU By-Laws",
|
| 36 |
+
"CIU National By-Laws (adopted by CIU National Conventions)",
|
| 37 |
+
_DOCS_BASE + "bylaws_en.pdf", "en"),
|
| 38 |
+
("ciu-bylaws", "CIU By-Laws",
|
| 39 |
+
"Règlements nationaux du SDI (adoptés par les congrès)",
|
| 40 |
+
_DOCS_BASE + "bylaws_fr.pdf", "fr"),
|
| 41 |
+
("ciu-regs", "CIU Regulations",
|
| 42 |
+
"CIU Regulations (adopted by the CIU National Board of Directors)",
|
| 43 |
+
_DOCS_BASE + "ciu-regs-en.pdf", "en"),
|
| 44 |
+
("ciu-branch-rules", "CIU Branch Rules",
|
| 45 |
+
"CIU Branch Rules (adopted by the CIU National Board of Directors)",
|
| 46 |
+
_DOCS_BASE + "branch-rules-en.pdf", "en"),
|
| 47 |
+
("ciu-local-mods", "CIU Local Modifications",
|
| 48 |
+
"Local Modifications to CIU Branch Rules",
|
| 49 |
+
_DOCS_BASE + "local_mod_branch_rules_en.pdf", "en"),
|
| 50 |
+
("ciu-elections-kit", "CIU Elections Kit",
|
| 51 |
+
"CIU Branch Elections Kit",
|
| 52 |
+
"https://www.ciu-sdi.ca/wp-content/uploads/elections/2023/EN/"
|
| 53 |
+
"elections-kit.pdf", "en"),
|
| 54 |
+
("psac-constitution", "PSAC Constitution",
|
| 55 |
+
"PSAC Constitution and Regulations (November 2024 edition)",
|
| 56 |
+
"https://psacunion.ca/sites/psac/files/"
|
| 57 |
+
"psac.constitution.november_2024_edition.eng_v002.pdf", "en"),
|
| 58 |
+
("ciu-guide-grievance", "CIU Grievance Guide",
|
| 59 |
+
"CIU Guide to Grievance Handling",
|
| 60 |
+
"https://ciu-sdi.ca/wp-content/uploads/2012/09/"
|
| 61 |
+
"guide_to_grievance_handling-e.pdf", "en"),
|
| 62 |
+
("ciu-guide-ee", "CIU EE Toolkit",
|
| 63 |
+
"CIU Employment Equity Toolkit",
|
| 64 |
+
"https://ciu-sdi.ca/wp-content/uploads/2012/09/"
|
| 65 |
+
"EMPLOYMENT-EQUITY-Tool-Kit-e.pdf", "en"),
|
| 66 |
+
("ciu-guide-transition", "CIU Transition Guide",
|
| 67 |
+
"Workers in Transition: a practical guide about gender transition",
|
| 68 |
+
"https://ciu-sdi.ca/wp-content/uploads/2012/09/"
|
| 69 |
+
"workers_in_transition_guide_-_en_-_web.pdf", "en"),
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
# Governance headings: "BY-LAW 5", "REGULATION 12", "RULE 3", "ARTICLE 7",
|
| 73 |
+
# "SECTION 25", "PART B" -- at line start, optionally followed by a title.
|
| 74 |
+
_HEADING = re.compile(
|
| 75 |
+
r"^(BY-?LAW|R[EÈ]GLEMENT|REGULATION|RULE|ARTICLE|SECTION|PART|ANNEX(?:E)?"
|
| 76 |
+
r"|APPENDIX)\s+([0-9]+[0-9A-Za-z.\-]*|[A-Z])\b\s*[-–—:]?\s*(.{0,80})",
|
| 77 |
+
re.IGNORECASE)
|
| 78 |
+
_DATE = re.compile(r"\b(20\d{2}|19\d{2})\b")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _pdf_pages(path):
|
| 82 |
+
from pypdf import PdfReader
|
| 83 |
+
pages = []
|
| 84 |
+
for page in PdfReader(str(path)).pages:
|
| 85 |
+
try:
|
| 86 |
+
pages.append(page.extract_text() or "")
|
| 87 |
+
except Exception:
|
| 88 |
+
pages.append("")
|
| 89 |
+
return pages
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _currency(pages):
|
| 93 |
+
"""Newest adoption/amendment year on the cover pages."""
|
| 94 |
+
years = _DATE.findall("\n".join(pages[:3]))
|
| 95 |
+
return max(years) if years else ""
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _doc_chunks(code, short, name, url, lang, pages):
|
| 99 |
+
text = "\n".join(pages)
|
| 100 |
+
lines = text.split("\n")
|
| 101 |
+
currency = _currency(pages)
|
| 102 |
+
marks = []
|
| 103 |
+
for i, l in enumerate(lines):
|
| 104 |
+
m = _HEADING.match(l.strip())
|
| 105 |
+
if m:
|
| 106 |
+
marks.append((i, f"{m.group(1).title()} {m.group(2)}",
|
| 107 |
+
_norm(m.group(3)) or ""))
|
| 108 |
+
segments = []
|
| 109 |
+
if len(marks) >= 6:
|
| 110 |
+
for j, (i, label, title) in enumerate(marks):
|
| 111 |
+
end = marks[j + 1][0] if j + 1 < len(marks) else len(lines)
|
| 112 |
+
body = "\n".join(p for p in (_norm(x) for x in lines[i:end]) if p)
|
| 113 |
+
if len(body) > 120:
|
| 114 |
+
segments.append((label, title, body))
|
| 115 |
+
if not segments:
|
| 116 |
+
for k, piece in enumerate(split_lines(text, 1800), start=1):
|
| 117 |
+
body = "\n".join(p for p in (_norm(x) for x in piece.split("\n")) if p)
|
| 118 |
+
if len(body) > 120:
|
| 119 |
+
segments.append((f"part {k}", "", body))
|
| 120 |
+
chunks = []
|
| 121 |
+
fr = "-fr" if lang == "fr" else ""
|
| 122 |
+
for n, (label, title, body) in enumerate(segments, start=1):
|
| 123 |
+
pieces = split_lines(body, 1800) if len(body) > 2400 else [body]
|
| 124 |
+
total = len(pieces)
|
| 125 |
+
for k, piece in enumerate(pieces, start=1):
|
| 126 |
+
suffix = "" if total == 1 else f"-p{k}"
|
| 127 |
+
note = "" if total == 1 else f" (part {k} of {total})"
|
| 128 |
+
chunk = {
|
| 129 |
+
"id": f"{code}{fr}-{n}{suffix}",
|
| 130 |
+
"doc_type": "union",
|
| 131 |
+
"act_code": code.upper(),
|
| 132 |
+
"act_short": short,
|
| 133 |
+
"act_name": name,
|
| 134 |
+
"section": label,
|
| 135 |
+
"marginal_note": title,
|
| 136 |
+
"part": short,
|
| 137 |
+
"division": "",
|
| 138 |
+
"heading": "",
|
| 139 |
+
"text": piece,
|
| 140 |
+
"history": "",
|
| 141 |
+
"last_amended": currency,
|
| 142 |
+
"in_force": "",
|
| 143 |
+
"status": "in force",
|
| 144 |
+
"current_to": currency,
|
| 145 |
+
"citation": f"{short}, {label}"
|
| 146 |
+
+ (f" ({title})" if title else "") + note,
|
| 147 |
+
"source_url": url,
|
| 148 |
+
}
|
| 149 |
+
if lang == "fr":
|
| 150 |
+
chunk["lang"] = "fr"
|
| 151 |
+
chunks.append(chunk)
|
| 152 |
+
return chunks
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _vssa_links():
|
| 156 |
+
req = urllib.request.Request(VSSA_PAGE, headers={"User-Agent": "Mozilla/5.0"})
|
| 157 |
+
with urllib.request.urlopen(req, timeout=60) as r:
|
| 158 |
+
html = r.read().decode("utf-8", "replace")
|
| 159 |
+
links = sorted(set(re.findall(
|
| 160 |
+
r'href="(https?://[^"]*?/uploads/vssa/([^"/]+)\.pdf)"', html)))
|
| 161 |
+
return links
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def _vssa_chunks(url, stem, pages):
|
| 165 |
+
site = _norm(stem.replace("-", " "))
|
| 166 |
+
text = "\n".join(p for p in (_norm(x) for x in "\n".join(pages).split("\n")) if p)
|
| 167 |
+
if len(text) < 60:
|
| 168 |
+
return []
|
| 169 |
+
out = []
|
| 170 |
+
pieces = split_lines(text, 1800)
|
| 171 |
+
total = len(pieces)
|
| 172 |
+
for k, piece in enumerate(pieces, start=1):
|
| 173 |
+
suffix = "" if total == 1 else f"-p{k}"
|
| 174 |
+
note = "" if total == 1 else f" (part {k} of {total})"
|
| 175 |
+
out.append({
|
| 176 |
+
"id": f"vssa-{stem.lower()}{suffix}",
|
| 177 |
+
"doc_type": "union",
|
| 178 |
+
"act_code": "VSSA",
|
| 179 |
+
"act_short": "VSSA",
|
| 180 |
+
"act_name": "Variable Shift Schedule Agreements (by work site)",
|
| 181 |
+
"section": site,
|
| 182 |
+
"marginal_note": f"VSSA — {site}",
|
| 183 |
+
"part": "Variable Shift Schedule Agreements",
|
| 184 |
+
"division": "",
|
| 185 |
+
"heading": "",
|
| 186 |
+
"text": piece,
|
| 187 |
+
"history": "",
|
| 188 |
+
"last_amended": "",
|
| 189 |
+
"in_force": "",
|
| 190 |
+
"status": "in force",
|
| 191 |
+
"current_to": "",
|
| 192 |
+
"citation": f"VSSA — {site}{note}",
|
| 193 |
+
"source_url": url,
|
| 194 |
+
})
|
| 195 |
+
return out
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def build():
|
| 199 |
+
RAW.mkdir(parents=True, exist_ok=True)
|
| 200 |
+
all_chunks = []
|
| 201 |
+
for code, short, name, url, lang in DOCS:
|
| 202 |
+
fr = "-fr" if lang == "fr" else ""
|
| 203 |
+
dest = RAW / f"{code}{fr}.pdf"
|
| 204 |
+
try:
|
| 205 |
+
fetch_cached(url, dest, pause=0.8)
|
| 206 |
+
chunks = _doc_chunks(code, short, name, url, lang,
|
| 207 |
+
_pdf_pages(dest))
|
| 208 |
+
except Exception as exc:
|
| 209 |
+
print(f" !! {short} ({lang}): {type(exc).__name__}: {exc}")
|
| 210 |
+
continue
|
| 211 |
+
print(f" {short:24} ({lang}): {len(chunks)} chunks")
|
| 212 |
+
all_chunks.extend(chunks)
|
| 213 |
+
try:
|
| 214 |
+
links = _vssa_links()
|
| 215 |
+
except Exception as exc:
|
| 216 |
+
links = []
|
| 217 |
+
print(f" !! VSSA index: {type(exc).__name__}: {exc}")
|
| 218 |
+
n_vssa = 0
|
| 219 |
+
for url, stem in links:
|
| 220 |
+
dest = RAW / f"vssa-{stem.lower()}.pdf"
|
| 221 |
+
try:
|
| 222 |
+
fetch_cached(url, dest, pause=0.6)
|
| 223 |
+
chunks = _vssa_chunks(url, stem, _pdf_pages(dest))
|
| 224 |
+
except Exception as exc:
|
| 225 |
+
print(f" !! VSSA {stem}: {type(exc).__name__}: {exc}")
|
| 226 |
+
continue
|
| 227 |
+
all_chunks.extend(chunks)
|
| 228 |
+
n_vssa += 1
|
| 229 |
+
print(f" VSSA sites: {n_vssa}")
|
| 230 |
+
uniquify_ids(all_chunks)
|
| 231 |
+
out = PROCESSED_DIR / "ciu.json"
|
| 232 |
+
out.write_text(json.dumps(all_chunks, ensure_ascii=False, indent=2),
|
| 233 |
+
encoding="utf-8")
|
| 234 |
+
print(f"ciu: {len(all_chunks)} chunks -> {out.name}")
|
| 235 |
+
return all_chunks
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
if __name__ == "__main__":
|
| 239 |
+
build()
|
|
@@ -70,8 +70,55 @@ _NON_XML = [
|
|
| 70 |
("charter", "charter.json", "py -m canlex.charter", "Charter / Constitution"),
|
| 71 |
("commentary", "commentary.json", "py -m canlex.commentary",
|
| 72 |
"Curated commentary chunks"),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
]
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
# Curated datasets age: state legislatures amend deferred-adjudication
|
| 76 |
# statutes every session, so a reviewed date older than this many days earns
|
| 77 |
# a warning even though nothing upstream can be diffed automatically.
|
|
@@ -488,6 +535,7 @@ def run(as_json=False):
|
|
| 488 |
curated = check_curated(today)
|
| 489 |
dmemo_idx = check_dmemo_index()
|
| 490 |
tariff = check_tariff_edition(today)
|
|
|
|
| 491 |
pending = pending_in_force(today)
|
| 492 |
stale = [r for r in leg if r["status"] in ("stale", "missing-local")]
|
| 493 |
errors = [r for r in leg if r["status"] == "error"]
|
|
@@ -496,16 +544,19 @@ def run(as_json=False):
|
|
| 496 |
curated_flag = [r for r in curated if r["status"] in ("aging", "drift")]
|
| 497 |
dmemo_stale = dmemo_idx["status"] == "stale"
|
| 498 |
tariff_stale = tariff["status"] == "stale"
|
|
|
|
| 499 |
|
| 500 |
if as_json:
|
| 501 |
print(json.dumps({"checked": today, "legislation": leg,
|
| 502 |
"delegation": deleg, "directives": dirs,
|
| 503 |
"non_xml": nonxml, "curated": curated,
|
| 504 |
"dmemo_index": dmemo_idx, "tariff": tariff,
|
|
|
|
| 505 |
"pending_in_force": pending,
|
| 506 |
"stale_count": (len(stale) + len(deleg_stale)
|
| 507 |
+ len(dirs_stale)
|
| 508 |
+ len(curated_flag)
|
|
|
|
| 509 |
+ (1 if dmemo_stale else 0)
|
| 510 |
+ (1 if tariff_stale else 0)),
|
| 511 |
"error_count": len(errors)}, indent=2))
|
|
@@ -529,6 +580,8 @@ def run(as_json=False):
|
|
| 529 |
codes = " ".join(r["code"] for r in stale)
|
| 530 |
print(f"\n -> {len(stale)} drifted. Refresh: "
|
| 531 |
f"py -m canlex.ingest --force {codes}\n"
|
|
|
|
|
|
|
| 532 |
f" then: py -m canlex.embed && py -m canlex.eval")
|
| 533 |
|
| 534 |
print("\nDelegation instruments (upstream check):")
|
|
@@ -610,6 +663,19 @@ def run(as_json=False):
|
|
| 610 |
print(f"\nCustoms Tariff edition: ok ({tariff['stored']} is "
|
| 611 |
f"current)")
|
| 612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
if pending:
|
| 614 |
print("\nPending bills now in force (re-ingest the affected Acts):")
|
| 615 |
for b in pending:
|
|
@@ -619,7 +685,8 @@ def run(as_json=False):
|
|
| 619 |
print(f" run: py -m canlex.ingest --force {' '.join(b['affects'])}")
|
| 620 |
|
| 621 |
return 1 if (stale or errors or deleg_stale or dirs_stale
|
| 622 |
-
or curated_flag or dmemo_stale or tariff_stale
|
|
|
|
| 623 |
|
| 624 |
|
| 625 |
def main():
|
|
|
|
| 70 |
("charter", "charter.json", "py -m canlex.charter", "Charter / Constitution"),
|
| 71 |
("commentary", "commentary.json", "py -m canlex.commentary",
|
| 72 |
"Curated commentary chunks"),
|
| 73 |
+
("union", "ciu.json", "py -m canlex.ciu", "CIU/PSAC union documents"),
|
| 74 |
+
("pdi", "pdi.json", "py -m canlex.pdi", "IRCC PDI pages (PR card / Tran)"),
|
| 75 |
+
("irb-guidelines", "irb_guidelines.json", "py -m canlex.irb_guidelines",
|
| 76 |
+
"IRB Chairperson's Guidelines"),
|
| 77 |
]
|
| 78 |
|
| 79 |
+
|
| 80 |
+
def check_union():
|
| 81 |
+
"""Upstream drift for the CIU/PSAC documents (incl. every site VSSA).
|
| 82 |
+
|
| 83 |
+
The union reposts amended documents at the same URLs, so the drift
|
| 84 |
+
signal is each PDF's Last-Modified header against our cached copy's
|
| 85 |
+
fetch time. New/removed VSSAs surface as fetch errors or new links on
|
| 86 |
+
the next canlex.ciu run; this check covers content changes to what we
|
| 87 |
+
already hold."""
|
| 88 |
+
import email.utils
|
| 89 |
+
from . import ciu
|
| 90 |
+
rows = []
|
| 91 |
+
targets = [(short, lang, ciu.RAW / f"{code}{'-fr' if lang == 'fr' else ''}.pdf", url)
|
| 92 |
+
for code, short, name, url, lang in ciu.DOCS]
|
| 93 |
+
try:
|
| 94 |
+
targets += [(f"VSSA {stem}", "en",
|
| 95 |
+
ciu.RAW / f"vssa-{stem.lower()}.pdf", url)
|
| 96 |
+
for url, stem in ciu._vssa_links()]
|
| 97 |
+
except Exception as exc:
|
| 98 |
+
rows.append({"short": "VSSA index", "lang": "en", "status": "error",
|
| 99 |
+
"remote": "", "error": f"{type(exc).__name__}: {exc}"})
|
| 100 |
+
for short, lang, cache, url in targets:
|
| 101 |
+
try:
|
| 102 |
+
req = urllib.request.Request(url, method="HEAD",
|
| 103 |
+
headers={"User-Agent": BROWSER_UA})
|
| 104 |
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
| 105 |
+
lm = resp.headers.get("Last-Modified", "")
|
| 106 |
+
except Exception as exc:
|
| 107 |
+
rows.append({"short": short, "lang": lang, "status": "error",
|
| 108 |
+
"remote": "", "error": f"{type(exc).__name__}: {exc}"})
|
| 109 |
+
continue
|
| 110 |
+
if not lm:
|
| 111 |
+
rows.append({"short": short, "lang": lang, "status": "no-date",
|
| 112 |
+
"remote": "", "error": ""})
|
| 113 |
+
continue
|
| 114 |
+
remote_ts = email.utils.parsedate_to_datetime(lm).timestamp()
|
| 115 |
+
local_ts = cache.stat().st_mtime if cache.exists() else 0
|
| 116 |
+
rows.append({"short": short, "lang": lang,
|
| 117 |
+
"status": "stale" if remote_ts > local_ts else "ok",
|
| 118 |
+
"remote": lm[:25], "error": ""})
|
| 119 |
+
time.sleep(0.3)
|
| 120 |
+
return rows
|
| 121 |
+
|
| 122 |
# Curated datasets age: state legislatures amend deferred-adjudication
|
| 123 |
# statutes every session, so a reviewed date older than this many days earns
|
| 124 |
# a warning even though nothing upstream can be diffed automatically.
|
|
|
|
| 535 |
curated = check_curated(today)
|
| 536 |
dmemo_idx = check_dmemo_index()
|
| 537 |
tariff = check_tariff_edition(today)
|
| 538 |
+
union = check_union()
|
| 539 |
pending = pending_in_force(today)
|
| 540 |
stale = [r for r in leg if r["status"] in ("stale", "missing-local")]
|
| 541 |
errors = [r for r in leg if r["status"] == "error"]
|
|
|
|
| 544 |
curated_flag = [r for r in curated if r["status"] in ("aging", "drift")]
|
| 545 |
dmemo_stale = dmemo_idx["status"] == "stale"
|
| 546 |
tariff_stale = tariff["status"] == "stale"
|
| 547 |
+
union_stale = [r for r in union if r["status"] == "stale"]
|
| 548 |
|
| 549 |
if as_json:
|
| 550 |
print(json.dumps({"checked": today, "legislation": leg,
|
| 551 |
"delegation": deleg, "directives": dirs,
|
| 552 |
"non_xml": nonxml, "curated": curated,
|
| 553 |
"dmemo_index": dmemo_idx, "tariff": tariff,
|
| 554 |
+
"union": union,
|
| 555 |
"pending_in_force": pending,
|
| 556 |
"stale_count": (len(stale) + len(deleg_stale)
|
| 557 |
+ len(dirs_stale)
|
| 558 |
+ len(curated_flag)
|
| 559 |
+
+ len(union_stale)
|
| 560 |
+ (1 if dmemo_stale else 0)
|
| 561 |
+ (1 if tariff_stale else 0)),
|
| 562 |
"error_count": len(errors)}, indent=2))
|
|
|
|
| 580 |
codes = " ".join(r["code"] for r in stale)
|
| 581 |
print(f"\n -> {len(stale)} drifted. Refresh: "
|
| 582 |
f"py -m canlex.ingest --force {codes}\n"
|
| 583 |
+
f" and the French twins: py -m canlex.ingest --fr "
|
| 584 |
+
f"--force {codes}\n"
|
| 585 |
f" then: py -m canlex.embed && py -m canlex.eval")
|
| 586 |
|
| 587 |
print("\nDelegation instruments (upstream check):")
|
|
|
|
| 663 |
print(f"\nCustoms Tariff edition: ok ({tariff['stored']} is "
|
| 664 |
f"current)")
|
| 665 |
|
| 666 |
+
n_union_ok = sum(1 for r in union if r["status"] == "ok")
|
| 667 |
+
print(f"\nCIU/PSAC union documents (upstream Last-Modified check, "
|
| 668 |
+
f"{len(union)} documents):")
|
| 669 |
+
for r in union:
|
| 670 |
+
if r["status"] == "stale":
|
| 671 |
+
print(f" STALE {r['short']} ({r['lang']}): upstream "
|
| 672 |
+
f"modified {r['remote']} after our fetch — run "
|
| 673 |
+
f"py -m canlex.ciu && py -m canlex.embed")
|
| 674 |
+
elif r["status"] == "error":
|
| 675 |
+
print(f" ERROR {r['short']}: {r['error'][:55]}")
|
| 676 |
+
if not union_stale and not any(r["status"] == "error" for r in union):
|
| 677 |
+
print(f" all {n_union_ok} current (upstream unchanged since fetch)")
|
| 678 |
+
|
| 679 |
if pending:
|
| 680 |
print("\nPending bills now in force (re-ingest the affected Acts):")
|
| 681 |
for b in pending:
|
|
|
|
| 685 |
print(f" run: py -m canlex.ingest --force {' '.join(b['affects'])}")
|
| 686 |
|
| 687 |
return 1 if (stale or errors or deleg_stale or dirs_stale
|
| 688 |
+
or curated_flag or dmemo_stale or tariff_stale
|
| 689 |
+
or union_stale) else 0
|
| 690 |
|
| 691 |
|
| 692 |
def main():
|
|
@@ -196,6 +196,20 @@ def _format_section(c: dict, related=None) -> str:
|
|
| 196 |
age = _guidance_age_note(c["current_to"])
|
| 197 |
if age:
|
| 198 |
lines.append(age)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
elif doc_type == "commentary":
|
| 200 |
lines.append("_**CURATED ANALYSIS — CanLex commentary, not a source "
|
| 201 |
"of law.** A curated synthesis of the cited authorities; "
|
|
@@ -267,7 +281,7 @@ def _format_section(c: dict, related=None) -> str:
|
|
| 267 |
# as an enum rather than free text (regex 'pattern' constraints are dropped by
|
| 268 |
# several function-calling translators, notably Gemini's).
|
| 269 |
DocType = Literal["legislation", "memorandum", "agreement", "directive",
|
| 270 |
-
"caselaw", "delegation", "benefits", "commentary"]
|
| 271 |
|
| 272 |
# Longest alternative first, and 's'/'ss' only at a word boundary or before
|
| 273 |
# a dotted number -- so 'section 20.1' loses the whole word (not its first
|
|
@@ -322,7 +336,7 @@ def canlex_search_legislation(
|
|
| 322 |
"""Search Canadian federal law, CBSA D-Memoranda, agreements, NJC directives,
|
| 323 |
and leading court decisions.
|
| 324 |
|
| 325 |
-
The CanLex corpus has
|
| 326 |
with their Schedules (immigration, customs, criminal, firearms, drugs,
|
| 327 |
food/health, labour, privacy and more); CBSA D-Memoranda (administrative
|
| 328 |
guidance on how the CBSA applies customs and border law); Treasury Board
|
|
@@ -331,8 +345,10 @@ def canlex_search_legislation(
|
|
| 331 |
leading decisions of the courts and federal tribunals (Supreme Court,
|
| 332 |
Federal Court of Appeal and Federal Court, the IRB, and the FPSLREB and
|
| 333 |
CIRB labour boards); instruments of delegation and designation under IRPA
|
| 334 |
-
and the IRPR; member booklets of the public-service benefit plans;
|
| 335 |
-
CanLex's curated commentary (US-disposition and equivalency analysis)
|
|
|
|
|
|
|
| 336 |
Use this for ANY question about that material. It ranks results by
|
| 337 |
relevance and returns their full text so the answer can cite the actual
|
| 338 |
wording; an explicit section reference (e.g. "section 34") is always
|
|
@@ -725,6 +741,7 @@ def canlex_list_acts() -> str:
|
|
| 725 |
delegations: dict[str, dict] = {}
|
| 726 |
benefits: dict[str, dict] = {}
|
| 727 |
commentary: dict[str, dict] = {}
|
|
|
|
| 728 |
memo_numbers: set[str] = set()
|
| 729 |
memo_chunks = 0
|
| 730 |
memo_date = ""
|
|
@@ -776,6 +793,12 @@ def canlex_list_acts() -> str:
|
|
| 776 |
"current_to": c["current_to"], "count": 0,
|
| 777 |
})
|
| 778 |
entry["count"] += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
else:
|
| 780 |
entry = acts.setdefault(c["act_code"], {
|
| 781 |
"short": c["act_short"], "name": c["act_name"],
|
|
@@ -822,6 +845,12 @@ def canlex_list_acts() -> str:
|
|
| 822 |
lines.append(f"- **{a['short']}** — {a['name']}: {a['count']} "
|
| 823 |
f"sections, booklet edition {a['current_to'] or 'n/a'}. "
|
| 824 |
f"Plain-language member guide; the plan rules govern.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 825 |
if commentary:
|
| 826 |
lines += ["", "## Curated commentary (not law)"]
|
| 827 |
for a in sorted(commentary.values(), key=lambda x: x["short"]):
|
|
|
|
| 196 |
age = _guidance_age_note(c["current_to"])
|
| 197 |
if age:
|
| 198 |
lines.append(age)
|
| 199 |
+
elif doc_type == "union":
|
| 200 |
+
lines.append("_Union document (CIU/PSAC) — internal governance "
|
| 201 |
+
"rules, member guides or locally negotiated scheduling "
|
| 202 |
+
"instruments published by the bargaining agent. They "
|
| 203 |
+
"bind the union and its members under the union's own "
|
| 204 |
+
"rules; they are NOT employer policy and NOT part of "
|
| 205 |
+
"the collective agreement itself (a VSSA operates "
|
| 206 |
+
"under the FB agreement's variable-shift provisions). "
|
| 207 |
+
"The union site may lag renegotiations — verify "
|
| 208 |
+
"currency with the Branch._")
|
| 209 |
+
lines.append(f"(document currency: {_stated(c['current_to'])})")
|
| 210 |
+
age = _guidance_age_note(c["current_to"])
|
| 211 |
+
if age:
|
| 212 |
+
lines.append(age)
|
| 213 |
elif doc_type == "commentary":
|
| 214 |
lines.append("_**CURATED ANALYSIS — CanLex commentary, not a source "
|
| 215 |
"of law.** A curated synthesis of the cited authorities; "
|
|
|
|
| 281 |
# as an enum rather than free text (regex 'pattern' constraints are dropped by
|
| 282 |
# several function-calling translators, notably Gemini's).
|
| 283 |
DocType = Literal["legislation", "memorandum", "agreement", "directive",
|
| 284 |
+
"caselaw", "delegation", "benefits", "commentary", "union"]
|
| 285 |
|
| 286 |
# Longest alternative first, and 's'/'ss' only at a word boundary or before
|
| 287 |
# a dotted number -- so 'section 20.1' loses the whole word (not its first
|
|
|
|
| 336 |
"""Search Canadian federal law, CBSA D-Memoranda, agreements, NJC directives,
|
| 337 |
and leading court decisions.
|
| 338 |
|
| 339 |
+
The CanLex corpus has nine kinds of source: federal Acts and regulations
|
| 340 |
with their Schedules (immigration, customs, criminal, firearms, drugs,
|
| 341 |
food/health, labour, privacy and more); CBSA D-Memoranda (administrative
|
| 342 |
guidance on how the CBSA applies customs and border law); Treasury Board
|
|
|
|
| 345 |
leading decisions of the courts and federal tribunals (Supreme Court,
|
| 346 |
Federal Court of Appeal and Federal Court, the IRB, and the FPSLREB and
|
| 347 |
CIRB labour boards); instruments of delegation and designation under IRPA
|
| 348 |
+
and the IRPR; member booklets of the public-service benefit plans;
|
| 349 |
+
CanLex's curated commentary (US-disposition and equivalency analysis);
|
| 350 |
+
and union documents (the CIU by-laws/regulations/branch rules, the PSAC
|
| 351 |
+
Constitution, member guides, and every published site VSSA).
|
| 352 |
Use this for ANY question about that material. It ranks results by
|
| 353 |
relevance and returns their full text so the answer can cite the actual
|
| 354 |
wording; an explicit section reference (e.g. "section 34") is always
|
|
|
|
| 741 |
delegations: dict[str, dict] = {}
|
| 742 |
benefits: dict[str, dict] = {}
|
| 743 |
commentary: dict[str, dict] = {}
|
| 744 |
+
union_docs: dict[str, dict] = {}
|
| 745 |
memo_numbers: set[str] = set()
|
| 746 |
memo_chunks = 0
|
| 747 |
memo_date = ""
|
|
|
|
| 793 |
"current_to": c["current_to"], "count": 0,
|
| 794 |
})
|
| 795 |
entry["count"] += 1
|
| 796 |
+
elif doc_type == "union":
|
| 797 |
+
entry = union_docs.setdefault(c["act_code"], {
|
| 798 |
+
"short": c["act_short"], "name": c["act_name"],
|
| 799 |
+
"current_to": c["current_to"], "count": 0,
|
| 800 |
+
})
|
| 801 |
+
entry["count"] += 1
|
| 802 |
else:
|
| 803 |
entry = acts.setdefault(c["act_code"], {
|
| 804 |
"short": c["act_short"], "name": c["act_name"],
|
|
|
|
| 845 |
lines.append(f"- **{a['short']}** — {a['name']}: {a['count']} "
|
| 846 |
f"sections, booklet edition {a['current_to'] or 'n/a'}. "
|
| 847 |
f"Plain-language member guide; the plan rules govern.")
|
| 848 |
+
if union_docs:
|
| 849 |
+
lines += ["", "## Union documents (CIU/PSAC)"]
|
| 850 |
+
for a in sorted(union_docs.values(), key=lambda x: x["short"]):
|
| 851 |
+
lines.append(f"- **{a['short']}** — {a['name']}: {a['count']} "
|
| 852 |
+
f"sections, currency {a['current_to'] or 'n/a'}. "
|
| 853 |
+
f"Bargaining-agent document; not employer policy.")
|
| 854 |
if commentary:
|
| 855 |
lines += ["", "## Curated commentary (not law)"]
|
| 856 |
for a in sorted(commentary.values(), key=lambda x: x["short"]):
|
|
@@ -1990,5 +1990,53 @@
|
|
| 1990 |
"query": "What notice must an Ontario landlord give to evict a tenant?",
|
| 1991 |
"answers": [],
|
| 1992 |
"style": "no-answer"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1993 |
}
|
| 1994 |
]
|
|
|
|
| 1990 |
"query": "What notice must an Ontario landlord give to evict a tenant?",
|
| 1991 |
"answers": [],
|
| 1992 |
"style": "no-answer"
|
| 1993 |
+
},
|
| 1994 |
+
{
|
| 1995 |
+
"query": "What do the CIU by-laws say about how branch elections are conducted?",
|
| 1996 |
+
"answers": [
|
| 1997 |
+
[
|
| 1998 |
+
"CIU By-Laws",
|
| 1999 |
+
""
|
| 2000 |
+
],
|
| 2001 |
+
[
|
| 2002 |
+
"CIU Elections Kit",
|
| 2003 |
+
""
|
| 2004 |
+
],
|
| 2005 |
+
[
|
| 2006 |
+
"CIU Branch Rules",
|
| 2007 |
+
""
|
| 2008 |
+
]
|
| 2009 |
+
],
|
| 2010 |
+
"style": "union"
|
| 2011 |
+
},
|
| 2012 |
+
{
|
| 2013 |
+
"query": "What is the variable shift schedule agreement for Windsor Bridge?",
|
| 2014 |
+
"answers": [
|
| 2015 |
+
[
|
| 2016 |
+
"VSSA",
|
| 2017 |
+
""
|
| 2018 |
+
]
|
| 2019 |
+
],
|
| 2020 |
+
"style": "union"
|
| 2021 |
+
},
|
| 2022 |
+
{
|
| 2023 |
+
"query": "How does the PSAC constitution deal with membership discipline and expulsion?",
|
| 2024 |
+
"answers": [
|
| 2025 |
+
[
|
| 2026 |
+
"PSAC Constitution",
|
| 2027 |
+
""
|
| 2028 |
+
]
|
| 2029 |
+
],
|
| 2030 |
+
"style": "union"
|
| 2031 |
+
},
|
| 2032 |
+
{
|
| 2033 |
+
"query": "What are the timelines for presenting a grievance according to the union's grievance guide?",
|
| 2034 |
+
"answers": [
|
| 2035 |
+
[
|
| 2036 |
+
"CIU Grievance Guide",
|
| 2037 |
+
""
|
| 2038 |
+
]
|
| 2039 |
+
],
|
| 2040 |
+
"style": "union"
|
| 2041 |
}
|
| 2042 |
]
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:94e937a9a6a36a17662c7fb63bc1cec7397a28ff910e0c6d5889c2b2ac8deed8
|
| 3 |
+
size 13150503
|
|
The diff for this file is too large to render.
See raw diff
|
|
|
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b6380fda9c44fafe766c7cf83a1b40f8a7ef8a689533069d8181b3a28a79c06f
|
| 3 |
+
size 78503650
|
|
@@ -124,7 +124,7 @@ TOOL_DECLARATIONS = [
|
|
| 124 |
"type": "string",
|
| 125 |
"enum": ["legislation", "memorandum", "agreement",
|
| 126 |
"directive", "caselaw", "delegation",
|
| 127 |
-
"benefits", "commentary"],
|
| 128 |
"description": (
|
| 129 |
"Optional. Restrict to one source type: 'legislation', "
|
| 130 |
"'memorandum' (CBSA D-Memoranda), 'agreement' "
|
|
|
|
| 124 |
"type": "string",
|
| 125 |
"enum": ["legislation", "memorandum", "agreement",
|
| 126 |
"directive", "caselaw", "delegation",
|
| 127 |
+
"benefits", "commentary", "union"],
|
| 128 |
"description": (
|
| 129 |
"Optional. Restrict to one source type: 'legislation', "
|
| 130 |
"'memorandum' (CBSA D-Memoranda), 'agreement' "
|