BSES part 1: a basic statutory expert system

article
code
analysis
bses
Author

Daniel Booy

Published

June 24, 2023

Modified

June 25, 2023

Abstract
Despite advances in generative AI, expert legal systems will continue to be necessary for legal tech and its applications. These systems can anticipate the hallucinations that generative AIs produce, guide them by prompting them along a control flow, and easily expose the logic underlying a legal AI’s decision. In this post I outline a high level basic statutory expert system (BSES) that answers a narrow, basic legal problem: whether a criminal defendant is guilty of high treason.

Introduction

When reading up on legal tech’s intersections with AI and expert systems, researchers and writers frequently mention Robert Kowalski, Fariba Sadri, and Marek Sogot’s paper “The British Nationality Act as a Logic Program”1 as a black swan event in legal AI.2 Their paper outlined an expert legal system the authors designed and coded in Prolog. Reading through their report, it seemed that a Python program could produce similar results. This little project aims to do that, starting with a simple statutory example.

High treason

What is high treason?

High treason is a criminal offence created by section 46(1) of the Canadian Criminal Code. When I wrote this post, the offence read as follows:

High treason

46 (1) Every one commits high treason who, in Canada,

  1. kills or attempts to kill Her Majesty, or does her any bodily harm tending to death or destruction, maims or wounds her, or imprisons or restrains her;

  2. levies war against Canada or does any act preparatory thereto; or

  3. assists an enemy at war with Canada, or any armed forces against whom Canadian Forces are engaged in hostilities, whether or not a state of war exists between Canada and the country whose forces they are.

Although treason has been a codified criminal offence since the Criminal Code, 1892, the distinction between treason and high treason is relatively recent. Before Parliament made significant amendments to the Criminal Code in the 1970s, treason and high treason were a single, albeit multi-faceted, codified offence.3 Historically, treason was one of three offences punishable by death,4 and remains one of the most severe offences in Canadian criminal law.

Why high treason?

There are several key advantages to working with an offence like high treason:

  • Minimal statutory elements. Criminal Code s 46 contains a reasonably straightforward control flow with only a few possible outcomes each time the program asks the user to choose between options.
  • Few conditional layers. High treason’s decision tree only contains a few conditional layers. At most, the user must make two decisions before reaching a result and can arrive at a result after as few as one decision. This conditional superficiality makes high treason easy to diagram and demonstrates some basic expert legal system fundamentals.
  • Underlitigated. Although all Canadian criminal offences begin as codified statutes, most accumulate greater complexity through litigation. Courts ask and answer what certain words mean in certain circumstances and often add dozens of conditions to one or more basic elemental steps. High treason is a relatively recently created and infrequently charged offence, with few (if any) reported cases turning up in any of the usual repositories. Infrequently charged offences are, by necessity, rarely judicially considered. This infrequency limits the degree to which the statute can be interpretively bifurcated and grow more complex.
  • Highly integrated. Despite not appearing much in reported case decisions, high treason is a serious offence that attracts special consideration throughout the Criminal Code, such as punishment availability, mandatory trial procedures, evidentiary prerequisites, jurisdictional nuances, etc. These conditions require the basic expert system I develop here to remain extensible to these future inputs.

High treason’s relative conceptual simplicity makes it an ideal offence to begin development. It is sufficiently complex to allow me to experiment with navigating control points while being straightforward enough to be captured by an automated expert system that answers a simple legal question.

The project’s scope

This project’s goal is to encapsulate high treason within an expert legal system fully. The project must account for the punishment, procedural, and jurisdictional components discussed above to be considered complete. But to start, I will represent high treason’s offence section at a high level. Once the basic control flow is outlined and codified, it can be made more extensible and further developed to incorporate these additional components.

Elementary control flow

One way of describing this high-level view is as an “elementary control flow” representation. By “elementary,” I mean it represents the offence’s essential elements.5 By “control flow,” I refer to the decision trees commonly used in computer science and juridical reasoning.

High treason’s elements

Criminal Code s 46(1)’s wording describes the following offence elements:

  1. The offence took place in Canada; AND
  2. Where the complainant is the sovereign, the defendant:
  3. Killed the sovereign; OR
  4. Attempted to kill the sovereign; OR
  5. Caused the sovereign bodily harm tending to cause death; OR
  6. Caused the sovereign bodily harm tending to cause destruction; OR
  7. Maims the sovereign; OR
  8. Wounds the sovereign; OR
  9. Imprisons the sovereign; OR
  10. Restrains the sovereign; OR
  11. Where the complainant is in Canada, the defendant:
  12. Levied war against Canada; OR
  13. Prepared to levy war against Canada; OR
  14. Assists an enemy at war with Canada; OR
  15. Assists an armed force engaged in hostilities with Canada.

Where none of these conditions obtain, the defendant did not commit high treason.

High treason’s elements as a flowchart

These elements can also be represented in a decision tree as follows:

flowchart TD
    A[Did the defendant commit high treason?] --> B1{The act took place in Canada}
    B1 -->|No| B2(Not guilty)
    B1 -->|Yes| C{Who is the complainant}
    C --> D[The sovereign]
    C --> E[Canada]
    C --> F["¬(The sovereign ∨ Canada)"]
    F --> G(Not guilty)
    E --> H0{Did the defendant}
    H0 --> H1[Levy war against Canada]
    H1 -->|"∨"| H2[Prepare to levy war against Canada]
    H2 -->|"∨"| H3[Assist an enemy at war with Canada]
    H3 -->|"∨"| H4[Assist a hostile armed force engaged with Canada]
    H4 -->|Yes| I1(Guilty)
    H4 -->|No| I2(Not guilty)
    D --> J0{Did the defendant}
    J0 --> J1[Kill the sovereign]
    J1 -->|"∨"| J2[Attempt to kill the sovereign]
    J2 -->|"∨"| J3[Do bodily harm to the sovereign tending to cause death]
    J3 -->|"∨"| J4[Do bodily harm to the sovereign tending to cause destruction]
    J4 -->|"∨"| J5[Maim the sovereign]
    J5 -->|"∨"| J6[Wound the sovereign]
    J6 -->|"∨"| J7[Imprison the sovereign]
    J7 -->|"∨"| J8[Restrain the sovereign]
    J8 -->|Yes| K1(Guilty)
    J8 -->|No| K2(Not guilty)

Figure 1: An ugly flowchart describing the basic expert system’s flow control

Viewed this way, it is apparent how this decision tree might be easily adapted into a Python script that can, at a high level with sufficient and reliable information, decide whether a defendant committed high treason.

A minimally-viable high treason expert system

rules_constants.py

Criminal offences occur when specific factual scenarios take place. This set of rules gives the user a rudimentary way of telling the program what facts exist, allowing it to record these facts when pertinent and return an opinion based on its analysis.

Currently, the program accomplishes this by first determining whether the victim falls into one of two classes covered by high treason. If so, the program asks several specific questions to determine whether there are enough additional facts to constitute the offence. If so, it records and returns the result. But if the complainant is not a member of the high treason complainant class or the defendant’s actions do not match the actus reus of any high treason variant, the function advises as much.

I will outline this functionality below, but for now, the HIGH_TREASON_RULES constant contains all the information needed to get a minimally-viable high treason system going. It currently includes the complainant class as its key and a list of tuples as the key’s value. The tuples have a string at index 0 that prompts the user for a yes/no question and a list at index 1 containing factual information for a future function to read and relay.

HIGH_TREASON_RULES = {
    "sovereign": [("Did the defendant kill the sovereign?",
                   ["kill"]),
                  ("Did the defendant attempt to kill the sovereign?",
                   ["kill", "attempt"]),
                  ("Did the defendant do bodily harm to the sovereign tending to cause death?",
                   ["bodily harm," "tending to death"]),
                  ("Did the defendant do bodily harm to the sovereign tending to cause destruction?",
                   ["bodily harm," "tending to destruction"]),
                  ("Did the defendant maim the sovereign?",
                   ["maim"]),
                  ("Did the defendant wound the sovereign?",
                   ["wound"]),
                  ("Did the defendant imprison the sovereign?",
                   ["imprison"]),
                  ("Did the defendant restrain the sovereign?",
                   ["restrain"])],
    "canada": [("Did the defendant levy war against Canada?",
                ["levy war"]),
               ("Did the defendant prepare to levy war against Canada?",
                ["prepare", "levy war"]),
               ("Did the defendant assist an enemy at war with Canada?",
                ["assist warring enemy"]),
               ("Did the defendant assist an armed force hostily engaged with Canadian Forces?",
                ["assist hostile force"])]}

facts.py

This function generates a factual matrix that future processes will use to determine whether the case facts make out a particular offence.

At first, having the get_actions() functions return True/False rather than a list seems best. After all, if any one condition obtains, the defendant committed high treason. But this overlooks that a defendant may have committed the same offence through different means. A person who levies war against Canada AND assists a separate armed force hostile engaged with Canadian Forces commits high treason twice. Similarly, a person may have caused the sovereign bodily harm that amounts to both maiming and bodily harm tending to cause death and can be legally charged and tried for both offences (albeit not convicted of both, per Stinchcombe.) A list allows the program to record multiple offence instances using fewer passes. Lists save time and ensure the program remains extensible.

def get_sovereign_actions():
    actions = []
    for question in HIGH_TREASON_RULES["sovereign"]:
        response = input(question[0] + " (yes/no): ")
        if response.lower() == 'yes':
            actions.append(("sovereign", question[1]))
    return actions

def get_canada_actions():
    actions = []
    for question in HIGH_TREASON_RULES["Canada"]:
        response = input(question[0] + " (yes/no): ")
        if response.lower() == 'yes':
            actions.append(("Canada", question[1]))
    return actions

def high_treason_facts(victim_name):
    actions = []

    if victim_name.lower() == 'sovereign':
        actions = get_sovereign_actions()
    elif victim_name.lower() == 'canada':
        actions = get_canada_actions()
    return actions

models.py

Once the program gathers this information, it needs a place to store it. The Facts class takes this job on. The version below contemplates a few variables that still need to be used. Because the program currently uses this class for just one offence, it only needs to be able to store the victim category and the defendant’s actions.

The Facts class is a simple class capable of handling the minimum facts required for a high treason offence. It currently contains the following attributes:

  • victim_category (str): The name of the victim of the offence.
  • offence_date (str): The date of the offence.
  • jurisdiction (str): The jurisdiction where the offence occurred.
  • 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. A distinct Facts object should represent any potential path to a conviction. Multiple Facts objects should represent multiple offences or offenders.

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

rules.py

Once the factual matrix is put together, the program calls a rules function on those facts. This function first checks to see if the complainant_key matches either of the complainant categories in HIGH_TREASON_RULES. If so, the program checks to see if any of the facts attached to that key match the offence facts in HIGH_TREASON_RULES. If so, the function appends them to a list and returns it. If not, the function returns an empty list for the main function to interpret as a null result.

def high_treason_rules(facts):
    matches = []  # create an empty list to hold matches
    for action in facts.actions:
        complainant_key = action[0] 
        action_value = action[1]

        if complainant_key in HIGH_TREASON_RULES:
            tuple_list = HIGH_TREASON_RULES[complainant_key]
            for item in tuple_list:
                if action_value == item[1]:
                    matches.append(item)  # append the matching item to the list
    return matches  # return the list of matches

input_correction.py

These assumptions assume imperfect input for the victim category and try to anticipate some synonymous terms.

def standardize_sovereign_names(name):
    known_aliases = ('queen', 'king', 'queen elizabeth', 'king charles')
    if name.lower() in known_aliases:
        return 'sovereign'
    else:
        return name

def standardize_canada_names(name):
    known_aliases = ('nation of canada', 'canadian people', 'canadian military')
    if name.lower() in known_aliases:
        return 'canada'
    else:
        return name

main.py

The program’s main function. It creates a Facts object and checks the factual matrix against the high treason rules system.

def create_facts():
    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)

    return Facts(complainant.name, offence_date, jurisdiction, actions)

def verify_high_treason(matches):
    if matches:
        print("High treason committed. Matches:")
        for match in matches:
            print(match)
    else:
        print("No offence detected.")

def high_treason_system():

    facts = create_facts()
    matches = high_treason_rules(facts)
    
    verify_high_treason(matches)
    
    return matches

And that’s it! We have the beginnings of a primitive expert legal system. I will continue developing this code in subsequent blog posts, though I may occasionally return here to update the code. Refer to the GitHub repo for version history, as needed.

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, GitHub Copilot (GPT-3.5 based)
  • Legal research. CanLII (non-generative)
  • Images. DALL-E

Footnotes

  1. See http://www.doc.ic.ac.uk/~rak/papers/British%20Nationality%20Act.pdf↩︎

  2. See e.g. AI & Law: British Nationality Act Unexpectedly Spurred AI And Law, How the British Nationality Act Served to Inadvertently Spur AI and the Law.↩︎

  3. Compare Criminal Code, 1952 s 46(1) with Aidan R. Vining, “Reforming Canadian Sentencing Practices: Problems, Prospects and Lessons” (1979) 17:2 Osgoode Hall L J 355 at note 116.↩︎

  4. The other historically capital offences were murder and rape.↩︎

  5. See e.g. https://en.wikibooks.org/wiki/Canadian_Criminal_Law/Proof_of_Elements↩︎