<jupyter_start><jupyter_text>Using Python Template Strings to Represent Legal Explanations The AuthoritySpoke library provides you with Python classes that you can use to represent a limited subset of English statements, so you can create smart annotations representing aspects of legal reasoning and factfinding. The interface for creating these phrases is similar to [Predicate logic](https://en.wikipedia.org/wiki/Category:Predicate_logic): it includes a Predicate, which is like a partial sentence with blank spaces marked by placeholders. The placeholders can be replaced by nouns that become the subjects or objects of this potential sentence. I chose to implement this feature with [template strings](https://docs.python.org/3/library/string.htmlstring.Template) instead of Python's more powerful methods for inserting data into text strings, such as f-strings or the `.format()` method. The reason was that template strings' relative lack of versatility makes them more predictable and less bug-prone. Template strings don't execute any code when they run, so they present less of a security problem and they can be used with untrusted user-generated data.  Predicate objects Here's an example of a template string used to create a Predicate object in AuthoritySpoke version 0.5:<jupyter_code>from authorityspoke import Predicate

parent_sentence = Predicate("$mother was ${child}'s parent")<jupyter_output><empty_output><jupyter_text>The phrase that we passed to the Predicate constructor is used to create a Python template string. [Template strings](https://docs.python.org/3/library/string.htmlstring.Template) are part of the Python standard library. The dollar signs and curly brackets are special symbols used to indicate placeholders in Python's template string syntax.Here's an example of what happens when you provide a template string with a mapping showing how to replace the placeholders with new text.<jupyter_code>parent_sentence.template.substitute(mother="Ann", child="Bob")<jupyter_output><empty_output><jupyter_text>Don't worry: the use of the past tense doesn't indicate that a tragedy has befallen Ann or Bob. The Predicate class is designed to be used only with an English-language phrase in the past tense. The past tense is used because legal analysis is usually backward-looking, determining the legal effect of past acts or past conditions. Don't use capitalization or end punctuation to signal the beginning or end of the phrase, because the phrase may be used in a context where it's only part of a longer sentence.Predicates can be compared using AuthoritySpoke's `.means()`, `.implies()`, and `.contradicts()` methods. The `means` method checks whether one Predicate has the same meaning as another Predicate. One reason for comparing Predicates using the `means` method instead of Python's `==` operator is that the `means` method can still consider Predicates to have the same meaning even if they use different identifiers for their placeholders.<jupyter_code>another_parent_sentence = Predicate("$adult was ${kid}'s parent")
parent_sentence.template == another_parent_sentence.template
another_parent_sentence.means(parent_sentence)<jupyter_output><empty_output><jupyter_text>You can also add a `truth` attribute to a Predicate to indicate whether the statement described by the template is considered true or false. AuthoritySpoke can then use that attribute to evaluate relationships between the truth values of different Predicates with the same template text. If you omit a `truth` parameter when creating a Predicate, the default value is `True`.<jupyter_code>not_parent_sentence = Predicate("$adult was ${kid}'s parent", truth=False)
str(not_parent_sentence)
parent_sentence.means(not_parent_sentence)
parent_sentence.contradicts(not_parent_sentence)<jupyter_output><empty_output><jupyter_text>In the `parent_sentence` example above, there are really two different placeholder formats. The first placeholder, `mother`, is just preceded by a dollar sign. The second placeholder, `child`, is preceded by a dollar sign and an open curly bracket, and followed by a closed curly bracket. These formats aren't specific to AuthoritySpoke; they're part of the Python standard library. The difference is that the format with just the dollar sign can only be used for a placeholder that is surrounded by whitespace. If the placeholder is next to some other character, like an apostrophe, then you need to use the "braced" format with the curly brackets. The placeholders themselves need to be valid Python identifiers, which means they can only be made up of letters, numbers, and underscores, and they can't start with a number. Docassemble users might already be familiar with these rules, since Docassemble variables also have to be Python identifiers. Check out Docassemble's documentation for more [guidance on creating valid Python identifiers](https://docassemble.org/docs/fields.htmlvariable%20names).  Comparison objects AuthoritySpoke's `Comparison` class extends the concept of a `Predicate`. A `Comparison` still contains a `truth` value and a `template` string, but that template should be used to identify a quantity that will be compared to an `expression` using a `sign` such as an equal sign or a greater-than sign. This `expression` must be a constant: either an integer, a floating point number, a date, or a physical quantity expressed in units that can be parsed using the [pint library](https://pint.readthedocs.io/en/stable/defining-quantities.htmlusing-string-parsing). To encourage consistent phrasing, the template string in every Comparison object must end with the word "was". AuthoritySpoke will then build the rest of the phrase using the comparison sign and expression that you provide.  Comparisons with Measurements and Units To use a measurement as a Comparison's `expression`, pass the measurement as a string when constructing the Comparison object, and it will be converted to a `pint` object.<jupyter_code>from authorityspoke import Comparison
drug_comparison = Comparison(
    "the weight of marijuana that $defendant possessed was", 
    sign=">=", 
    expression="0.5 kilograms"
)
str(drug_comparison)<jupyter_output><empty_output><jupyter_text>(The pint library always uses singular nouns for units like "kilogram", when rendering them as text.)By making the quantitative part of the phrase explicit, you make it possible for AuthoritySpoke to consider quantities when checking whether one Comparison implies or contradicts another.<jupyter_code>smaller_drug_comparison = Comparison(
    "the weight of marijuana that $defendant possessed was", 
    sign=">=", 
    expression="250 grams"
)
str(smaller_drug_comparison)<jupyter_output><empty_output><jupyter_text>AuthoritySpoke will understand that if the weight was at least 0.5 kilograms, that implies it was also at least 250 grams.<jupyter_code>drug_comparison.implies(smaller_drug_comparison)<jupyter_output><empty_output><jupyter_text>If you phrase a Comparison with an inequality sign using `truth=False`, AuthoritySpoke will silently modify your statement so it can have `truth=True` with a different sign. In this example, the user's input indicates that it's false that the weight of the marijuana was more than 10 grams. AuthoritySpoke interprets this to mean it's true that the weight was no more than 10 grams.<jupyter_code>drug_comparison_with_upper_bound = Comparison(
    "the weight of marijuana that $defendant possessed was", 
    sign=">", 
    expression="10 grams",
    truth=False
)
str(drug_comparison_with_upper_bound)<jupyter_output><empty_output><jupyter_text>Of course, this Comparison contradicts the other Comparisons that asserted the weight was much greater.<jupyter_code>drug_comparison_with_upper_bound.contradicts(drug_comparison)<jupyter_output><empty_output><jupyter_text>The unit that the Comparison parses doesn't have to be weight. It could also be distance, time, volume,units of surface area such as square kilometers or acres, or units that combine multiple dimensions such as miles per hour or meters per second.  Comparisons with Integer and Float Expressions When the number needed for a Comparison isn't a physical quantity that can be described with the units in the `pint` library, you should phrase the text in the template string to explain what the number describes. The template string will still need to end with the word "was". The value of the expression parameter should be an integer or a floating point number, not a string to be parsed.<jupyter_code>three_children = Comparison("the number of children in ${taxpayer}'s household was", sign="=", expression=3)
str(three_children)<jupyter_output><empty_output><jupyter_text>The numeric expression will still be available for comparison methods like `implies` or `contradicts`, but no unit conversion will be available.<jupyter_code>at_least_two_children = Comparison("the number of children in ${taxpayer}'s household was", sign=">=", expression=2)
three_children.implies(at_least_two_children)<jupyter_output><empty_output><jupyter_text>Floating point comparisons work similarly.<jupyter_code>specific_tax_rate = Comparison("${taxpayer}'s marginal income tax rate was", sign="=", expression=.3)
tax_rate_over_25 = Comparison("${taxpayer}'s marginal income tax rate was", sign=">", expression=.25)
specific_tax_rate.implies(tax_rate_over_25)<jupyter_output><empty_output><jupyter_text>Comparisons with Dates The ``expression`` field of a Comparison can be a ``datetime.date``.<jupyter_code>from datetime import date

copyright_date_range = Comparison("the date when $work was created was", sign=">=", expression = date(1978,1,1))
str(copyright_date_range)<jupyter_output><empty_output><jupyter_text>And ``date``s and ``date`` ranges can be compared with each other, similar to how numbers can be compared to number ranges.<jupyter_code>copyright_date_specific = Comparison("the date when $work was created was", sign="=", expression = date(1980,6,20))
copyright_date_specific.implies(copyright_date_range)<jupyter_output><empty_output><jupyter_text>Using Entities as Context Terms AuthoritySpoke isn't limited to comparing Predicates and Comparisons containing unassigned placeholder text. You can use Entity objects to assign specific terms to the placeholders. You then link the terms to the Predicate or Comparison inside a Fact object.<jupyter_code>from authorityspoke import Entity, Fact

ann = Entity("Ann", generic=False)
claude = Entity("Claude", generic=False)

ann_tax_rate = Fact(specific_tax_rate, terms=ann)
claude_tax_rate = Fact(tax_rate_over_25, terms=claude)
str(ann_tax_rate)
str(claude_tax_rate)<jupyter_output><empty_output><jupyter_text>Before, we saw that the Comparison `specific_tax_rate` implies `tax_rate_over_25`. But when we have a fact about the tax rate of a specific person named Ann, it doesn't imply anything about Claude's tax rate.<jupyter_code>ann_tax_rate.implies(claude_tax_rate)<jupyter_output><empty_output><jupyter_text>That seems to be the right answer in this case. But sometimes, in legal reasoning, we want to refer to people in a generic sense. We might want to say that a statement about one person can imply a statement about a different person, because most legal rulings can be generalized to apply to many different people regardless of exactly who those people are. To illustrate that idea, let's create two "generic" people and show that a Fact about one of them implies a Fact about the other.<jupyter_code>devon = Entity("Devon", generic=True)
elaine = Entity("Elaine", generic=True)

devon_tax_rate = Fact(specific_tax_rate, terms=devon)
elaine_tax_rate = Fact(tax_rate_over_25, terms=elaine)

devon_tax_rate.implies(elaine_tax_rate)<jupyter_output><empty_output><jupyter_text>In the string representations of Facts, generic Entities are shown in angle brackets as a reminder that they may be considered to correspond to different Entities when being compared to other objects.<jupyter_code>str(devon_tax_rate)
str(elaine_tax_rate)<jupyter_output><empty_output><jupyter_text>When the `implies` method produces the answer `True`, we can also use the `explain_implication` method to find out which pairs of generic terms can be considered analagous to one another.<jupyter_code>explanation = devon_tax_rate.explain_implication(elaine_tax_rate)
str(explanation)<jupyter_output><empty_output><jupyter_text>Identical Terms If for some reason you need to mention the same term more than once in a Predicate or Comparison, use the same placeholder for that term each time. When you provide a sequence of terms for the Fact object using that Predicate, only include each unique term once. The terms should be listed in the same order that they first appear in the template text.<jupyter_code>opened_account = Fact(
    Predicate("$applicant opened a bank account for $applicant and $cosigner"), 
    terms=(devon, elaine)
)
str(opened_account)<jupyter_output><empty_output><jupyter_text>Interchangeable Terms Sometimes, a Predicate or Comparison needs to mention two terms that are different from each other, but that have interchangeable positions in that particular phrase. To convey interchangeability, the template string should use identical text for the placeholders for the interchangeable terms, except that the different placeholders should each end with a different digit.<jupyter_code>ann = Entity("Ann", generic=False)
bob = Entity("Bob", generic=False)

ann_and_bob_were_family = Fact(Predicate("$relative1 and $relative2 both were members of the same family"), 
    terms=(ann, bob))
bob_and_ann_were_family = Fact(Predicate("$relative1 and $relative2 both were members of the same family"), 
    terms=(bob, ann))

str(ann_and_bob_were_family)
str(bob_and_ann_were_family)
ann_and_bob_were_family.means(bob_and_ann_were_family)<jupyter_output><empty_output><jupyter_text>If you choose placeholders that don't fit the pattern of being identical except for a final digit, then transposing two non-generic terms will change the meaning of the Fact.<jupyter_code>parent_sentence = Predicate("$mother was ${child}'s parent")
ann_is_parent = Fact(parent_sentence, terms = (ann, bob))
bob_is_parent = Fact(parent_sentence, terms = (bob, ann))

str(ann_is_parent)
str(bob_is_parent)
ann_is_parent.means(bob_is_parent)<jupyter_output><empty_output><jupyter_text>Higher-Order Predicates In AuthoritySpoke, terms referenced by a Predicate or Comparison can contain references to Facts as well as Entities. That mean they can include the text of other Predicates. This feature is intended for incorporating references to what people said, knew, or believed.<jupyter_code>statement = Predicate("$speaker told $listener $event")
bob_had_drugs = Fact(smaller_drug_comparison, terms=bob)
bob_told_ann_about_drugs = Fact(statement, terms=(bob, ann, bob_had_drugs))
str(bob_told_ann_about_drugs)<jupyter_output><empty_output><jupyter_text>A higher-order Predicate can be used to establish that one Fact implies another. In legal reasoning, it's common to accept that if a person knew or communicated something, then the person also knew or communicated any facts that are obviously implied by what the person actually knew or said. In this example, the fact that Bob told Ann he possessed more than 0.5 kilograms means he also told Ann that he possessed more than 250 grams.<jupyter_code>bob_had_more_drugs = Fact(drug_comparison, terms=bob)
bob_told_ann_about_more_drugs = Fact(statement, terms=(bob, ann, bob_had_more_drugs))
str(bob_told_ann_about_more_drugs)
bob_told_ann_about_more_drugs.implies(bob_told_ann_about_drugs)<jupyter_output><empty_output><jupyter_text>However, a contradiction between Facts referenced in higher-order Predicates doesn't cause the first-order Facts to contradict one another. It's not contradictory to say that a person has said two contradictory things.<jupyter_code>bob_had_less_drugs = Fact(drug_comparison_with_upper_bound, terms=bob)
bob_told_ann_about_less_drugs = Fact(statement, terms=(bob, ann, bob_had_less_drugs))
str(bob_told_ann_about_less_drugs)
bob_told_ann_about_less_drugs.contradicts(bob_told_ann_about_more_drugs)<jupyter_output><empty_output><jupyter_text>Higher-order Facts can refer to terms that weren't referenced by the first-order Fact. AuthoritySpoke will recognize that the use of different terms in the second-order Fact changes the meaning of the first-order Fact.<jupyter_code>claude_had_drugs = Fact(smaller_drug_comparison, terms=claude)
bob_told_ann_about_claude = Fact(statement, terms=(bob, ann, claude_had_drugs))
str(bob_told_ann_about_claude)
bob_told_ann_about_drugs.implies(bob_told_ann_about_claude)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06429384624603032, 'fraction_numerical': 0.00353526274791779, 'mean_word_length': 6.652453003209537, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 111, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '478709', 'license': 'FTL', 'n_tokens_mistral': 4734, 'n_tokens_neox': 4379, 'n_words': 1890, 'path': 'notebooks/template_strings_and_explanations.ipynb', 'repo_name': 'mscarey/AuthoritySpoke', 'size': '16689'}