BSES part 2: adding jurisdictional functionality

code
bses
Author

Daniel Booy

Published

July 16, 2023

Modified

July 18, 2023

Abstract
The BSES from the previous post covered most of high treason’s basics, but left out one question with some particular intra-statutory considerations — namely, jurisdiction. This next section adds this step and begins the process of incorporating other aspects of the Criminal Code into the program.

Basic statutory expert system 2 — jurisdiction

For a defendant to be guilty of a Canadian criminal offence, the offence must have been committed “in Canada.” In most cases, determining whether a crime was committed in Canada is straightforward. But in rare instances, Canada will have jurisdiction over a crime committed outside Canada. In these cases, Canadian jurisdiction must either be constructed or waived. The next set of functions goes through the necessary steps to determine whether an offence was committed “in Canada,” with an extensive focus on constructive jurisdiction.

Canadian jurisdictions

Before delving into constructive jurisdiction, I outline a general system for dealing with jurisdiction. The first (and most straightforward) way to understand an offender committing an offence “in Canada” is for that offence to have geographically occurred in Canada. In virtually every case, the crime will allegedly have happened in Canada geographically, so it is crucial to implement this functionality early on. Doing so requires another set of constants and some code to use the Facts.jurisdiction attribute.

CANADIAN_PROVINCES: dict[str, tuple[str, str]] = {
    'Alberta': ('AB', 'Alta'),
    'British Columbia': ('BC', 'B.C.'),
    'Manitoba': ('MB', 'Man.'),
    'New Brunswick': ('NB', 'N.B.'),
    'Newfoundland and Labrador': ('NL', 'N.L.'),
    'Nova Scotia': ('NS', 'N.S.'),
    'Ontario': ('ON', 'Ont.'),
    'Prince Edward Island': ('PE', 'P.E.I.'),
    'Quebec': ('QC', 'Que.'),
    'Saskatchewan': ('SK', 'Sask.')
}

CANADIAN_TERRITORIES: dict[str, tuple[str, str]] = {
    'Northwest Territories': ('NT', 'N.W.T.'),
    'Nunavut': ('NU', 'Nvt.'),
    'Yukon': ('YT', 'Y.T.')
}

Jurisdiction verification

With that preliminary jurisdictional matrix implemented, the next step is determining whether the jurisdiction entered matches one of the geographical Canadian jurisdictions. Legal jurisdiction is not something inherent to the offence itself or the defendant. Rather, jurisdiction is primarily determined based on the location of the crime. It therefore makes sense to situate jurisdictional functions within the Facts class.1

Handling geographic jurisdiction via verify_canadian_jurisdiction()

The verify_canadian_jurisdiction() function checks whether the given jurisdiction string matches any Canadian provincial, territorial, or federal jurisdiction. If so, the function determines that the offence occurred “in Canada,” thus making out general jurisdiction.

    from typing import Optional
    def verify_canadian_jurisdiction(self,
                                     jurisdiction: str, 
                                     provinces: dict[str, tuple[str, str]] = CANADIAN_PROVINCES, 
                                     territories: dict[str, tuple[str, str]] = CANADIAN_TERRITORIES) -> tuple[bool, Optional[str]]:
        """
        This function checks if a given jurisdiction is Canadian.

        The function accepts a string representing a jurisdiction and two 
        optional dictionaries representing provinces and territories. Each 
        dictionary maps a region's name to a tuple consisting of its 
        abbreviation and French translation. The function checks if the 
        jurisdiction matches the name, abbreviation, or French translation of 
        any of the provinces or territories. The function also checks if the 
        jurisdiction is "canada" or its abbreviation "ca".

        If the program finds a match, the function returns True; otherwise, it returns 
        False.

        Args:
            jurisdiction (str): The jurisdiction to check.
            provinces (dict[str, tuple[str, str]], optional): A dictionary of 
            Canadian provinces. Defaults to CANADIAN_PROVINCES.
            territories (dict[str, tuple[str, str]], optional): A dictionary of
            Canadian territories. Defaults to CANADIAN_TERRITORIES.

        Returns:
            tuple[bool, Optional[str]]: A tuple where the first element is a 
            boolean indicating if the jurisdiction is recognized, and the 
            second element is an optional string message.
        """
        
        if jurisdiction.lower() in ('canada', 'ca'):
            return True

        for region in [provinces, territories]:
            for key, value in region.items():
                if jurisdiction.lower() in [key.lower(), value[0].lower(), value[1].lower()]:
                    return True
        return False

User-generated facts via create_facts()

Previously, the create_facts function was part of the main.py program execution file. While this was sufficient for a minimally-viable product, more workable solutions exist. For example, there will be times when we want the program to run without having to go through a lengthy checklist of questions, especially when the user may enter that information directly into a Facts class object.

For now, the create_facts method can continue to have high_treason_facts assigned to it through hard coding. I will eventually start siphoning off processes related to high treason and other offences into separate functions and possibly a separate Offence class.

       def create_facts(self):
        """
        Interactively collects details about the offence and stores them as instance attributes.

        This method asks the user for details about the victim, offence date, jurisdiction, and actions related to the offence. 
        The program stores these details as instance attributes for future use.
        
        Note:
            This method uses the `input` function interactively.
        """
        
        print("Please enter the facts of the case:")

        victim_category = input("Who is the victim? ")
        victim_category = standardize_sovereign_names(victim_category)
        victim_category = standardize_canada_names(victim_category)
        complainant = Complainant(victim_category)

        offence_date = input("Date of the offence (YYYY-MM-DD): ")
        jurisdiction = input("Jurisdiction: ")
        self.jurisdiction = self.verify_canadian_jurisdiction(jurisdiction)

        actions = high_treason_facts(victim_category)

        self.victim_category = complainant.name
        self.offence_date = offence_date
        self.actions = actions

Constructive jurisdiction

The code thus far covers geographical jurisdiction. But the jurisdictional question still needs to be fully resolved. In rare circumstances, the Criminal Code allows the court to construe Canadian jurisdiction constructively, even if an offence didn’t occur “in Canada.” Furthermore, for certain classes of people, the jurisdictional requirement for high treason is waived, while for other categories of people, Canadian jurisdiction may exist regardless of where a defendant commits an offence. These edge cases include:

  • Public Service employees. Public Service employees are deemed to have committed an offence in Canada regardless of where they committed it.
  • Canadian citizens and allegiance-owers. For treason and high treason specifically, a Canadian citizen, or anyone who owes allegiance to the sovereign, will have committed treason or high treason regardless of whether the jurisdictional component obtains. Unlike Public Service employees, who have jurisdiction constructed regardless of where they commit crimes, this exception obviates the jurisdictional requirement.
  • Aircraft, spacecraft, the space station and the lunar surface. Crimes committed in specific unorthodox locations will be deemed committed in Canada if certain conditions obtain. The requirements and exceptions are several, as will be seen below.

Public Service employees

Public Service employees are a unique class in the Criminal Code, in that they alone are deemed to have committed an offence “in Canada” regardless of where they commit a crime. They appear to be the only class singled out in this way in the Criminal Code. The statutory authority for this also extends to another act — the Public Service Employment Act, SC 2003, c 22, ss 12, 13.

CC s 7(4) — Offences by Public Service employees

Every one who, while employed as an employee within the meaning of the Public Service Employment Act in a place outside Canada, commits an act or omission in that place that is an offence under the laws of that place and that, if committed in Canada, would be an offence punishable by indictment shall be deemed to have committed that act or omission in Canada.

PSEA 2(1) — definition: employee

employee means a person employed in that part of the public service to which the Commission has exclusive authority to make appointments. (fonctionnaire)

PSEA 29(1) - Commission’s exclusive authority

Except as provided in this Act, the Commission has the exclusive authority to make appointments, to or from within the public service, of persons for whose appointment there is no authority in or under any other Act of Parliament.

Determining who exactly qualifies as a Public Service employee, then, requires that:

  • They are a public service employee;
  • The Public Service Commission may appoint that employee; and
  • No other [federal] statutory authority exists to appoint that employee.
PUBLIC_EMPLOYEE_CRITERIA = ("Is the defendant a public service employee?",
                            "Does the Public Service Commission have the authority to appoint the defendant?",
                            "Does any other federal statute confer authority to appoint the defendant?")

OTHER_ACTS_OF_PARLIAMENT = {
    
}

Querying the user about public employment via public_employee_test()

Iff all three indices are True, the individual is a Public Service employee within the meaning of the PSEA and the Criminal Code. But answering this question correctly requires additional knowledge about the Public Service Commission’s exclusive authority, which in turn requires additional knowledge about the scope of all other Acts of Parliament that cordon off appointment authority, if any. While I don’t know whether there are any such statutes, I’ve built the function to incorporate them if they exist. If they don’t, adding this functionality can help future-proof the function if Parliament passes any such acts in the future.

We can incorporate these questions into a simple UI method for the Defendant class that determines whether a person is a public employee, as the Criminal Code understands the term. The function returns True iff all conditions presented are True.

    def public_employee_test(self):
        """
        Verifies whether a defendant is a public employee as understood by the 
        Criminal Code.
        """
        
        for question in PUBLIC_EMPLOYEE_CRITERIA:
            response = input(question + " (yes/no): ")
            if response.strip().lower() != 'yes':
                return
        self.vocation.append("public employee")

Canadian citizens and allegiance-owers

CC 46(3) — Canadian citizen

Notwithstanding subsection (1) or (2), a Canadian citizen or a person who owes allegiance to Her Majesty in right of Canada,

  1. commits high treason if, while in or out of Canada, he does anything mentioned in subsection (1); or

  2. commits treason if, while in or out of Canada, he does anything mentioned in subsection (2).

Because the statute doesn’t define allegiance, I must make some assumptions. Primarily, I assume that a person owes allegiance to the sovereign iff they are a Commonwealth country citizen. If this theory holds, we can use a dictionary to check whether a defendant owes allegiance to the sovereign. This dictionary lists demonyms as values and tuples containing country names as keys. Because Canada is also a Commonwealth country, distinguishing the two programmatically is optional.

COMMONWEALTH_DEMONYMS = {
    "antigua and barbuda": ["antiguan", 
                            "barbudan"],
    "australia": ["australian"],
    "the bahamas": ["bahamian"],
    "bangladesh": ["bangladeshi"],
    "barbados": ["barbadian", 
                 "bajan"],
    "belize": ["belizean"],
    "botswana": ["botswanan"],
    "brunei darussalam": ["bruneian"],
    "cameroon": ["cameroonian"],
    "canada": ["canadian"],
    "cyprus": ["cypriot"],
    "dominica": ["dominican",
                 "dominican commonwealth"],
    "eswatini": ["swazi"],
    "fiji": ["fijian"],
    "gambia": ["gambian"],
    "ghana": ["ghanaian"],
    "grenada": ["grenadian"],
    "guyana": ["guyanese"],
    "india": ["indian"],
    "jamaica": ["jamaican"],
    "kenya": ["kenyan"],
    "kiribati": ["i-kiribati"],
    "lesotho": ["mosotho", 
                "basotho"],
    "malawi": ["malawian"],
    "malaysia": ["malaysian"],
    "maldives": ["maldivian"],
    "malta": ["maltese"],
    "mauritius": ["mauritian"],
    "mozambique": ["mozambican"],
    "namibia": ["namibian"],
    "nauru": ["nauruan"],
    "new zealand": ["new zealander", 
                    "kiwi"],
    "nigeria": ["nigerian"],
    "pakistan": ["pakistani"],
    "papua new guinea": ["papuan", 
                         "guinean"],
    "rwanda": ["rwandan"],
    "saint lucia": ["saint lucian"],
    "samoa": ["samoan"],
    "seychelles": ["seychellois"],
    "sierra leone": ["sierra leonean"],
    "singapore": ["singaporean"],
    "solomon islands": ["solomon islander"],
    "south africa": ["south african"],
    "sri lanka": ["sri lankan"],
    "st kitts and nevis": ["kittitian", "nevisian"],
    "st vincent and the grenadines": ["vincentian"],
    "tanzania": ["tanzanian"],
    "tonga": ["tongan"],
    "trinidad and tobago": ["trinidadian", 
                            "tobagonian"],
    "tuvalu": ["tuvaluan"],
    "uganda": ["ugandan"],
    "united kingdom": ["british", 
                       "english", 
                       "scottish", 
                       "welsh", 
                       "northern irish"],
    "vanuatu": ["vanuatuan"],
    "zambia": ["zambian"],
}

Checking citizenship and allegiance to the sovereign with allegiance_test()

With this list in place, I next need to code a function that can check a defendant’s citizenship attribute against this dictionary to determine whether they owe the sovereign allegiance. Because a defendant’s citizenship is generally an attribute about them and not about the offence’s facts, it makes sense to turn this into a Defendant attribute. But because the allegiance question so rarely comes up, future iterations of the Defendant class may move this function into an Offence function or out into the general namespace.

def allegiance_test(self):
    """
    Determines whether a defendant owes allegiance to the sovereign based
    on their citizenship. The method iterates over the list of citizenships
    and checks each one. If the individual has "dominican" citizenship, 
    the program must further determine whether the individual is a citizen
    of Dominica (Commonwealth) or the Dominican Republic (not Commonwealth).

    For "dominican" citizenship, it invokes the _handle_dominican method to handle
    the special case. For other citizenships, it checks if they are Commonwealth
    using the _is_commonwealth_citizen method.

    If it finds Commonwealth citizenship, it immediately returns True. If none 
    of the citizenships are Commonwealth, it returns False. If the citizenship list is 
    empty or not provided, it also returns False.

    Returns:
        bool: True if the defendant has Commonwealth citizenship, False otherwise.
    """

    if self.citizenship:
        for i, citizenship in enumerate(self.citizenship):
            if citizenship.lower() == "dominican":
                self.citizenship[i] = self._handle_dominican()
                citizenship = self.citizenship[i]  # update citizenship value with new string

            if self._is_commonwealth_citizen(citizenship):
                return True
        return False
    else:
        return False

Private support functions _is_commonwealth_citizen() and _handle_dominican()

Testing for a defendant’s allegiance contemplates two private methods: namely, the _handle_dominican() method and the _is_commonwealth_citizen() method. _is_commonwealth_citizen() checks whether a given citizenship belongs to a Commonwealth country, while _handle_dominican() prompts the user for more input if a defendant has “dominican” citizenship. This is because a “Dominican” citizen may be from the Dominican Republic, which is not a Commonwealth country, or Dominica, which is. I’ve coded these methods as follows:

    def _is_commonwealth_citizen(self, citizenship):
        """
        A private method that checks if a given citizenship belongs to a 
        Commonwealth country.

        Args:
            citizenship (str): The citizenship string to check. This should be a 
            lowercase string that denotes a country or its demonym.

        Returns:
            bool: True if the citizenship is in the list of Commonwealth countries 
            or demonyms, False otherwise.

        Note:
            This method assumes that `COMMONWEALTH_DEMONYMS` is a dictionary 
            available in the global scope, where the keys are countries and 
            the values are lists of associated demonyms.
        """
        
        return any(citizenship.lower() in demonyms for demonyms in COMMONWEALTH_DEMONYMS.values())
    
    def _handle_dominican(self):
        """
        This is a private method that checks if a "dominican" citizenship belongs 
        to the Commonwealth country Dominica or the non-Commonwealth country, 
        the Dominican Republic.

        Args:
            index (int): The "dominican" citizenship index in the citizenship list.

        Returns:
            None
        """
        
        while True:
            print("Are you from (1) Dominica or (2) the Dominican Republic?")
            response = input("Please enter 1 or 2: ").strip()
            if response == "1":
                return "dominican commonwealth"
            elif response == "2":
                return "dominican not commonwealth"
            else:
                print("Invalid input. Please enter 1 or 2.")

Aircraft, spacecraft, the space station and the moon

The final step in settling jurisdiction is to deal with offences committed on aircraft, spacecraft, the ISS, the Lunar Gateway, or the lunar surface. Although the high treason system uses the code here, it may have a broader application later within future Criminal Code offences. This general application notwithstanding, this jurisdictional section is unlikely to capture much criminal activity.

Conceptually, these different locales are divisible into two distinct groups:

  • Aircraft offences
  • Spacecraft, the ISS, the Lunar Gateway, and the lunar surface

The main difference between these two groups is the Partner State consideration in the latter group.

CC s 7(1) — Offences committed on aircraft

Notwithstanding anything in this Act or any other Act, every one who

  1. on or in respect of an aircraft

    1. registered in Canada under regulations made under the Aeronautics Act, or
    2. leased without crew and operated by a person who is qualified under regulations made under the Aeronautics Act to be registered as owner of an aircraft registered in Canada under those regulations,

while the aircraft is in flight, or

  1. on any aircraft, while the aircraft is in flight if the flight terminated in Canada,

commits an act or omission in or outside Canada that if committed in Canada would be an offence punishable by indictment shall be deemed to have committed that act or omission in Canada.

Testing for constructive jurisdiction via aircarft using verify_constructive_jurisdiction_aircraft()

To deal with the complexities of jurisdiction for offences committed in aircraft, verify_constructive_jurisdiction_aircraft() tests for constructive jurisdiction. The Criminal Code for aircraft establishes Canadian jurisdiction when an aircraft is registered in Canada, terminates in Canada, or is legally leased in Canada, per the following control flow:

  • The defendant is deemed to have committed an offence in Canada if:
    • The defendant committed an offence on an aircraft in flight AND
    • The aircraft was registered in Canada under regulations made under the Aeronautics Act OR
    • The aircraft was leased without crew AND operated by a person who is qualified under regulations made under the Aeronautics Act to be registered as the owner of an aircraft registered in Canada under those regulations OR
    • The flight terminated in Canada

I’ve codified this control flow as a Facts class method, adding constructive jurisdiction to a case’s facts if sufficient facts exist to do so.

def verify_constructive_jurisdiction_aircraft(self):
    """
    Verifies whether an offence committed on an aircraft falls under Canadian jurisdiction and updates the jurisdiction attribute accordingly.

    This method asks the user a series of questions to determine if the offence falls under the Canadian statute when committed on an aircraft. If it does, the jurisdiction is updated to include the specific location.

    Note:
        This method uses the `input` function in an interactive context.
    """

    deemed_jurisdiction = []

    offense_committed = verify_yes_no("Was the offense committed on an aircraft in flight? (yes/no): ")
    aircraft_registered = aircraft_leased = qualified_operator = flight_terminated = False

    if offense_committed:
        flight_terminated = verify_yes_no("Did the flight terminate in Canada? (yes/no): ")
        aircraft_registered = verify_yes_no("Is the aircraft\n * Registered in Canada under regulations made under the Aeronautics Act? (yes/no): ")
        aircraft_leased = verify_yes_no(" * Leased without crew operated by a person qualified under regulations made under the Aeronautics Act to be registered as owner of an aircraft registered in Canada under those regulations? (yes/no): ")
    if aircraft_leased:
        qualified_operator = verify_yes_no(" * Operated by a person who is qualified under regulations made under the Aeronautics Act to be registered as owner of an aircraft registered in Canada under those regulations? (yes/no): ")


    if aircraft_registered:
        deemed_jurisdiction.append("deemed jurisdiction (legally registered aircraft)")
    if aircraft_leased and qualified_operator:
        deemed_jurisdiction.append("deemed jurisdiction (legally leased and operated aircraft)")
    if flight_terminated:
        deemed_jurisdiction.append("deemed jurisdiction (flight terminated in Canada)")

    for item in deemed_jurisdiction:
        if item not in self.jurisdiction:
            self.jurisdiction.append(item)

CC s 7(2.3) — Space Station — Canadian crew members

This section applies to Canadian crew members during space flights involving the International Space Station. The Criminal Code deems any action or inaction by a Canadian crew member during such flights that would constitute an indictable offence in Canada to have been committed within Canada.

Despite anything in this Act or any other Act, a Canadian crew member who, during a space flight, commits an act or omission outside Canada that if committed in Canada would constitute an indictable offence is deemed to have committed that act or omission in Canada, if that act or omission is committed

  1. on, or in relation to, a flight element of the Space Station; or
  2. on any means of transportation to or from the Space Station.

CC s 7(2.31) — Space Station — crew members of Partner States

This section expands the principle to include Partner States crew members and applies when a defendant commits an act or omission that would constitute an indictable offence in Canada during a space flight involving the International Space Station.

Despite anything in this Act or any other Act, a crew member of a Partner State who commits an act or omission outside Canada during a space flight on, or in relation to, a flight element of the Space Station or on any means of transportation to and from the Space Station that if committed in Canada would constitute an indictable offence is deemed to have committed that act or omission in Canada, if that act or omission

  1. threatens the life or security of a Canadian crew member; or
  2. is committed on or in relation to, or damages, a flight element provided by Canada.

CC s 7(2.35) — Lunar Gateway — Canadian crew members

Similar provisions apply to Canadian crew members during space flights involving the Lunar Gateway or the lunar surface.

Despite anything in this Act or any other Act, a Canadian crew member who, during a space flight, commits an act or omission outside Canada that if committed in Canada would constitute an indictable offence is deemed to have committed that act or omission in Canada, if that act or omission is committed

  1. on, or in relation to, a flight element of the Lunar Gateway;
  2. on any means of transportation to or from the Lunar Gateway; or
  3. on the surface of the Moon.

CC s 7(2.36) — Lunar Gateway — crew members of Partner States

Finally, crew members of Partner States involved in space flights to or from the Lunar Gateway or on the lunar surface are subject to the same principles of constructive jurisdiction if their actions or omissions would constitute an indictable offence in Canada.

Despite anything in this Act or any other Act, a crew member of a Partner State who commits an act or omission outside Canada during a space flight on, or in relation to, a flight element of the Lunar Gateway, on any means of transportation to and from the Lunar Gateway or on the surface of the Moon that if committed in Canada would constitute an indictable offence is deemed to have committed that act or omission in Canada, if that act or omission

  1. threatens the life or security of a Canadian crew member; or
  2. is committed on or in relation to, or damages, a flight element provided by Canada.

This decision tree structure outlines the conditions that would extend Canadian jurisdiction to actions committed in space, including the International Space Station (ISS), Lunar Gateway, and the lunar surface. The control flow starts with determining whether the actor is a member or authorized agent of a Partner State.

Under this structure, for each possible location (ISS, en route to/from ISS, Lunar Gateway, en route to/from Lunar Gateway, and the lunar surface), the methods consider a range of offences, including actions that threaten the life or security of a Canadian crew member, damage a flight element provided by Canada, or are conducted concerning such a flight element:

  • Verify if the actor is a member or authorized agent of a Partner State.
  • If yes, determine the location of the offence (ISS, en route to/from ISS, Lunar Gateway, en route to/from Lunar Gateway, lunar surface).
  • At each location, check if the defendant committed an offence which:
    • Threatens the life or security of a Canadian crew member,
    • Is in relation to or damages a flight element provided by Canada.

Determining constructive jurisdiction in space using verify_constructive_jurisdiction_space()

The complexity of these control flows requires a systematic approach to determining jurisdiction. The verify_constructive_jurisdiction_space() facilitates this purpose by prompting the user with questions to assess whether a crew member’s actions fall within Canadian jurisdiction according to the criteria outlined in these sections. The function itself encapsulates our decision tree. The corresponding method assesses each offence and location if the defendant is a Partner State member:

    def verify_constructive_jurisdiction_space(self):
        # List of possible locations
        locations = ["on the iss", 
                     "en route to iss", 
                     "on the lunar gateway", 
                     "en route to/from Lunar Gateway", 
                     "on the lunar surface"]

        # Combine all offences into one list
        all_offences = ["threatend a canadian crew member's life", 
                        "threatend a canadian crew member's security", 
                        "damaged a canadian flight element", 
                        "offended in relation to a canadian flight element",
                        "damaged a canadian flight element"]

        partner_state_member = self._verify_partner_state(defendant.agency)
        
        if partner_state_member:
            for offence in all_offences:
                print(f"The defendant {offence} — (yes/no): ")
                offence_committed = input().lower()
                if offence_committed == 'yes':
                    for location in locations:
                        print(f"Did the offence take place {location}? (yes/no): ")
                        offence_location = input().lower()
                        if offence_location == 'yes':
                            self.jurisdiction.append(f"deemed jurisdiction ({offence} {location})")

Assessing Partner State membership using _verify_partner_state()

The code above contemplates a private _verify_partner_state() function and an agency variable. Partner State membership is not tied to citizenship. Instead, a person is statutorily a member of a Partner State if they are a crew member of a Partner State. To the extent that a person can be a crew member acting for a Partner State and not a citizen of that country, the two are conceptually distinct through an agency variable.

To verify Partner States, however, I must first outline what constitutes a Partner State.

CC s 7(2.34) — definition: Partner States

Agreement has the same meaning as in section 2 of the Civil International Space Station Agreement Implementation Act.

Partner State means a State, other than Canada, who contracted to enter into the Agreement and for which the Agreement has entered into force in accordance with article 25 of the Agreement.

The pertinent section of Civil International Space Station Agreement Implementation Act art 25 reads as follows:

  1. This Agreement shall remain open for signature by the States listed in the Preamble of this Agreement.

The Preamble then outlines the fact that Partner States are

An Act to implement the Agreement among the Government of Canada, Governments of Member States of the European Space Agency, the Government of Japan, the Government of the Russian Federation, and the Government of the United States of America concerning Cooperation on the Civil International Space Station and to make related amendments to other Acts.

Given the above information, a partner state is:

  • Canada
  • Japan;
  • The Russian Federation;
  • The United States of America; or
  • A member state of the ESA

ESA member states include:

  • Austria
  • Belgium
  • Czech Republic
  • Denmark
  • Estonia
  • Finland
  • France
  • Germany
  • Greece
  • Hungary
  • Ireland
  • Italy
  • Luxembourg
  • Netherlands
  • Norway
  • Poland
  • Portugal
  • Romania
  • Spain
  • Sweden
  • Switzerland
  • United Kingdom

These can be expressed as Python dictionaries, just as I did with the Commonwealth countries above:

NON_ESA_PARTNER_STATE_DEMONYMS = {
    "canada": ("canadian",),
    "japan": ("japanese",),
    "russian federation": ("russian",),
    "united states of america": ("american",)
}

ESA_PARTNER_STATE_DEMONYMS = {
    "austria": ("austrian",),
    "belgium": ("belgian",),
    "czech republic": ("czech", "czechoslovakian"),
    "denmark": ("danish",),
    "estonia": ("estonian",),
    "finland": ("finnish",),
    "france": ("french",),
    "germany": ("german",),
    "greece": ("greek",),
    "hungary": ("hungarian",),
    "ireland": ("irish",),
    "italy": ("italian",),
    "luxembourg": ("luxembourger",),
    "netherlands": ("dutch",),
    "norway": ("norwegian",),
    "poland": ("polish",),
    "portugal": ("portuguese",),
    "romania": ("romanian",),
    "spain": ("spanish",),
    "sweden": ("swedish",),
    "switzerland": ("swiss",),
    "united kingdom": ("british", "scottish", "english", "welsh", "irish")
}

Checking Partner State membership using _verify_partner_state()

Once the Partner States are archived in the dictionary, we need a method to determine whether a particular defendant’s agency variable correlates to a Partner State value:

    def _verify_partner_state(self, memberships):
        """
        This is a private method that checks if any given citizenship belongs to a 
        Partner State country.

        Args:
            citizenships (list): The list of citizenships to check. Each should be a 
            lowercase string that denotes a country or its demonym.

        Returns:
            bool: True if any of the citizenships is in the list of Partner States' countries 
            or demonyms, False otherwise.
        """

        # Convert the dictionary values (which are tuples) into sets for easy searching
        non_esa_demonyms = set(val for sublist in NON_ESA_PARTNER_STATE_DEMONYMS.values() for val in sublist)
        esa_demonyms = set(val for sublist in ESA_PARTNER_STATE_DEMONYMS.values() for val in sublist)

        for membership in memberships:
            if membership in non_esa_demonyms or membership in esa_demonyms:
                return True

        return False

Jurisdiction verification through verify_high_treason()

The final step is to code a function that will check to determine whether there is jurisdiction for an offence by checking both Facts and Defendant objects to see whether the jurisdictional preconditions are met. Because high treason has its own form of constructive jurisdiction, the control flow will have to account for that:

  • Is the offence committed in Canada?
    • Is the defendant jurisdictionally exempt?
    • Can jurisdiction be constructed?
      • Aircraft jurisdiction
      • Space jurisdiction
    • Is the offence treason?
      • Does the defendant owe allegiance to Canada?

The verify_high_treason() function determines whether the offence is made out. The previous verify_high_treason() function simply checked to see if matches had any content. If it did, it would print them out. But jurisdiction introduces a new issue: we can’t say a defendant committed high treason unless we also establish that the offence occurred in Canada. We must expand ``verify_high_treason()```’s scope to do this. For now, this function is class-independent.2

def verify_high_treason(matches, facts, defendant):
    """
    Determines whether the factual and legal matrix is sufficient to make out 
    high treason.
    """
    
    reasons = []

    if not matches:
        reasons.append("* No wrongful act")

    # Check allegiance
    if not defendant.allegiance_test():
        reasons.append("* Defendant does not owe allegiance to the sovereign")

    # If defendant doesn't owe allegiance, then we also check for jurisdiction
    if not defendant.allegiance_test() and facts.jurisdiction == "not recognized":
        reasons.append("* No jurisdiction")

    # If there are any reasons in the list, print them out. 
    # Otherwise, high treason has been committed.
    if reasons:
        print("High treason not committed:")
        for reason in reasons:
            print(reason)
    else:
        print("High treason committed. Matches:")
        for match in matches:
            print(match)

Conclusion

The basic statutory expert system now accounts for jurisdiction and high treason’s offence elements. The next step will be to create functions that allow the program to deal with case law, interpretive ambiguities, and legal uncertainties.

In the first post, I cited high treason’s limited case law base as a reason for building an expert system around it. Because case law introduces new legal rules, nuances, definitions, and applications, accounting for case law necessarily complicates statutory interpretation control flow. Bypassing these complications for a minimally-viable product is a sensible way to start. But once we have that MVP, we need a system that accounts for legal ambiguities and tells the user when more than one conclusion is possible. Because high treason has limited court consideration, these potential complications will be much fewer than those introduced in frequently-charged offences.

Complete code

Constants

CANADIAN_PROVINCES: dict[str, tuple[str, str]] = {
    'Alberta': ('AB', 'Alta'),
    'British Columbia': ('BC', 'B.C.'),
    'Manitoba': ('MB', 'Man.'),
    'New Brunswick': ('NB', 'N.B.'),
    'Newfoundland and Labrador': ('NL', 'N.L.'),
    'Nova Scotia': ('NS', 'N.S.'),
    'Ontario': ('ON', 'Ont.'),
    'Prince Edward Island': ('PE', 'P.E.I.'),
    'Quebec': ('QC', 'Que.'),
    'Saskatchewan': ('SK', 'Sask.')
}

CANADIAN_TERRITORIES: dict[str, tuple[str, str]] = {
    'Northwest Territories': ('NT', 'N.W.T.'),
    'Nunavut': ('NU', 'Nvt.'),
    'Yukon': ('YT', 'Y.T.')
}

PUBLIC_EMPLOYEE_CRITERIA = ("Is the defendant a public service employee?",
                            "Does the Public Service Commission have authority to appoint the defendant?",
                            "Does any other federal statute confer authority to appoint the defendant?")

OTHER_ACTS_OF_PARLIAMENT = {
    
}

COMMONWEALTH_DEMONYMS = {
    "antigua and barbuda": ["antiguan", 
                            "barbudan"],
    "australia": ["australian"],
    "the bahamas": ["bahamian"],
    "bangladesh": ["bangladeshi"],
    "barbados": ["barbadian", 
                 "bajan"],
    "belize": ["belizean"],
    "botswana": ["botswanan"],
    "brunei darussalam": ["bruneian"],
    "cameroon": ["cameroonian"],
    "canada": ["canadian"],
    "cyprus": ["cypriot"],
    "dominica": ["dominican",
                 "dominican commonwealth"],
    "eswatini": ["swazi"],
    "fiji": ["fijian"],
    "gambia": ["gambian"],
    "ghana": ["ghanaian"],
    "grenada": ["grenadian"],
    "guyana": ["guyanese"],
    "india": ["indian"],
    "jamaica": ["jamaican"],
    "kenya": ["kenyan"],
    "kiribati": ["i-kiribati"],
    "lesotho": ["mosotho", 
                "basotho"],
    "malawi": ["malawian"],
    "malaysia": ["malaysian"],
    "maldives": ["maldivian"],
    "malta": ["maltese"],
    "mauritius": ["mauritian"],
    "mozambique": ["mozambican"],
    "namibia": ["namibian"],
    "nauru": ["nauruan"],
    "new zealand": ["new zealander", 
                    "kiwi"],
    "nigeria": ["nigerian"],
    "pakistan": ["pakistani"],
    "papua new guinea": ["papuan", 
                         "guinean"],
    "rwanda": ["rwandan"],
    "saint lucia": ["saint lucian"],
    "samoa": ["samoan"],
    "seychelles": ["seychellois"],
    "sierra leone": ["sierra leonean"],
    "singapore": ["singaporean"],
    "solomon islands": ["solomon islander"],
    "south africa": ["south african"],
    "sri lanka": ["sri lankan"],
    "st kitts and nevis": ["kittitian", "nevisian"],
    "st vincent and the grenadines": ["vincentian"],
    "tanzania": ["tanzanian"],
    "tonga": ["tongan"],
    "trinidad and tobago": ["trinidadian", 
                            "tobagonian"],
    "tuvalu": ["tuvaluan"],
    "uganda": ["ugandan"],
    "united kingdom": ["british", 
                       "english", 
                       "scottish", 
                       "welsh", 
                       "northern irish"],
    "vanuatu": ["vanuatuan"],
    "zambia": ["zambian"],
}

NON_ESA_PARTNER_STATE_DEMONYMS = {
    "canada": ("canadian",),
    "japan": ("japanese",),
    "russian federation": ("russian",),
    "united states of america": ("american",)
}

ESA_PARTNER_STATE_DEMONYMS = {
    "austria": ("austrian",),
    "belgium": ("belgian",),
    "czech republic": ("czech", "czechoslovakian"),
    "denmark": ("danish",),
    "estonia": ("estonian",),
    "finland": ("finnish",),
    "france": ("french",),
    "germany": ("german",),
    "greece": ("greek",),
    "hungary": ("hungarian",),
    "ireland": ("irish",),
    "italy": ("italian",),
    "luxembourg": ("luxembourger",),
    "netherlands": ("dutch",),
    "norway": ("norwegian",),
    "poland": ("polish",),
    "portugal": ("portuguese",),
    "romania": ("romanian",),
    "spain": ("spanish",),
    "sweden": ("swedish",),
    "switzerland": ("swiss",),
    "united kingdom": ("british", "scottish", "english", "welsh", "irish")
}

Models

from typing import Optional

class Facts:
    """
    A basic class capable of handling the minimum facts required for a high 
    treason offence.

    Attributes:
        victim_category (str): The name of the victim of the offence.
        offence_date (str): The date of the offence.
        jurisdiction (str): The jurisdiction in which the offence took place.
        actions (list): A list of actions that the defendant took against the 
            victim.
        role (list): A list of roles that the defendant played in the offence.

    A Facts object should account for one offence and offender. Any potential
    path to a conviction should be represented by a distinct Facts object. 
    Multiple offences or offenders should be represented by multiple Facts 
    objects.
    """

    def __init__(self, 
                 victim_category: str = None, 
                 offence_date: str = None, 
                 jurisdiction: str = None, 
                 actions: list = None, 
                 role: list = None,
                 defendant: 'Defendant' = None
                ):
        self.victim_category = victim_category
        self.offence_date = offence_date
        self.jurisdiction = jurisdiction if jurisdiction is not None else []
        self.actions = actions if actions is not None else []
        self.role = role if role is not None else []
        self.defendant = defendant

        
    def create_facts(self):
        """
        Interactively collects details about the offence and stores them as 
        instance attributes.

        This method asks the user for details about the victim, offence date, 
        jurisdiction, and actions related to the offence. 
        These details are then stored as instance attributes for future use.
        
        Note:
            This method uses the `input` function and thus is intended for use 
            in an interactive context.
        """
        
        print("Please enter the facts of the case:")

        victim_category = input("Who is the victim? ")
        victim_category = standardize_sovereign_names(victim_category)
        victim_category = standardize_canada_names(victim_category)
        complainant = Complainant(victim_category)
        offence_date = input("Date of the offence (YYYY-MM-DD): ")
        jurisdiction = input("Jurisdiction: ")
        actions = high_treason_facts(victim_category)

        self.victim_category = complainant.name
        self.offence_date = offence_date
        self.actions = actions
        self.jurisdiction = jurisdiction

        
    def verify_jurisdiction(self):
        """
        Runs a broad analysis to determine whether Canadian jurisdiction exists
        or can be construed through the circumstances.
        """
        
        if self.verify_canadian_jurisdiction(self.jurisdiction):
            self.jurisdiction = jurisdiction
        elif self.verify_constructive_jurisdiction_aircraft():
            self.jurisdiction = jurisdiction
        elif self.verify_constructive_jurisdiction_space():
            self.jurisdiction = jurisdiction
        else:
            return False

            
    def verify_canadian_jurisdiction(self,
                                     jurisdiction: list, 
                                     provinces: dict[str, tuple[str, str]] = CANADIAN_PROVINCES, 
                                     territories: dict[str, tuple[str, str]] = CANADIAN_TERRITORIES) -> tuple[bool, Optional[str]]:
        """
        This function checks if a given jurisdiction is recognized as part of Canada.

        The function accepts a string representing a jurisdiction and two 
        optional dictionaries representing provinces and territories. Each 
        dictionary maps a region's name to a tuple consisting of its 
        abbreviation and French translation. The function checks if the 
        jurisdiction matches the name, abbreviation, or French translation of 
        any of the provinces or territories. The function also checks if the 
        jurisdiction is "canada" or its abbreviation "ca".

        If a match is found, the function returns True; otherwise, it returns 
        False.

        Args:
            jurisdiction (str): The jurisdiction to check.
            provinces (dict[str, tuple[str, str]], optional): A dictionary of 
            Canadian provinces. Defaults to CANADIAN_PROVINCES.
            territories (dict[str, tuple[str, str]], optional): A dictionary of
            Canadian territories. Defaults to CANADIAN_TERRITORIES.

        Returns:
            tuple[bool, Optional[str]]: A tuple where the first element is a 
            boolean indicating if the jurisdiction is recognized, and the 
            second element is an optional string message.
        """
        
        for item in jurisdiction:
            if item.lower() in ('canada', 'ca'):
                return True

            for region in [provinces, territories]:
                for key, value in region.items():
                    if item.lower() in [key.lower(), value[0].lower(), value[1].lower()]:
                        return True
            return False

    
    def verify_constructive_jurisdiction_aircraft(self):
        """
        Verifies whether an offence committed on an aircraft falls under 
        Canadian jurisdiction and updates the jurisdiction attribute 
        accordingly.

        This method asks the user a series of questions to determine if the 
        offence, when committed on an aircraft, falls under the Canadian 
        statute. If it does, the jurisdiction is updated to include the 
        specific location.

        Note:
            This method uses the `input` function and thus is intended for use 
            in an interactive context.
        """
        
        deemed_jurisdiction = []

        offense_committed = verify_yes_no("Was the offense committed on an aircraft in flight? (yes/no): ")
        aircraft_registered = aircraft_leased = qualified_operator = flight_terminated = False

        if offense_committed:
            flight_terminated = verify_yes_no("Did the flight terminate in Canada? (yes/no): ")
            aircraft_registered = verify_yes_no("Is the aircraft\n * Registered in Canada under regulations made under the Aeronautics Act? (yes/no): ")
            aircraft_leased = verify_yes_no(" * Leased without crew operated by a person who is qualified under regulations made under the Aeronautics Act to be registered as owner of an aircraft registered in Canada under those regulations? (yes/no): ")
        if aircraft_leased:
            qualified_operator = verify_yes_no(" * Operated by a person who is qualified under regulations made under the Aeronautics Act to be registered as owner of an aircraft registered in Canada under those regulations? (yes/no): ")
            

        if aircraft_registered:
            deemed_jurisdiction.append("deemed jurisdiction (legally registered aircraft)")
        if aircraft_leased and qualified_operator:
            deemed_jurisdiction.append("deemed jurisdiction (legally leased and operated aircraft)")
        if flight_terminated:
            deemed_jurisdiction.append("deemed jurisdiction (flight terminated in Canada)")

        for item in deemed_jurisdiction:
            if item not in self.jurisdiction:
                self.jurisdiction.append(item)
 

    def verify_constructive_jurisdiction_space(self):
        # List of possible locations
        locations = ["on the iss", 
                     "en route to iss", 
                     "on the lunar gateway", 
                     "en route to/from Lunar Gateway", 
                     "on the lunar surface"]

        # Combine all offences into one list
        all_offences = ["threatend a canadian crew member's life", 
                        "threatend a canadian crew member's security", 
                        "damaged a canadian flight element", 
                        "offended in relation to a canadian flight element",
                        "damaged a canadian flight element"]

        partner_state_member = self._verify_partner_state(self.defendant.agency)
        
        if partner_state_member:
            for offence in all_offences:
                print(f"The defendant {offence} — (yes/no): ")
                offence_committed = input().lower()
                if offence_committed == 'yes':
                    for location in locations:
                        print(f"Did the offence take place {location}? (yes/no): ")
                        offence_location = input().lower()
                        if offence_location == 'yes':
                            self.jurisdiction.append(f"deemed jurisdiction ({offence} {location})")


    def _verify_partner_state(self, memberships):
        """
        This is a private method that checks if any given citizenship belongs to a 
        Partner State country.

        Args:
            citizenships (list): The list of citizenships to check. Each should be a 
            lowercase string that denotes a country or its demonym.

        Returns:
            bool: True if any of the citizenships is in the list of Partner States' countries 
            or demonyms, False otherwise.
        """

        # Convert the dictionary values (which are tuples) into sets for easy searching
        non_esa_demonyms = set(val for sublist in NON_ESA_PARTNER_STATE_DEMONYMS.values() for val in sublist)
        esa_demonyms = set(val for sublist in ESA_PARTNER_STATE_DEMONYMS.values() for val in sublist)

        for membership in memberships:
            if membership in non_esa_demonyms or membership in esa_demonyms:
                return True

        return False

class Defendant:
    """
    Creates a defendant instance. Necessary to the extent that some offences 
    only apply to defendants with certain characteristics, and to the extent
    that some offences will involve multiple defendants who need to be kept
    distinct from one another.
    """
    
    def __init__(self, 
                 name=None, 
                 age=None, 
                 liability=None, 
                 criminal_record=None,
                 vocation=None,
                 citizenship=None,
                 agency=None):
        
        self.name = name
        self.age = age
        self.liability = liability
        self.criminal_record = criminal_record
        self.vocation = vocation if vocation is not None else []
        self.citizenship = citizenship if citizenship is not None else []
        self.agency = agency if agency is not None else []

    def public_employee_test(self):
        """
        Verifies whether a defendant is a public employee as understood by the 
        Criminal Code.
        """
        
        for question in PUBLIC_EMPLOYEE_CRITERIA:
            response = input(question + " (yes/no): ")
            if response.strip().lower() != 'yes':
                return
        self.vocation.append("public employee")

    def allegiance_test(self):
        """
        Determines whether a defendant owes allegiance to the sovereign based
        on their citizenship. The method iterates over the list of citizenships
        and checks each one. If the individual has "dominican" citizenship, 
        the program must further determine whether the individual is a citizen
        of Dominica (Commonwealth) or the Dominican Republic (not Commonwealth).

        For "dominican" citizenship, it invokes the _handle_dominican method to handle
        the special case. For other citizenships, it checks if they are Commonwealth
        using the _is_commonwealth_citizen method.

        If a Commonwealth citizenship is found, it immediately returns True. If none 
        of the citizenships are Commonwealth, it returns False. If citizenship list is 
        empty or not provided, it also returns False.

        Returns:
            bool: True if the defendant has a Commonwealth citizenship, False otherwise.
        """
        
        if self.citizenship:
            for i, citizenship in enumerate(self.citizenship):
                if citizenship.lower() == "dominican":
                    self.citizenship[i] = self._handle_dominican()
                    citizenship = self.citizenship[i]  # update citizenship value with new string

                if self._is_commonwealth_citizen(citizenship):
                    return True
            return False
        else:
            return False

    def _is_commonwealth_citizen(self, citizenship):
        """
        This is a private method that checks if a given citizenship belongs to a 
        Commonwealth country.

        Args:
            citizenship (str): The citizenship string to check. This should be a 
            lowercase string that denotes a country or its demonym.

        Returns:
            bool: True if the citizenship is in the list of Commonwealth countries 
            or demonyms, False otherwise.

        Note:
            This method assumes that `COMMONWEALTH_DEMONYMS` is a dictionary 
            available in the global scope, where the keys are countries and 
            the values are lists of associated demonyms.
        """
        
        return any(citizenship.lower() in demonyms for demonyms in COMMONWEALTH_DEMONYMS.values())

    def _handle_dominican(self):
        """
        This is a private method that checks if a "dominican" citizenship belongs 
        to the Commonwealth country Dominica or the non-Commonwealth country, 
        the Dominican Republic.

        Args:
            index (int): The index of "dominican" citizenship in the citizenship list.

        Returns:
            None
        """
        
        while True:
            print("Are you from (1) Dominica or (2) the Dominican Republic?")
            response = input("Please enter 1 or 2: ").strip()
            if response == "1":
                return "dominican commonwealth"
            elif response == "2":
                return "dominican not commonwealth"
            else:
                print("Invalid input. Please enter 1 or 2.")

Updated main.py functions

def verify_high_treason(matches, facts, defendant):
    """
    Determines whether the factual and legal matrix is sufficient to make out 
    high treason.
    """
    
    reasons = []

    if not matches:
        reasons.append("* No wrongful act")

    # Check allegiance
    if not defendant.allegiance_test():
        reasons.append("* Defendant does not owe allegiance to the sovereign")

    # If defendant doesn't owe allegiance, then we also check for jurisdiction
    if not defendant.allegiance_test() and facts.jurisdiction == "not recognized":
        reasons.append("* No jurisdiction")

    # If there are any reasons in the list, print them out. 
    # Otherwise, high treason has been committed.
    if reasons:
        print("High treason not committed:")
        for reason in reasons:
            print(reason)
    else:
        print("High treason committed. Matches:")
        for match in matches:
            print(match)

AI disclaimer

I’ve used the following AI tools to help create this project:

  • Spelling and grammar. Grammarly (non-generative)
  • Text summarization. ChatGPT-3.5 & 4
  • Code. ChatGPT-3.5 & 4
  • Legal research. CanLII (non-generative), HeinOnline (non-generative)
  • Images. DALL-E

Footnotes

  1. As the system develops, jurisdiction may become a more significant issue. If that proves to be the case, jurisdictional functions will likely have to move to a separate class. For now, treating them as just another fact makes sense.↩︎

  2. See note 1 above. The same logic applies to stray functions that deal with individual offences. How I should best deal with these is still very much to be determined.↩︎