Skip to content

Commit

Permalink
make standards_of_proof a ClassVar
Browse files Browse the repository at this point in the history
  • Loading branch information
mscarey committed Jun 9, 2019
1 parent 5666f03 commit c4432da
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 66 deletions.
2 changes: 2 additions & 0 deletions authorityspoke/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""AuthoritySpoke: Reading the law for the last time."""

from .enactments import Code, Enactment
from .factors import Factor, Entity
from .jurisdictions import Jurisdiction, Regime
Expand Down
9 changes: 7 additions & 2 deletions authorityspoke/courts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Classes describing judicial institutions and their effect on legal authority."""

from __future__ import annotations

from typing import List, Optional
Expand All @@ -6,12 +8,15 @@

from authorityspoke.opinions import Opinion


@dataclass
class Court:
"""
Issues :class:`.Opinion`\s, has :class:`.Judge`\s,
may be inferior to other `Courts`.
An institution that issues :class:`.Opinion`\s and decides litigation.
Has :class:`.Judge`\s and may be inferior to other :class:`Court`\s.
"""

id: str
opinions: Optional[List[Opinion]] = None
inferior_to: Optional[List[Court]] = None
124 changes: 77 additions & 47 deletions authorityspoke/factors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
""":class:`Factor`\s, or inputs and outputs of legal :class:`.Rule`\s."""

from __future__ import annotations

import datetime
Expand Down Expand Up @@ -590,6 +592,16 @@ class Fact(Factor):
a series of :class:`Factor`\s that have already been mentioned
in the :class:`.Opinion`. They are available for composing the
new :class:`Factor` object and don't need to be recreated.
:attr standards_of_proof:
a tuple with every allowable name for a standard of
proof, in order from weakest to strongest.
.. note:
If any courts anywhere in a legal regime disagree about the
relative strength of the various standards of proof, or if
any court considers the order context-specific, then this
approach of hard-coding their names and order will have to change.
"""

predicate: Optional[Predicate] = None
Expand All @@ -599,15 +611,23 @@ class Fact(Factor):
absent: bool = False
generic: bool = False
case_factors: Tuple[Factor, ...] = ()
standards_of_proof: ClassVar = (
"scintilla of evidence",
"substantial evidence",
"preponderance of evidence",
"clear and convincing",
"beyond reasonable doubt",
)


def __post_init__(self):

if (
self.standard_of_proof
and self.standard_of_proof not in self.standards_of_proof()
and self.standard_of_proof not in self.standards_of_proof
):
raise ValueError(
f"standard of proof must be one of {self.standards_of_proof()} or None."
f"standard of proof must be one of {self.standards_of_proof} or None."
)
case_factors = self.__class__._wrap_with_tuple(self.case_factors)
if not self.context_factors:
Expand Down Expand Up @@ -648,27 +668,6 @@ def get_factor_by_index(
)
object.__setattr__(self, "context_factors", context_factors)

@classmethod
def standards_of_proof(cls) -> Tuple[str, ...]:
"""
.. note:
If any courts anywhere in a legal regime disagree about the
relative strength of the various standards of proof, or if
any court considers the order context-specific, then this
approach of hard-coding their names and order will have to change.
:returns:
a tuple with every allowable name for a standard of
proof, in order from weakest to strongest.
"""
return (
"scintilla of evidence",
"substantial evidence",
"preponderance of evidence",
"clear and convincing",
"beyond reasonable doubt",
)

def __str__(self):
predicate = str(self.predicate.content_with_entities(self.context_factors))
standard = (
Expand Down Expand Up @@ -746,19 +745,22 @@ def from_string(
@property
def interchangeable_factors(self) -> List[Dict[Factor, Factor]]:
"""
Get ways to reorder context :class:`Factor`\s without changing truth value of ``self``.
Each :class:`dict` returned has :class:`Factor`\s to replace as keys,
and :class:`Factor`\s to replace them with as values.
If there's more than one way to rearrange the context factors,
more than one :class:`dict` should be returned.
Currently the predicate must be phrased either in a way that
doesn't make any context factors interchangeable, or if the
``reciprocal`` flag is set, in a way that allows only the
first two context factors to switch places.
:returns:
the ways the context factors referenced by the
:class:`Factor` object can be reordered without changing
the truth value of the :class:`Factor`. Currently the
predicate must be phrased either in a way that doesn't
make any context factors interchangeable, or if the
``reciprocal`` flag is set, in a way that allows only the
first two context factors to switch places.
the truth value of the :class:`Factor`.
"""
if self.predicate and self.predicate.reciprocal:
Expand All @@ -780,6 +782,8 @@ def _equal_if_concrete(self, other: Factor) -> bool:

def predicate_in_context(self, entities: Sequence[Factor]) -> str:
"""
Insert :class:`str` representations of ``entities`` into ``self``\s :class:`Predicate`.
:returns:
the representation of ``self``\s :class:`Predicate` with
:class:`str` representations of ``entities`` added into
Expand All @@ -797,12 +801,18 @@ def __len__(self):
return len(self.context_factors)

def _implies_if_concrete(self, other: Factor) -> bool:
"""
Test if ``self`` impliess ``other``, assuming they are not ``generic``.
:returns:
whether ``self`` implies ``other`` under the given assumption.
"""
if bool(self.standard_of_proof) != bool(other.standard_of_proof):
return False

if self.standard_of_proof and self.standards_of_proof().index(
if self.standard_of_proof and self.standards_of_proof.index(
self.standard_of_proof
) < self.standards_of_proof().index(other.standard_of_proof):
) < self.standards_of_proof.index(other.standard_of_proof):
return False

if not self.predicate >= other.predicate:
Expand All @@ -811,9 +821,11 @@ def _implies_if_concrete(self, other: Factor) -> bool:

def _contradicts_if_present(self, other: Fact) -> bool:
"""
Test if ``self`` contradicts ``other``, assuming they are not ``absent``.
:returns:
whether ``self`` contradicts ``other``
under the assumption that ``self.absent == False``.
whether ``self`` and ``other`` can't both be true at
the same time under the given assumption.
"""
if (self.predicate.contradicts(other.predicate) and not other.absent) or (
self.predicate >= other.predicate and other.absent
Expand All @@ -823,9 +835,11 @@ def _contradicts_if_present(self, other: Fact) -> bool:

def _contradicts_if_factor(self, other: Factor) -> bool:
"""
Test if ``self`` contradicts ``other``, assuming they are both :class:`Factor`\s.
:returns:
``True`` if ``self`` and ``other`` can't both be true at
the same time. Otherwise returns ``False``.
whether ``self`` and ``other`` can't both be true at
the same time under the given assumption.
"""

if not isinstance(other, self.__class__):
Expand All @@ -840,8 +854,16 @@ def _build_from_dict(
cls, fact_dict: Dict[str, Union[str, bool]], mentioned: List[Factor]
) -> Optional[Fact]:
"""
Constructs and returns a :class:`Fact` object from a dict imported from
a JSON file in the format used in the "input" folder.
Construct and return a :class:`Fact` object from a :py:class:`dict`.
:param fact_dict:
imported from a JSON file in the format used in the "input" folder.
:param mentioned:
a list of :class:`.Factor`\s that may be included by reference to their ``name``\s.
:returns:
a :class:`Fact`.
"""

placeholder = "{}" # to be replaced in the Fact's string method
Expand All @@ -850,8 +872,10 @@ def add_content_references(
content: str, mentioned: List[Factor], placeholder: str
) -> Tuple[str, List[Factor]]:
"""
Get context :class:`Factor`\s for new :class:`Fact`.
:param content:
the content for the :class:`Fact`\'s :class:`Predicate`
the content for the :class:`Fact`\'s :class:`Predicate`.
:param mentioned:
list of :class:`Factor`\s with names that could be
Expand Down Expand Up @@ -885,7 +909,6 @@ def add_content_references(
quantity = None
content = fact_dict.get("content")
if fact_dict.get("content"):
content = fact_dict.get("content")
content, context_factors = add_content_references(
content, mentioned, placeholder
)
Expand All @@ -906,20 +929,22 @@ def add_content_references(
quantity=quantity,
)

factor = cls(
return cls(
predicate,
context_factors,
name=fact_dict.get("name", None),
standard_of_proof=fact_dict.get("standard_of_proof", None),
absent=fact_dict.get("absent", False),
generic=fact_dict.get("generic", False),
)
return factor

@new_context_helper
def new_context(self, changes: Dict[Factor, Factor]) -> Factor:
"""
Create new :class:`Factor`, replacing keys of ``changes`` with values.
:returns:
a version of ``self`` with the new context.
"""
return self.evolve(
{
Expand Down Expand Up @@ -948,8 +973,10 @@ def __str__(self):
@dataclass(frozen=True)
class Allegation(Factor):
"""
A formal assertion of a :class:`Fact`, included by a party in a
:class:`Pleading` to establish a cause of action.
A formal assertion of a :class:`Fact`.
May be included by a party in a :class:`Pleading`
to establish a cause of action.
"""

to_effect: Optional[Fact] = None
Expand Down Expand Up @@ -1011,10 +1038,7 @@ def __str__(self):

@dataclass(frozen=True)
class Evidence(Factor):
"""
An :class:`Exhibit` that has been admitted by the court to aid a
factual determination.
"""
"""An :class:`Exhibit` admitted by a court to aid a factual determination."""

exhibit: Optional[Exhibit] = None
to_effect: Optional[Fact] = None
Expand All @@ -1033,7 +1057,7 @@ def __str__(self):

def means(left: Factor, right: Factor) -> bool:
"""
Call :meth:`.Factor.means` as function alias
Call :meth:`.Factor.means` as function alias.
This only exists because :class:`.Relation` objects expect
a function rather than a method for :attr:`~.Relation.comparison`.
Expand Down Expand Up @@ -1131,6 +1155,12 @@ def _context_register(
yield {self: other, other: self}

def contradicts(self, other: Factor) -> bool:
"""
Test whether ``self`` contradicts the ``other`` :class:`Factor`.
:returns:
``False``, because an :class:`Entity` contradicts no other :class:`Factor`.
"""
if not isinstance(other, Factor):
raise TypeError(
f"'Contradicts' not supported between class "
Expand Down
37 changes: 24 additions & 13 deletions authorityspoke/relations.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Objects that test whether a function holds between two groups of :class:`.Factor`\s."""

from __future__ import annotations

from typing import Callable, Dict, Iterator
Expand All @@ -9,10 +11,10 @@
@dataclass(frozen=True)
class Relation:
"""
Describes two groups of :class:`.Factor`\s and specifies a function
that must hold between the two groups. Can be used to find ways to
assign the :class:`.Factor`\s' context assignments consistently with
the ``Relation``.
Two groups of :class:`.Factor`\s and a function that must hold between them.
Can be used to find ways to assign the :class:`.Factor`\s'
context assignments consistently with the ``Relation``.
:param need_matches:
:class:`.Factor`\s that all need to satisfy the ``comparison``
Expand All @@ -31,13 +33,13 @@ class Relation:
:meth:`.Factor.__ge__`.
"""

need_matches: Tuple["Factor", ...]
available: Tuple["Factor", ...]
need_matches: Tuple[Factor, ...]
available: Tuple[Factor, ...]
comparison: Callable

def ordered_comparison(
self, matches: Optional[Dict["Factor", "Factor"]] = None
) -> Iterator[Dict["Factor", Optional["Factor"]]]:
self, matches: Optional[Dict[Factor, Factor]] = None
) -> Iterator[Dict[Factor, Optional[Factor]]]:
"""
Find ways for a series of pairs of :class:`.Factor`\s to satisfy a comparison.
Expand Down Expand Up @@ -82,9 +84,9 @@ def ordered_comparison(

def unordered_comparison(
self,
matches: Dict["Factor", "Factor"],
still_need_matches: Optional[List["Factor"]] = None,
) -> Iterator[Dict["Factor", Optional["Factor"]]]:
matches: Dict[Factor, Factor],
still_need_matches: Optional[List[Factor]] = None,
) -> Iterator[Dict[Factor, Optional[Factor]]]:
"""
Find ways for two unordered sets of :class:`.Factor`\s to satisfy a comparison.
Expand Down Expand Up @@ -133,8 +135,17 @@ def unordered_comparison(
yield next_step

def update_matchlist(
self, matchlist: List[Dict["Factor", "Factor"]]
) -> List[Dict["Factor", Optional["Factor"]]]:
self, matchlist: List[Dict[Factor, Factor]]
) -> List[Dict[Factor, Optional[Factor]]]:
"""
Filter a :py:class:`list` of possible :class:`.Factor` assignments with an :meth:`~Relation.unordered_comparison`.
:param matchlist:
possible ways to match generic :class:`.Factor`\s of ``need_matches`` with ``available``.
:returns:
a new version of ``matchlist`` filtered to be consistent with ``self``\'s :meth:`~Relation.unordered_comparison`.
"""
new_matchlist = []
for matches in matchlist:
for answer in self.unordered_comparison(matches, list(self.need_matches)):
Expand Down

0 comments on commit c4432da

Please sign in to comment.