Skip to content

Commit

Permalink
link Opinion text selectors to Opinion Factors
Browse files Browse the repository at this point in the history
Still need a function to dedupe/merge these selectors, a way to collect the Opinion text referenced by all of a Holding's selectors, and lots more tests.

Closes #21
  • Loading branch information
mscarey committed Aug 9, 2019
1 parent 1beffa4 commit dcea9ba
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 25 deletions.
7 changes: 4 additions & 3 deletions authorityspoke/enactments.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,10 @@ def consolidate_enactments(enactments: List[Enactment]) -> List[Enactment]:
match_made = False
left = enactments.pop()
for right in enactments:
if left + right is not None:
enactments.remove(right) # TODO: not efficient
enactments.append(left + right) # TODO: calculation repeated
combined = left + right
if combined is not None:
enactments.remove(right)
enactments.append(combined)
match_made = True
break
if match_made is False:
Expand Down
4 changes: 0 additions & 4 deletions authorityspoke/factors.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,10 +599,6 @@ class Fact(Factor):
any court considers the order context-specific, then this
approach of hard-coding their names and order will have to change.
"""
# TODO: use dicts for named context_factors
# and tuples for ordered context_factors like Fact has.
# Then Procedure would have a dict with tuples as values?
# Actually, why bother?
predicate: Predicate
context_factors: Sequence[Factor] = ()
name: Optional[str] = None
Expand Down
7 changes: 5 additions & 2 deletions authorityspoke/io/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,11 @@ def read_holdings(
"""
# populates mentioned with context factors that don't
# appear in inputs, outputs, or despite
if holdings.get("mentioned_factors"):
for factor_dict in holdings["mentioned_factors"]:
mentioned_factors = holdings.get("mentioned_factors")
if mentioned_factors:
if isinstance(mentioned_factors, dict):
mentioned_factors = [mentioned_factors]
for factor_dict in mentioned_factors:
_, mentioned = read_factor(
factor_record=factor_dict,
mentioned=mentioned,
Expand Down
42 changes: 32 additions & 10 deletions authorityspoke/opinions.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def exposit(
self,
holdings: Sequence[Holding],
text_links: Optional[Dict[Factor, List[TextQuoteSelector]]] = None,
context: Optional[Sequence[Factor]] = None,
):
r"""
:meth:`~Opinion.posit` :class:`.Rule` objects and link :class:`Factor`\s to text.
Expand All @@ -111,19 +112,22 @@ def exposit(
to ``self.holdings``.
:param holdings:
:class:`Holding`\s to :meth:`~Opinion.posit`
:class:`Holding`\s to :meth:`~Opinion.posit`.
:param text_links:
mapping of :class:`Factor`\s to the :class:`Opinion` passages where
they can be found
they can be found. Can be obtained as the "mentioned" return value
of one of the functions in :module:`authorityspoke.io.readers`\.
:param context:
an ordered sequence (probably :py:class:`dict`) of
generic :class:`.Factor` objects from ``self`` which
will provide the context for the new holding in the
present case.
"""

for holding in holdings:
self.posit(holding)
if text_links:
for factor in text_links:
if self.factors.get(factor) is None:
self.factors[factor] = text_links[factor]
self.posit(holding=holding, text_links=text_links, context=context)
return self

@property
Expand Down Expand Up @@ -168,9 +172,10 @@ def get_factor_by_name(self, name: str) -> Optional[Factor]:
def posit(
self,
holding: Union[Holding, Iterable[Holding]],
text_links: Optional[Dict[Factor, List[TextQuoteSelector]]] = None,
context: Optional[Sequence[Factor]] = None,
) -> None:
"""
r"""
Add a :class:`.Holding` as a holding of this ``Opinion``.
Adds ``holding`` (or every :class:`.Holding` in ``holding``, if ``holding``
Expand All @@ -185,6 +190,11 @@ def posit(
case, and regardless of whether the court orders that
the ``outputs`` of the :class:`.Holding` be put into effect.
:param text_links:
mapping of :class:`Factor`\s to the :class:`Opinion` passages where
they can be found. Can be obtained as the "mentioned" return value
of one of the functions in :module:`authorityspoke.io.readers`\.
:param context:
an ordered sequence (probably :py:class:`dict`) of
generic :class:`.Factor` objects from ``self`` which
Expand All @@ -195,6 +205,7 @@ def posit(
def posit_one_holding(
holding: Union[Holding, Iterable[Holding]],
context: Optional[Sequence[Factor]] = None,
text_links=None,
) -> None:
if not isinstance(holding, Holding):
raise TypeError('"holding" must be an object of type Holding.')
Expand All @@ -215,15 +226,26 @@ def posit_one_holding(
holding = holding.new_context(context)
self.holdings.append(holding)

if text_links:
for factor in holding.recursive_factors:
for selector in text_links.get(factor, []):
if factor not in self.factors:
self.factors[factor] = [] # repeated elsewhere?
if not any(
selector == known_selector
for known_selector in self.factors[factor]
):
self.factors[factor].append(selector)

# These lines repeat lines in new_context_helper
if isinstance(context, Factor) or isinstance(context, str):
context = context._wrap_with_tuple(context)

if isinstance(holding, Iterable):
for item in holding:
posit_one_holding(item, context)
posit_one_holding(item, context, text_links)
else:
posit_one_holding(holding, context)
posit_one_holding(holding, context, text_links)

def get_anchors(self, holding: Holding, include_factors: bool = True) -> List[str]:
r"""
Expand Down
20 changes: 19 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Tuple
from typing import Any, Dict, Tuple

import pytest

Expand Down Expand Up @@ -1056,3 +1056,21 @@ def make_opinion_with_holding(make_opinion, make_regime) -> Dict[str, Opinion]:
opinions[f"{case}_{opinion.position}"] = opinion
opinions[f"{case}_majority"].exposit(*loaders.load_holdings(f"holding_{case}.json", regime=make_regime, report_mentioned=True))
return opinions

@pytest.fixture(scope="class")
def make_analysis() -> Dict[str, Dict["str", Any]]:
"""Example user analysis data."""
analysis = {}
analysis["minimal"] = {
"mentioned_factors": {"type": "entity", "name": "Bradley"},
"holdings": [
{
"outputs": {
"type": "fact",
"content": "Bradley made a minimal holding object.",
"text": "upholding searches in |open fields or grounds|around a house",
}
}
],
}
return analysis
20 changes: 20 additions & 0 deletions tests/test_holdings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest

from authorityspoke.io import readers


class TestSameMeaning:
def test_identical_holdings_equal(self, make_holding):
Expand Down Expand Up @@ -184,3 +186,21 @@ def test_contradiction_with_distance(self, make_opinion_with_holding, make_holdi
def test_error_contradiction_with_procedure(self, make_holding, make_procedure):
with pytest.raises(TypeError):
make_holding["h2_undecided"].contradicts(make_procedure["c2"])


class TestHoldingImports:
def test_make_selector_for_opinion_text(self, make_analysis):
holdings, mentioned = readers.read_holdings(
make_analysis["minimal"], report_mentioned=True
)
fact = holdings[0].outputs[0]
assert mentioned[fact][0].exact == "open fields or grounds"

def test_posit_holding_with_selector(self, make_analysis, make_opinion):
holdings, mentioned = readers.read_holdings(
make_analysis["minimal"], report_mentioned=True
)
brad = make_opinion["brad_majority"]
brad.posit(holdings[0], text_links=mentioned)
fact = holdings[0].outputs[0]
assert brad.factors[fact][0].exact == "open fields or grounds"
12 changes: 7 additions & 5 deletions tests/test_opinions.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,21 @@ def test_opinion_entity_list(
h = real_holding
e = make_entity

watt.posit(h["h1"], (e["motel"], e["watt"]))
watt.posit(h["h2"], (e["trees"], e["motel"]))
watt.posit(h["h1"], context=(e["motel"], e["watt"]))
watt.posit(h["h2"], context=(e["trees"], e["motel"]))
watt.posit(
h["h3"],
(
context=(
make_evidence["generic"],
e["motel"],
e["watt"],
e["trees"],
e["tree_search"],
),
)
watt.posit(h["h4"], (e["trees"], e["tree_search"], e["motel"], e["watt"]))
watt.posit(
h["h4"], context=(e["trees"], e["tree_search"], e["motel"], e["watt"])
)
assert make_entity["watt"] in make_opinion["watt_majority"].generic_factors

def test_opinion_date(self, make_opinion):
Expand Down Expand Up @@ -171,7 +173,7 @@ def test_new_context_inferring_factors_to_change(self, make_opinion, make_regime
"Hideaway Lodge",
"the stockpile of trees",
]
watt.posit(brad.holdings[0], context_items)
watt.posit(brad.holdings[0], context=context_items)
assert watt.holdings[-1].means(brad.holdings[0])


Expand Down

0 comments on commit dcea9ba

Please sign in to comment.