Beemer Claude Fable 5 commited on
Commit
1d4acf4
·
1 Parent(s): 0b22e9a

Equivalency pairings + rehabilitation engine: the s. 36 screening suite

Browse files

canlex_us_equivalency: 13 curated US->Canadian offence pairings with every
maximum penalty verified against the corpus text of the Criminal Code and
CDSA. Headlines: one US DUI = serious criminality via hybrid deeming (with
the Tran 2017 SCC 50 commission-date caveat for pre-2018-12-18 offences);
cannabis simple possession has NO Canadian equivalent post-legalization;
a single disorderly conduct equates to s. 175 (pure summary) and grounds
nothing; property offences split at the Canadian $5,000 line. Hill
methodology chunk included; honest no-pairing fallback.

canlex_rehabilitation: pure rule engine (canlex/rehab.py) for IRPA
s. 36(3)(c) + IRPR ss. 17/18/18.1, rules verified verbatim against the
corpus consolidation -- deemed rehabilitation (10 clean years single /
5 years two-plus-summary; never for serious criminality), individual
applications at 5 years, record-suspension route for in-Canada convictions,
the 18.1 two-summary exemption. 10 unit tests.

Together with canlex_us_disposition these form the three-step US-record
screening flow: conviction? -> equivalent offence? -> cured by time?
Reviewed by the user 2026-07-15.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

canlex/rehab.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Criminal-rehabilitation assessment under IRPA s. 36(3)(c) and IRPR ss. 17-18.1.
2
+
3
+ Pure rule logic, verified against the corpus text of IRPR ss. 17, 18 and 18.1
4
+ (SOR-2002-227, current consolidation) and IRPA s. 36(3). The rules:
5
+
6
+ Deemed rehabilitation (IRPR s. 18(2)) -- automatic, no application:
7
+ (a) ONE foreign conviction, indictable-equivalent, punishable in Canada by
8
+ a maximum of LESS THAN 10 years: 10 years after sentence completion,
9
+ with the cleanliness conditions (i)-(vii).
10
+ (b) TWO OR MORE foreign convictions that would all be summary offences:
11
+ 5 years after the sentences were completed, with conditions.
12
+ (c) ONE committed act (s. 36(1)(c)/(2)(c)), <10-year maximum: 10 years
13
+ after the commission, with conditions.
14
+ Serious criminality (>=10-year maximum equivalent) can NEVER be deemed
15
+ rehabilitated -- only an individual application.
16
+
17
+ Individual rehabilitation (IRPA s. 36(3)(c), period per IRPR s. 17):
18
+ eligible to APPLY 5 years after sentence completion (conviction branches)
19
+ or 5 years after commission (act branches). Available for serious
20
+ criminality too.
21
+
22
+ In-Canada convictions are outside the rehabilitation scheme entirely --
23
+ the cure is a record suspension (IRPA s. 36(3)(b)) -- EXCEPT the IRPR
24
+ s. 18.1 prescribed class: inadmissible SOLELY for two or more in-Canada
25
+ summary-only convictions, exempt from s. 36(2)(a) five years after the
26
+ sentences were completed.
27
+ """
28
+
29
+ BASES = ("foreign-conviction", "foreign-act", "canada-conviction")
30
+ COUNTS = ("one", "two-plus-summary-only", "multiple")
31
+
32
+
33
+ def assess(basis, max_penalty_lt_10, count, years_elapsed, clean_record):
34
+ """Assess rehabilitation status. Returns a dict:
35
+ {status, provision, explanation, caveats}.
36
+
37
+ basis: how the inadmissibility arises (BASES).
38
+ max_penalty_lt_10: True if the equivalent Canadian offence's maximum term
39
+ is under 10 years (False = serious criminality territory).
40
+ count: 'one' | 'two-plus-summary-only' | 'multiple' (COUNTS).
41
+ years_elapsed: full years since sentence completion (conviction bases)
42
+ or since commission (act basis).
43
+ clean_record: True if there are no other convictions or committed acts
44
+ (the s. 18(2) (i)-(vii) conditions, simplified -- see caveats).
45
+ """
46
+ caveats = [
47
+ "The 'clean record' answer stands in for the detailed conditions in "
48
+ "IRPR s. 18(2)(i)-(vii) (no Canadian indictable conviction ever; no "
49
+ "recent summary convictions; at most one older summary conviction; no "
50
+ "other committed acts) -- confirm each condition against the full "
51
+ "record before relying on deemed rehabilitation.",
52
+ "A temporary resident permit (IRPA s. 24) remains available at any "
53
+ "time regardless of rehabilitation status.",
54
+ "Time runs from the day AFTER completion of the ENTIRE sentence -- "
55
+ "including probation, fines and driving prohibitions -- not from the "
56
+ "conviction date.",
57
+ ]
58
+
59
+ if basis == "canada-conviction":
60
+ if count == "two-plus-summary-only":
61
+ if years_elapsed >= 5:
62
+ return _r("exempt", "IRPR s. 18.1",
63
+ "Inadmissible solely for two or more in-Canada "
64
+ "summary-only convictions, and at least five years "
65
+ "have passed since the sentences were completed: "
66
+ "the person is in the prescribed class exempt from "
67
+ "IRPA s. 36(2)(a).", caveats)
68
+ return _r("not-yet", "IRPR s. 18.1",
69
+ "The in-Canada two-summary-only exemption requires five "
70
+ "years since the sentences were completed "
71
+ f"({years_elapsed} elapsed).", caveats)
72
+ return _r("record-suspension-route", "IRPA s. 36(3)(b)",
73
+ "Rehabilitation does not apply to in-Canada convictions. "
74
+ "The cure is a record suspension under the Criminal Records "
75
+ "Act (IRPA s. 36(3)(b)); once ordered and not revoked, the "
76
+ "conviction cannot ground inadmissibility.", caveats)
77
+
78
+ since = ("sentence completion" if basis == "foreign-conviction"
79
+ else "the commission of the act")
80
+
81
+ if not max_penalty_lt_10:
82
+ # Serious criminality: deemed rehabilitation is never available.
83
+ if years_elapsed >= 5:
84
+ return _r("eligible-to-apply", "IRPA s. 36(3)(c); IRPR s. 17",
85
+ "The equivalent Canadian offence carries a maximum of "
86
+ "10 years or more (serious criminality), so deemed "
87
+ "rehabilitation is NOT available -- but at least five "
88
+ f"years have passed since {since}, so the person is "
89
+ "eligible to APPLY for individual rehabilitation.",
90
+ caveats)
91
+ return _r("not-yet", "IRPR s. 17",
92
+ "Serious criminality: deemed rehabilitation is not "
93
+ "available, and the five-year eligibility period to apply "
94
+ f"for individual rehabilitation has not yet run "
95
+ f"({years_elapsed} of 5 years since {since}).", caveats)
96
+
97
+ if count == "multiple":
98
+ if years_elapsed >= 5:
99
+ return _r("eligible-to-apply", "IRPA s. 36(3)(c); IRPR s. 17",
100
+ "More than one indictable-equivalent conviction/act: "
101
+ "deemed rehabilitation is not available (IRPR s. 18(2) "
102
+ "covers only a single such conviction or act), but the "
103
+ "five-year period to apply for individual "
104
+ "rehabilitation has run.", caveats)
105
+ return _r("not-yet", "IRPR s. 17",
106
+ "Multiple indictable-equivalent matters: only individual "
107
+ "rehabilitation is possible, and its five-year eligibility "
108
+ f"period has not run ({years_elapsed} of 5 years).", caveats)
109
+
110
+ if count == "two-plus-summary-only":
111
+ para = "IRPR s. 18(2)(b)"
112
+ need = 5
113
+ elif basis == "foreign-act":
114
+ para = "IRPR s. 18(2)(c)"
115
+ need = 10
116
+ else:
117
+ para = "IRPR s. 18(2)(a)"
118
+ need = 10
119
+
120
+ if years_elapsed >= need and clean_record:
121
+ return _r("deemed-rehabilitated", para,
122
+ f"All {para} requirements appear met: {years_elapsed} years "
123
+ f"since {since} (needs {need}), a qualifying single/summary "
124
+ "profile, and a clean record. The person is deemed "
125
+ "rehabilitated -- the matter cannot ground inadmissibility "
126
+ "under IRPA s. 36(3)(c).", caveats)
127
+ if years_elapsed >= 5:
128
+ why = (f"only {years_elapsed} of {need} years have run"
129
+ if years_elapsed < need else
130
+ "the record is not clean, which defeats deemed rehabilitation")
131
+ return _r("eligible-to-apply", f"{para}; IRPR s. 17",
132
+ f"Deemed rehabilitation is not (yet) made out ({why}), but "
133
+ "five years have passed, so the person is eligible to APPLY "
134
+ "for individual rehabilitation.", caveats)
135
+ return _r("not-yet", f"{para}; IRPR s. 17",
136
+ f"Neither deemed rehabilitation ({years_elapsed} of {need} "
137
+ "years) nor application eligibility (five years) has been "
138
+ "reached.", caveats)
139
+
140
+
141
+ def _r(status, provision, explanation, caveats):
142
+ return {"status": status, "provision": provision,
143
+ "explanation": explanation, "caveats": caveats}
canlex/server.py CHANGED
@@ -480,6 +480,171 @@ def _match_dispositions(data, disposition, state):
480
  return [e for _, e in scored[:2]]
481
 
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  @mcp.tool(name="canlex_us_disposition",
484
  annotations={"title": "US Disposition vs IRPA 'Conviction'", **_READONLY})
485
  def canlex_us_disposition(params: DispositionInput) -> str:
 
480
  return [e for _, e in scored[:2]]
481
 
482
 
483
+ class EquivalencyInput(BaseModel):
484
+ """Input for canlex_us_equivalency."""
485
+ model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
486
+
487
+ offense: str = Field(
488
+ ...,
489
+ description="The US offence as charged or described, e.g. 'DUI', "
490
+ "'petty theft', 'aggravated assault', 'possession of a controlled "
491
+ "substance', 'disorderly conduct'.",
492
+ min_length=2, max_length=200,
493
+ )
494
+
495
+
496
+ def _load_equivalency():
497
+ from canlex.config import DATA_DIR
498
+ path = DATA_DIR / "curated" / "us_equivalency.json"
499
+ if not path.exists():
500
+ return None
501
+ return json.loads(path.read_text(encoding="utf-8"))
502
+
503
+
504
+ @mcp.tool(name="canlex_us_equivalency",
505
+ annotations={"title": "US Offence Equivalency", **_READONLY})
506
+ def canlex_us_equivalency(params: EquivalencyInput) -> str:
507
+ """Map a common US offence to its Canadian equivalent and the resulting
508
+ IRPA s. 36 inadmissibility branch (serious criminality vs criminality vs
509
+ none), from CanLex's curated pairing table with corpus-verified maximum
510
+ penalties.
511
+
512
+ This is step 2 of a s. 36 analysis (step 1: is the disposition a
513
+ conviction -- canlex_us_disposition; step 3: rehabilitation --
514
+ canlex_rehabilitation, which takes this tool's max-penalty answer as
515
+ input). Covers the COMMON pairings (DUI, theft, assault, fraud, drugs,
516
+ cannabis, disorderly conduct...); anything unusual gets the Hill
517
+ methodology and an honest no-entry statement.
518
+
519
+ Returns:
520
+ str: Markdown -- the pairing with penalty, branch, caveats and
521
+ authorities, or the Hill framework when nothing matches.
522
+ """
523
+ from canlex.commentary import BANNER
524
+ data = _load_equivalency()
525
+ if data is None:
526
+ return ("The equivalency dataset is not present in this deployment. "
527
+ "Apply Hill v Canada (MEI) (1987), 73 NR 315 (FCA): compare "
528
+ "essential elements, or the evidence of what was done, or "
529
+ "both; remember IRPA s. 36(3)(a) hybrid deeming.")
530
+ q = set(re.findall(r"[a-z0-9]+", params.offense.lower()))
531
+ scored = []
532
+ for p in data.get("pairings", []):
533
+ best = 0.0
534
+ for t in p["us_terms"]:
535
+ toks = set(re.findall(r"[a-z0-9]+", t.lower()))
536
+ if toks:
537
+ best = max(best, len(q & toks) / len(toks))
538
+ if best > 0.4:
539
+ scored.append((best, p))
540
+ scored.sort(key=lambda x: -x[0])
541
+ parts = [BANNER]
542
+ if scored:
543
+ for _, p in scored[:2]:
544
+ lines = [f"US offence: {'; '.join(p['us_terms'][:5])}",
545
+ f"Canadian equivalent: {p['canadian_offence']}",
546
+ f"Maximum penalty (verified): {p['penalty']}",
547
+ f"Inadmissibility branch: {p['branch']}", "",
548
+ p["analysis"]]
549
+ if p.get("caveats"):
550
+ lines.append("")
551
+ lines += [f"- CAVEAT: {c}" for c in p["caveats"]]
552
+ if p.get("authorities"):
553
+ lines += ["", "Authorities:"]
554
+ lines += [f"- {a['cite']} ({a['court']}): {a['holding']}"
555
+ for a in p["authorities"]]
556
+ parts.append("\n".join(lines))
557
+ else:
558
+ parts.append(
559
+ f"No curated pairing matches '{params.offense}'. Apply the Hill "
560
+ "framework (below) to the actual foreign statute -- compare "
561
+ "essential elements, or the evidence of what was done, or both.")
562
+ for m in data.get("methodology", []):
563
+ if m["id"] == "equivalency-framework":
564
+ parts.append("---\n\nGoverning framework:\n\n" + m["text"])
565
+ break
566
+ parts.append("Retrieve the Canadian provisions with canlex_get_section "
567
+ "to verify elements and penalties; feed the max-penalty "
568
+ "answer into canlex_rehabilitation for step 3.")
569
+ return "\n\n".join(parts)
570
+
571
+
572
+ class RehabInput(BaseModel):
573
+ """Input for canlex_rehabilitation."""
574
+ model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
575
+
576
+ basis: str = Field(
577
+ ...,
578
+ description="How the inadmissibility arises: 'foreign-conviction' "
579
+ "(IRPA s. 36(1)(b)/(2)(b)), 'foreign-act' (s. 36(1)(c)/(2)(c)), or "
580
+ "'canada-conviction' (s. 36(1)(a)/(2)(a)).",
581
+ pattern="^(foreign-conviction|foreign-act|canada-conviction)$",
582
+ )
583
+ max_penalty_lt_10: bool = Field(
584
+ ...,
585
+ description="True if the equivalent Canadian offence's maximum term "
586
+ "of imprisonment is LESS THAN 10 years (remember IRPA s. 36(3)(a): "
587
+ "hybrid offences count as indictable at their indictable maximum).",
588
+ )
589
+ count: str = Field(
590
+ ...,
591
+ description="'one' (a single conviction or act), "
592
+ "'two-plus-summary-only' (two or more, all summary-equivalent), or "
593
+ "'multiple' (more than one indictable-equivalent matter).",
594
+ pattern="^(one|two-plus-summary-only|multiple)$",
595
+ )
596
+ years_elapsed: int = Field(
597
+ ..., ge=0, le=100,
598
+ description="Full years since the day after completion of the ENTIRE "
599
+ "imposed sentence (conviction bases) or since the act was committed "
600
+ "(act basis).",
601
+ )
602
+ clean_record: bool = Field(
603
+ ...,
604
+ description="True if the person has no other convictions or "
605
+ "committed acts anywhere (the IRPR s. 18(2)(i)-(vii) conditions, "
606
+ "simplified -- the answer carries a caveat to confirm in detail).",
607
+ )
608
+
609
+
610
+ @mcp.tool(name="canlex_rehabilitation",
611
+ annotations={"title": "Criminal Rehabilitation Assessment", **_READONLY})
612
+ def canlex_rehabilitation(params: RehabInput) -> str:
613
+ """Assess whether a person is deemed rehabilitated, eligible to apply for
614
+ individual rehabilitation, or neither, under IRPA s. 36(3)(c) and IRPR
615
+ ss. 17-18.1 -- the time-based cures for criminal inadmissibility.
616
+
617
+ Rules verified against the current IRPR consolidation: deemed
618
+ rehabilitation (automatic) for a single <10-year-maximum foreign
619
+ conviction after 10 clean years, two-plus summary-only convictions after
620
+ 5 years, or a single committed act after 10 years; individual
621
+ rehabilitation applications after 5 years (available even for serious
622
+ criminality, which can never be deemed); record suspensions for in-Canada
623
+ convictions. This is step 3 of a s. 36 screening: step 1 is whether the
624
+ disposition is a conviction (canlex_us_disposition), step 2 is offence
625
+ equivalency (which also supplies the max-penalty input here).
626
+
627
+ Returns:
628
+ str: The assessment with the governing provision, explanation and
629
+ mandatory caveats. Curated logic -- verify against the cited
630
+ provisions via canlex_get_section.
631
+ """
632
+ from canlex.rehab import assess
633
+ from canlex.commentary import BANNER
634
+ r = assess(params.basis, params.max_penalty_lt_10, params.count,
635
+ params.years_elapsed, params.clean_record)
636
+ lines = [BANNER, "",
637
+ f"REHABILITATION STATUS: {r['status'].upper().replace('-', ' ')}",
638
+ f"Governing provision(s): {r['provision']}", "",
639
+ r["explanation"], "", "Caveats:"]
640
+ lines += [f"- {c}" for c in r["caveats"]]
641
+ lines.append("")
642
+ lines.append("Retrieve the provisions with canlex_get_section (act "
643
+ "'IRPR', sections '17'/'18'/'18.1'; act 'IRPA', section "
644
+ "'36').")
645
+ return "\n".join(lines)
646
+
647
+
648
  @mcp.tool(name="canlex_us_disposition",
649
  annotations={"title": "US Disposition vs IRPA 'Conviction'", **_READONLY})
650
  def canlex_us_disposition(params: DispositionInput) -> str:
data/curated/us_equivalency.json ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "reviewed": "2026-07-15",
3
+ "_provenance": "Authored 2026-07-12. Every Canadian maximum penalty verified against the corpus text of the Criminal Code (C-46) and CDSA (C-38.8), current consolidations; branch classifications apply IRPA s. 36(3)(a) (hybrid offences deemed indictable). Tran v Canada, 2017 SCC 50 (in-corpus) supplies the commission-date rule for assessing maximum penalties. Equivalency methodology per Hill v Canada (MEI) (1987), 73 NR 315 (FCA), as set out in IRB Legal Services, Sponsorship Appeals Ch. 2 (Criminal Refusals). These are the COMMON pairings only -- an unusual offence needs the full Hill analysis against the actual foreign statute.",
4
+ "methodology": [
5
+ {
6
+ "id": "equivalency-framework",
7
+ "title": "How offence equivalency works (step 2 of the s. 36 analysis)",
8
+ "text": "Equivalency asks whether the foreign offence, if committed in Canada, would constitute a federal offence of the required seriousness. The governing test is Hill v Canada (MEI) (1987), 73 NR 315 (FCA): equivalency can be established (1) by comparing the precise wording and essential elements of the two provisions; (2) by examining the evidence of what the person actually did, to see whether it would satisfy the essential elements of the Canadian offence; or (3) by a combination of both. Key rules: (a) HYBRID DEEMING -- IRPA s. 36(3)(a): an offence prosecutable either way in Canada is deemed indictable, at its indictable maximum, even if it would likely proceed summarily; (b) TIMING -- Tran v Canada, 2017 SCC 50: the maximum penalty is assessed as of the DATE OF COMMISSION, so a later increase in the Canadian maximum does not retroactively convert criminality into serious criminality; (c) THRESHOLDS -- Canadian property offences split at $5,000 (theft, fraud, mischief), so the value in the foreign record often decides the branch; (d) the comparison is with the CANADIAN offence's elements -- a foreign offence broader than its Canadian counterpart requires the evidence branch of Hill to see what was actually done; (e) purely SUMMARY Canadian equivalents (e.g. causing a disturbance, s. 175) cannot ground s. 36(2)(b) inadmissibility on a single conviction -- only the two-or-more-convictions branch reaches them."
9
+ }
10
+ ],
11
+ "pairings": [
12
+ {
13
+ "id": "dui",
14
+ "us_terms": [
15
+ "DUI",
16
+ "DWI",
17
+ "OWI",
18
+ "OUI",
19
+ "driving under the influence",
20
+ "driving while intoxicated",
21
+ "operating under the influence",
22
+ "drunk driving",
23
+ "impaired driving"
24
+ ],
25
+ "canadian_offence": "Criminal Code s. 320.14 (operation while impaired / over the legal limit); punishment s. 320.19",
26
+ "penalty": "Hybrid; indictable maximum 10 years (s. 320.19(1)(a): 'liable to imprisonment for a term of not more than 10 years')",
27
+ "branch": "SERIOUS CRIMINALITY -- IRPA s. 36(1)(b): via s. 36(3)(a) hybrid deeming, a single ordinary US DUI conviction equates to an offence punishable by 10 years",
28
+ "status": "judicially-considered",
29
+ "analysis": "The single most consequential everyday pairing: one US misdemeanor DUI grounds serious criminality, because Canada's impaired-driving offence is hybrid with a 10-year indictable maximum since December 18, 2018. Deemed rehabilitation is therefore unavailable (10-year-or-more maximum); only individual rehabilitation or a TRP cures it.",
30
+ "caveats": [
31
+ "TIMING (Tran, 2017 SCC 50): for offences COMMITTED before December 18, 2018, the equivalent was the former s. 253 (maximum 5 years) -- criminality under s. 36(2)(b) only, not serious criminality, and deemed rehabilitation remains available.",
32
+ "Zero-tolerance administrative per-se findings are not convictions (see the DUI entry in the dispositions helper); this pairing addresses criminal DUI convictions.",
33
+ "A US 'wet reckless' plea-down maps to careless/dangerous driving analysis, not s. 320.14 -- check what was actually convicted."
34
+ ],
35
+ "authorities": [
36
+ {
37
+ "cite": "Tran v Canada (Public Safety), 2017 SCC 50",
38
+ "court": "Supreme Court of Canada",
39
+ "holding": "The maximum term for equivalency/seriousness purposes is assessed as of the date of commission; subsequent increases do not apply retrospectively."
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "id": "theft-under",
45
+ "us_terms": [
46
+ "petty theft",
47
+ "petit larceny",
48
+ "shoplifting",
49
+ "misdemeanor theft",
50
+ "theft under"
51
+ ],
52
+ "canadian_offence": "Criminal Code s. 334(b) (theft not exceeding $5,000)",
53
+ "penalty": "Hybrid; indictable maximum 2 years (s. 334(b)(i))",
54
+ "branch": "CRIMINALITY -- IRPA s. 36(2)(b) (foreign national); a permanent resident is NOT inadmissible on this alone",
55
+ "status": "guidance-only",
56
+ "analysis": "Ordinary shoplifting equates to theft under $5,000: hybrid, deemed indictable, 2-year maximum. One conviction makes a foreign national inadmissible for criminality; two convictions not arising from a single occurrence also engage s. 36(2)(b). Deemed rehabilitation after 10 years (single) per IRPR s. 18(2)(a).",
57
+ "caveats": [
58
+ "Convert the stolen value at the offence date: a US 'felony grand theft' at a low state threshold (some states: $500+) may still be theft UNDER $5,000 in Canadian terms -- the CANADIAN threshold controls the branch.",
59
+ "Value over $5,000 -> s. 334(a), 10-year maximum -> serious criminality (see theft-over)."
60
+ ],
61
+ "authorities": []
62
+ },
63
+ {
64
+ "id": "theft-over",
65
+ "us_terms": [
66
+ "grand theft",
67
+ "grand larceny",
68
+ "felony theft",
69
+ "auto theft",
70
+ "grand theft auto"
71
+ ],
72
+ "canadian_offence": "Criminal Code s. 334(a) (theft exceeding $5,000); motor vehicle theft s. 333.1",
73
+ "penalty": "Indictable maximum 10 years (s. 334(a): 'not more than 10 years'); motor vehicle theft (s. 333.1) hybrid, 10 years",
74
+ "branch": "SERIOUS CRIMINALITY -- IRPA s. 36(1)(b), where the value genuinely exceeds $5,000 (or the subject is a motor vehicle under s. 333.1)",
75
+ "status": "guidance-only",
76
+ "analysis": "Theft of property over $5,000 (Canadian dollars, at the offence date) is a 10-year offence: serious criminality; no deemed rehabilitation.",
77
+ "caveats": [
78
+ "The US felony label does not control -- verify the actual value against the Canadian $5,000 line; borderline values with exchange-rate movement deserve the evidence branch of Hill."
79
+ ],
80
+ "authorities": []
81
+ },
82
+ {
83
+ "id": "assault-simple",
84
+ "us_terms": [
85
+ "simple assault",
86
+ "misdemeanor assault",
87
+ "battery",
88
+ "simple battery",
89
+ "assault in the fourth degree"
90
+ ],
91
+ "canadian_offence": "Criminal Code s. 266 (assault)",
92
+ "penalty": "Hybrid; indictable maximum 5 years (s. 266(a): 'not exceeding five years')",
93
+ "branch": "CRIMINALITY -- IRPA s. 36(2)(b)",
94
+ "status": "guidance-only",
95
+ "analysis": "US simple assault/battery generally equates to s. 266: hybrid, 5-year maximum -- criminality, not serious criminality. Deemed rehabilitation available after 10 years (single conviction).",
96
+ "caveats": [
97
+ "Bodily injury, a weapon, or choking moves the equivalent to s. 267 (10 years) -> serious criminality (see assault-aggravated).",
98
+ "Domestic-violence variants equate to the same assault provisions; the DV character matters to sentencing and to some US dispositions, not to the Canadian equivalency.",
99
+ "Some US assault statutes cover mere threats -- threat-only conduct maps to s. 264.1 (uttering threats, 5 years), same branch."
100
+ ],
101
+ "authorities": []
102
+ },
103
+ {
104
+ "id": "assault-aggravated",
105
+ "us_terms": [
106
+ "aggravated assault",
107
+ "assault with a deadly weapon",
108
+ "ADW",
109
+ "felony assault",
110
+ "assault causing injury",
111
+ "domestic battery with injury"
112
+ ],
113
+ "canadian_offence": "Criminal Code s. 267 (assault with a weapon / causing bodily harm / choking); s. 268 (aggravated assault)",
114
+ "penalty": "s. 267: hybrid, indictable maximum 10 years; s. 268: indictable, maximum 14 years",
115
+ "branch": "SERIOUS CRIMINALITY -- IRPA s. 36(1)(b)",
116
+ "status": "guidance-only",
117
+ "analysis": "Weapon involvement or bodily harm lifts the Canadian equivalent to s. 267 (10 years) or s. 268 (wounding/maiming, 14 years): serious criminality either way; no deemed rehabilitation.",
118
+ "caveats": [
119
+ "US 'aggravated' labels sometimes rest only on the victim's status (e.g. assault on an officer) -- if no weapon/injury, the closer equivalent may be s. 270 (assaulting an officer, hybrid 5 years) -> criminality only. Use the evidence branch."
120
+ ],
121
+ "authorities": []
122
+ },
123
+ {
124
+ "id": "fraud",
125
+ "us_terms": [
126
+ "fraud",
127
+ "bad checks",
128
+ "check fraud",
129
+ "insufficient funds",
130
+ "credit card fraud",
131
+ "identity theft",
132
+ "wire fraud"
133
+ ],
134
+ "canadian_offence": "Criminal Code s. 380 (fraud); s. 342 (credit card offences); s. 402.2 (identity theft)",
135
+ "penalty": "s. 380(1)(a): indictable maximum 14 years where value exceeds $5,000; s. 380(1)(b): hybrid, 2 years where value $5,000 or less",
136
+ "branch": "SERIOUS CRIMINALITY over $5,000 (36(1)(b)); CRIMINALITY at or under $5,000 (36(2)(b))",
137
+ "status": "guidance-only",
138
+ "analysis": "Fraud splits on the $5,000 line exactly like theft. Bad-check convictions are usually small-value: hybrid 2-year equivalents -> criminality. Larger frauds (14 years) are serious criminality.",
139
+ "caveats": [
140
+ "Many US bad-check statutes criminalize mere non-payment without dishonesty elements -- where intent to defraud is not an element, the Canadian equivalent may fail on essential elements; use the Hill evidence branch."
141
+ ],
142
+ "authorities": []
143
+ },
144
+ {
145
+ "id": "drug-possession",
146
+ "us_terms": [
147
+ "drug possession",
148
+ "possession of a controlled substance",
149
+ "simple possession",
150
+ "cocaine possession",
151
+ "meth possession",
152
+ "narcotics possession"
153
+ ],
154
+ "canadian_offence": "CDSA s. 4(1) (possession); punishment s. 4(3)-(6) by schedule",
155
+ "penalty": "Schedule I (cocaine, heroin, meth, fentanyl): hybrid, indictable maximum 7 years (s. 4(3)(a): 'not exceeding seven years'); Schedule III: hybrid, 3 years",
156
+ "branch": "CRIMINALITY -- IRPA s. 36(2)(b) (7-year maximum is below the 10-year serious-criminality line)",
157
+ "status": "guidance-only",
158
+ "analysis": "Simple possession of hard drugs equates to CDSA s. 4(1): hybrid, 7-year maximum for Schedule I -- criminality, NOT serious criminality. Deemed rehabilitation available after 10 clean years for a single conviction.",
159
+ "caveats": [
160
+ "CANNABIS IS DIFFERENT: see the cannabis entry -- simple possession within legal limits is no longer an offence in Canada at all.",
161
+ "Possession WITH INTENT to distribute equates to trafficking (see drug-trafficking): a completely different branch."
162
+ ],
163
+ "authorities": []
164
+ },
165
+ {
166
+ "id": "cannabis-possession",
167
+ "us_terms": [
168
+ "marijuana possession",
169
+ "cannabis possession",
170
+ "possession of marijuana",
171
+ "weed possession"
172
+ ],
173
+ "canadian_offence": "None for simple possession within legal limits -- the Cannabis Act legalized adult possession of up to 30 g of dried cannabis (public)",
174
+ "penalty": "Not an offence in Canada (within limits); over-limit possession: Cannabis Act s. 8, hybrid, maximum 5 years less a day",
175
+ "branch": "NO INADMISSIBILITY for simple possession equivalents: with no equivalent Canadian offence, neither s. 36(1)(b) nor s. 36(2)(b) can be made out",
176
+ "status": "guidance-only",
177
+ "analysis": "A US conviction for simple marijuana possession (an amount lawful in Canada) has no Canadian equivalent offence since October 17, 2018 -- there is nothing to equate, so the conviction cannot ground criminal inadmissibility. This is the clearest 'NO' in the pairing table.",
178
+ "caveats": [
179
+ "Quantities beyond Canadian legal limits, distribution, sale, cultivation beyond limits, and import/export DO have Cannabis Act equivalents (ss. 8-14; import/export max 14 years -> serious criminality).",
180
+ "The equivalency is assessed against Canadian law at the ADMISSIBILITY DECISION date for the would-it-be-an-offence question -- post-legalization assessments of old possession convictions find no equivalent offence.",
181
+ "US felony marijuana TRAFFICKING remains serious criminality (Cannabis Act s. 9/10 or CDSA analysis)."
182
+ ],
183
+ "authorities": []
184
+ },
185
+ {
186
+ "id": "drug-trafficking",
187
+ "us_terms": [
188
+ "drug trafficking",
189
+ "possession with intent to distribute",
190
+ "PWID",
191
+ "drug distribution",
192
+ "sale of a controlled substance",
193
+ "drug dealing"
194
+ ],
195
+ "canadian_offence": "CDSA s. 5 (trafficking / possession for the purpose)",
196
+ "penalty": "Schedule I or II: indictable, maximum LIFE (s. 5(3)(a): 'liable to imprisonment for life'); Schedule III/V: hybrid, 10 years",
197
+ "branch": "SERIOUS CRIMINALITY -- IRPA s. 36(1)(b) for any schedule (all branches reach at least 10 years)",
198
+ "status": "guidance-only",
199
+ "analysis": "Any trafficking or possession-with-intent conviction equates to CDSA s. 5: serious criminality at every schedule level. No deemed rehabilitation; individual rehabilitation after 5 years or TRP only.",
200
+ "caveats": [
201
+ "Also engages potential organized-criminality analysis (s. 37) where a criminal organization is involved."
202
+ ],
203
+ "authorities": []
204
+ },
205
+ {
206
+ "id": "disorderly-conduct",
207
+ "us_terms": [
208
+ "disorderly conduct",
209
+ "disturbing the peace",
210
+ "public disturbance",
211
+ "breach of the peace",
212
+ "public intoxication"
213
+ ],
214
+ "canadian_offence": "Criminal Code s. 175 (causing a disturbance)",
215
+ "penalty": "SUMMARY ONLY -- s. 175 is a pure summary conviction offence",
216
+ "branch": "A SINGLE conviction grounds NO inadmissibility: s. 36(2)(b) requires an indictable-equivalent, and s. 175 is not one. TWO OR MORE convictions (not from a single occurrence) engage s. 36(2)(b)'s second branch",
217
+ "status": "guidance-only",
218
+ "analysis": "The most common harmless record: one US disorderly conduct/disturbing-the-peace conviction, equated to s. 175 (summary only), makes NOBODY inadmissible -- not even a foreign national. The two-or-more branch changes that, and IRPR s. 18(2)(b) then offers deemed rehabilitation five years after the sentences.",
219
+ "caveats": [
220
+ "Check what the conviction actually covers: some US disorderly statutes bundle assaultive or threatening conduct that would equate to hybrid offences -- the evidence branch of Hill controls where the statute is broader than s. 175.",
221
+ "Public intoxication alone typically has no federal Canadian equivalent at all (provincial regulatory matter) -- even cleaner than s. 175."
222
+ ],
223
+ "authorities": []
224
+ },
225
+ {
226
+ "id": "resisting-arrest",
227
+ "us_terms": [
228
+ "resisting arrest",
229
+ "obstruction",
230
+ "obstructing an officer",
231
+ "evading arrest",
232
+ "failure to comply with police"
233
+ ],
234
+ "canadian_offence": "Criminal Code s. 129 (resisting or obstructing a peace officer)",
235
+ "penalty": "Hybrid; indictable maximum 2 years (s. 129(d): 'not exceeding two years')",
236
+ "branch": "CRIMINALITY -- IRPA s. 36(2)(b)",
237
+ "status": "guidance-only",
238
+ "analysis": "Non-violent resisting/obstruction equates to s. 129: hybrid, 2-year maximum -- criminality only.",
239
+ "caveats": [
240
+ "Vehicle flight equates to s. 320.17 (flight from police: hybrid, 10 years) -> SERIOUS criminality -- distinguish on-foot obstruction from vehicular flight.",
241
+ "Assaulting the officer moves it to s. 270 or s. 267 territory."
242
+ ],
243
+ "authorities": []
244
+ },
245
+ {
246
+ "id": "threats",
247
+ "us_terms": [
248
+ "criminal threatening",
249
+ "terroristic threats",
250
+ "menacing",
251
+ "harassment threats",
252
+ "intimidation"
253
+ ],
254
+ "canadian_offence": "Criminal Code s. 264.1 (uttering threats)",
255
+ "penalty": "Hybrid; indictable maximum 5 years for threats to cause death or bodily harm (s. 264.1(2)(a))",
256
+ "branch": "CRIMINALITY -- IRPA s. 36(2)(b)",
257
+ "status": "guidance-only",
258
+ "analysis": "Threat offences equate to s. 264.1: hybrid, 5-year maximum -- criminality. Criminal harassment courses of conduct equate to s. 264 (10 years) -> serious criminality instead.",
259
+ "caveats": [
260
+ "'Terroristic threats' is a state-law label with no terrorism content -- do not confuse with IRPA s. 34 security grounds absent actual terrorism facts.",
261
+ "Repeated/stalking conduct: s. 264 criminal harassment (10 years) -> serious criminality."
262
+ ],
263
+ "authorities": []
264
+ },
265
+ {
266
+ "id": "mischief-vandalism",
267
+ "us_terms": [
268
+ "vandalism",
269
+ "criminal mischief",
270
+ "malicious mischief",
271
+ "property damage",
272
+ "graffiti"
273
+ ],
274
+ "canadian_offence": "Criminal Code s. 430 (mischief)",
275
+ "penalty": "Value over $5,000: hybrid, indictable maximum 10 years (s. 430(3)); $5,000 or under: hybrid, 2 years (s. 430(4))",
276
+ "branch": "CRIMINALITY at or under $5,000 (36(2)(b)); SERIOUS CRIMINALITY over $5,000 (36(1)(b))",
277
+ "status": "guidance-only",
278
+ "analysis": "Vandalism splits on the $5,000 damage value like the other property offences. Ordinary graffiti/vandalism records are 2-year equivalents -> criminality.",
279
+ "caveats": [
280
+ "The damage figure in the US record (restitution ordered is a good proxy) decides the branch -- convert to CAD at the offence date."
281
+ ],
282
+ "authorities": []
283
+ }
284
+ ]
285
+ }
tests/test_rehab.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unit tests for the rehabilitation rule engine (canlex/rehab.py).
2
+
3
+ Offline; the rule table mirrors IRPR ss. 17, 18(2)(a)-(c) and 18.1.
4
+
5
+ python -m unittest discover -s tests
6
+ """
7
+ import unittest
8
+
9
+ from canlex.rehab import assess
10
+
11
+
12
+ class DeemedRehabTests(unittest.TestCase):
13
+ def test_single_foreign_conviction_deemed_after_10_clean_years(self):
14
+ r = assess("foreign-conviction", True, "one", 10, True)
15
+ self.assertEqual(r["status"], "deemed-rehabilitated")
16
+ self.assertIn("18(2)(a)", r["provision"])
17
+
18
+ def test_not_deemed_at_9_years_but_eligible_to_apply(self):
19
+ r = assess("foreign-conviction", True, "one", 9, True)
20
+ self.assertEqual(r["status"], "eligible-to-apply")
21
+
22
+ def test_dirty_record_defeats_deemed(self):
23
+ r = assess("foreign-conviction", True, "one", 12, False)
24
+ self.assertEqual(r["status"], "eligible-to-apply")
25
+
26
+ def test_two_summary_convictions_deemed_after_5_years(self):
27
+ r = assess("foreign-conviction", True, "two-plus-summary-only", 5, True)
28
+ self.assertEqual(r["status"], "deemed-rehabilitated")
29
+ self.assertIn("18(2)(b)", r["provision"])
30
+
31
+ def test_committed_act_deemed_after_10_years(self):
32
+ r = assess("foreign-act", True, "one", 10, True)
33
+ self.assertEqual(r["status"], "deemed-rehabilitated")
34
+ self.assertIn("18(2)(c)", r["provision"])
35
+
36
+
37
+ class SeriousCriminalityTests(unittest.TestCase):
38
+ def test_never_deemed_even_after_decades(self):
39
+ r = assess("foreign-conviction", False, "one", 30, True)
40
+ self.assertEqual(r["status"], "eligible-to-apply")
41
+ self.assertIn("17", r["provision"])
42
+
43
+ def test_under_5_years_nothing_available(self):
44
+ r = assess("foreign-conviction", False, "one", 3, True)
45
+ self.assertEqual(r["status"], "not-yet")
46
+
47
+
48
+ class InCanadaTests(unittest.TestCase):
49
+ def test_record_suspension_route(self):
50
+ r = assess("canada-conviction", True, "one", 20, True)
51
+ self.assertEqual(r["status"], "record-suspension-route")
52
+ self.assertIn("36(3)(b)", r["provision"])
53
+
54
+ def test_two_summary_class_exempt_after_5_years(self):
55
+ r = assess("canada-conviction", True, "two-plus-summary-only", 5, True)
56
+ self.assertEqual(r["status"], "exempt")
57
+ self.assertIn("18.1", r["provision"])
58
+
59
+ def test_two_summary_class_not_yet_at_4_years(self):
60
+ r = assess("canada-conviction", True, "two-plus-summary-only", 4, True)
61
+ self.assertEqual(r["status"], "not-yet")
62
+
63
+
64
+ class MultipleIndictableTests(unittest.TestCase):
65
+ def test_never_deemed_apply_only(self):
66
+ r = assess("foreign-conviction", True, "multiple", 15, True)
67
+ self.assertEqual(r["status"], "eligible-to-apply")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()