-
Notifications
You must be signed in to change notification settings - Fork 2
/
test_opinions.py
433 lines (365 loc) · 17.3 KB
/
test_opinions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
import pytest
from anchorpoint.textselectors import TextQuoteSelector
from nettlesome.entities import Entity
from authorityspoke.facts import Fact, Predicate
from authorityspoke.holdings import Holding, HoldingGroup
from authorityspoke.procedures import Procedure
from authorityspoke.rules import Rule
from authorityspoke.io import loaders, readers
from authorityspoke.io.fake_enactments import FakeClient
from authorityspoke.opinions import Opinion, FactorIndex
class TestOpinions:
def test_opinion_features(self, make_decision):
assert make_decision["watt"].court == "9th-cir"
assert make_decision["watt"].citations[0].cite == "388 F.2d 853"
def test_repr(self, make_opinion):
assert "HAMLEY, Circuit Judge" in repr(make_opinion["watt_majority"])
def test_repr_excludes_text(self, make_opinion):
"""
reprs would be way too long if they could contain the
full opinion text.
"""
assert "text" not in repr(make_opinion["watt_majority"])
def test_opinion_author(self, make_opinion):
assert make_opinion["watt_majority"].author == "HAMLEY, Circuit Judge"
assert make_opinion["brad_majority"].author == "BURKE, J."
assert (
make_opinion["brad_concurring-in-part-and-dissenting-in-part"].author
== "TOBRINER, J."
)
def test_opinion_holding_list(self, make_opinion, real_holding):
watt = make_opinion["watt_majority"]
h3_specific = real_holding["h3"]
watt.posit(h3_specific)
assert h3_specific in watt.holdings
def test_opinion_entity_list(
self, make_opinion, real_holding, make_entity, make_evidence
):
watt = make_opinion["watt_majority"]
h = real_holding
e = make_entity
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"], context=(e["trees"], e["tree_search"], e["motel"], e["watt"])
)
assert make_entity["watt"] in make_opinion["watt_majority"].generic_terms()
def test_opinion_date(self, make_decision):
"""
This no longer tests whether two Opinions from the same Decision
have the same date, because the date field has been moved to the
Decision class.
"""
assert make_decision["watt"].date < make_decision["brad"].date
def test_opinion_string(self, make_opinion):
opinion = make_opinion["cardenas_majority"]
assert str(opinion).lower() == "majority opinion by bird, c. j."
class TestOpinionText:
def test_opinion_text(self, make_opinion):
assert (
"Feist responded that such efforts were economically "
+ "impractical and, in any event, unnecessary"
) in make_opinion["feist_majority"].text
def test_opinion_text_anchor(self, make_opinion_with_holding):
feist = make_opinion_with_holding["feist_majority"]
assert any(
"ideas" in anchor[0].exact for anchor in feist.factor_anchors.values()
)
def test_select_opinion_text_for_factor(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
anchor = oracle.holdings[0].anchors[0]
selected = oracle.select_text(selector=anchor)
assert selected == "must be “original” to qualify"
def test_select_opinion_text_for_enactment(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
enactment_name = str(oracle.holdings[0].enactments[0])
anchor = oracle.enactment_anchors[enactment_name][0]
selected = oracle.select_text(selector=anchor)
assert selected == "17 U.S.C. § 102(a)"
def test_select_opinion_text_for_holding(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
holding = oracle.holdings[0]
anchor = holding.anchors[0]
selected = oracle.select_text(selector=anchor)
assert selected == "must be “original” to qualify"
def test_invalid_text_selector(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
anchor = TextQuoteSelector(exact="text not in opinion")
with pytest.raises(ValueError):
_ = oracle.select_text(selector=anchor)
class TestOpinionHoldings:
def test_positing_non_rule_error(self, make_opinion, make_procedure):
with pytest.raises(TypeError):
make_opinion["watt_majority"].posit(make_procedure["c1"])
def test_error_posit_with_no_rule_source(self, make_opinion):
with pytest.raises(TypeError):
make_opinion["watt_majority"].posit()
def test_posit_rule(self, make_opinion, make_rule, make_holding):
"""
"Positing" a Rule causes the Rule to be converted to a Holding first.
So the Opinion implies the corresponding Holding.
"""
watt = make_opinion["watt_majority"]
watt.posit(make_rule["h1"])
assert watt.implies(make_holding["h1"])
def test_new_context_wrong_number_of_changes(self, make_opinion, make_holding):
"""
The context here (a Factor outside an iterable) only changes the first
generic factor of the Rule being posited, which may not be what the user
expects.
"""
brad = make_opinion["brad_majority"]
with pytest.raises(ValueError):
brad.posit(make_holding["h1"], context=Entity("House on Haunted Hill"))
def test_new_context_naming_nonexistent_factor(self, make_opinion, make_holding):
"""
The context here (a Factor outside an iterable) only changes the first
generic factor of the Rule being posited, which may not be what the user
expects.
"""
brad = make_opinion["brad_majority"]
brad.clear_holdings()
with pytest.raises(ValueError):
brad.posit(
make_holding["h1"],
context=(Entity("House on Haunted Hill"), "nonexistent factor"),
)
def test_new_context_creates_equal_rule(self, make_opinion, make_response):
watt = make_opinion["watt_majority"]
brad = make_opinion["brad_majority"]
client = FakeClient(responses=make_response)
watt.clear_holdings()
watt_raw = loaders.load_holdings("holding_watt.json")
watt.posit(readers.read_holdings(watt_raw, client=client))
brad.clear_holdings()
brad_raw = loaders.load_holdings("holding_brad.json")
brad.posit(readers.read_holdings(brad_raw, client=client))
context_pairs = {
"proof of Bradley's guilt": "proof of Wattenburg's guilt",
"Bradley": "Wattenburg",
"officers' search of the yard": "officers' search of the stockpile",
"Bradley's marijuana patch": "the stockpile of trees",
}
watt.posit(brad.holdings[0], context_pairs)
assert watt.holdings[-1].means(brad.holdings[0])
def test_getting_factors_from_opinion(self, make_opinion, make_response):
client = FakeClient(responses=make_response)
watt = make_opinion["watt_majority"]
watt.clear_holdings()
watt_raw = loaders.load_holdings("holding_watt.json")
holdings_to_posit = readers.read_holdings(watt_raw, client=client)
watt.posit(holdings_to_posit)
factors = watt.factors_by_name()
assert "proof of Wattenburg's guilt" in factors.keys()
def test_new_context_inferring_factors_to_change(self, make_opinion, make_response):
"""
This changes watt's holdings; may break tests below.
"""
watt = make_opinion["watt_majority"]
brad = make_opinion["brad_majority"]
client = FakeClient(responses=make_response)
watt.clear_holdings()
watt_raw = loaders.load_holdings("holding_watt.json")
watt.posit(readers.read_holdings(watt_raw, client=client))
brad.clear_holdings()
brad_raw = loaders.load_holdings("holding_brad.json")
brad.posit(readers.read_holdings(brad_raw, client=client))
context_items = [
"proof of Wattenburg's guilt",
"<Wattenburg>",
"<officers' search of the stockpile>",
"<Hideaway Lodge>",
"<the stockpile of trees>",
]
watt.posit(brad.holdings[0], context=context_items)
assert watt.holdings[-1].means(brad.holdings[0])
def test_getting_factors_from_new_holding(self, make_opinion):
watt = make_opinion["watt_majority"]
elephants = Fact("$animal was an elephant", terms=Entity("the elephant"))
mouseholes = Fact(
Predicate("$animal hides in mouseholes", truth=False),
terms=Entity("the elephant"),
)
procedure = Procedure(inputs=elephants, outputs=mouseholes)
rule = Rule(procedure=procedure)
holding = Holding(rule=rule)
watt.posit(holding)
factors = watt.factors_by_name()
factor = factors["the fact that <the elephant> was an elephant"]
assert factor.terms[0].name == "the elephant"
class TestOpinionFactors:
def test_only_one_factor_with_same_content(self, make_opinion_with_holding):
"""
Tests that a particular Factor appears only once, and that all
three of the text anchors for that Factor appear in the value
for the Factor in Opinion.factors.
"""
oracle = make_opinion_with_holding["oracle_majority"]
scenes_a_faire = [
factor
for factor in oracle.factors()
if isinstance(factor, Fact)
and factor.short_string
== "the fact that <the Java API> was a scene a faire"
]
assert len(scenes_a_faire) == 1 # 1 Factor
def test_insert_duplicate_anchor_in_factor_index(self):
api = Entity(name="the Java API", generic=True, plural=False)
anchor = TextQuoteSelector(
exact="it possesses at least some minimal degree of creativity."
)
fact = Fact(
predicate=Predicate(
"$product possessed at least some minimal degree of creativity"
),
terms=[api],
anchors=[anchor],
)
name = "the Java API possessed at least some minimal degree of creativity"
factor_index = FactorIndex({name: fact})
factor_index.insert(key=name, value=fact)
assert len(factor_index[name].anchors) == 1
def test_get_factor_from_opinion(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
company = oracle.get_factor_by_name("the Java API")
assert isinstance(company, Entity)
def test_factors_by_name(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
factors = oracle.factors_by_name()
factor = factors[
"the fact it was false that <the Java API> was an original work"
]
assert factor.terms[0].name == "the Java API"
class TestImplication:
def test_no_implication(self, make_opinion_with_holding):
watt = make_opinion_with_holding["watt_majority"]
brad = make_opinion_with_holding["brad_majority"]
assert not watt >= brad
assert not watt.implies(brad.holdings)
def test_posit_list_of_holdings_and_imply(self, make_opinion, make_response):
watt = make_opinion["watt_majority"]
brad = make_opinion["brad_majority"]
client = FakeClient(responses=make_response)
some_rules_raw = loaders.load_holdings(filename="holding_watt.json")
some_rules = readers.read_holdings(some_rules_raw, client=client)
for case in (watt, brad):
case.clear_holdings()
case.posit(some_rules[:3])
watt.posit(some_rules[3])
assert watt > brad
assert not brad >= watt
def test_error_to_compare_to_str(self, make_opinion):
watt = make_opinion["watt_majority"]
with pytest.raises(TypeError):
watt.implies("this")
def test_error_to_check_contradiction_of_str(self, make_opinion):
watt = make_opinion["watt_majority"]
with pytest.raises(TypeError):
watt.contradicts("this")
def test_opinion_implies_None(self, make_opinion, make_holding):
watt = make_opinion["watt_majority"]
assert watt.implies(None)
def test_opinion_implies_holding(self, make_opinion, make_holding):
watt = make_opinion["watt_majority"]
watt.posit(make_holding["h2_invalid_undecided"])
assert watt >= make_holding["h2_undecided"]
assert watt > make_holding["h2_undecided"]
def test_opinion_does_not_imply_holding(self, make_opinion, make_holding):
watt = make_opinion["watt_majority"]
watt.clear_holdings()
watt.posit(make_holding["h2_irrelevant_inputs_undecided"])
assert not watt >= make_holding["h2_undecided"]
assert not watt > make_holding["h2_undecided"]
def test_opinion_implied_by_rule(self, make_opinion, make_holding, make_rule):
watt = make_opinion["oracle_majority"]
watt.clear_holdings()
watt.posit(make_holding["h2"])
assert watt.implied_by(make_rule["h2_despite_due_process"])
assert not watt.implies(make_rule["h2_despite_due_process"])
def test_opinion_implies_holding_group(self, make_opinion_with_holding):
watt = make_opinion_with_holding["watt_majority"]
holdings = watt.holdings[:2]
assert isinstance(holdings, HoldingGroup)
assert watt.implies(holdings)
explanation = watt.explain_implication(holdings)
assert len(explanation.reasons) == 2
def test_opinion_does_not_imply_holding_group(self, make_opinion_with_holding):
watt = make_opinion_with_holding["watt_majority"]
holdings = make_opinion_with_holding["oracle_majority"].holdings
assert isinstance(holdings, HoldingGroup)
assert not watt.implies(holdings)
explanation = watt.explain_implication(holdings)
assert explanation is None
def test_opinion_implied_by_decision(
self, make_holding, make_decision_with_holding
):
watt = make_decision_with_holding["watt"]
opinion = Opinion()
opinion.posit(watt.holdings[0])
assert opinion.implied_by(watt)
class TestContradiction:
def test_opinion_contradicts_opinion(self, make_opinion_with_holding):
"""Return the only contradictory pair of Holdings between these two Opinions."""
oracle = make_opinion_with_holding["oracle_majority"]
lotus = make_opinion_with_holding["lotus_majority"]
left = Opinion()
left.posit(lotus.holdings[6])
right = Opinion()
right.posit(oracle.holdings[10])
explanation = left.explain_contradiction(right)
assert len(explanation.reasons) == 1
assert isinstance(explanation.reasons[0].left, Holding)
assert isinstance(explanation.reasons[0].right, Holding)
def test_contradiction_of_holding(
self, make_opinion_with_holding, e_search_clause, make_holding
):
assert make_opinion_with_holding["watt_majority"].contradicts(
make_holding["h2_output_false_ALL_MUST"] + e_search_clause
)
def test_explain_opinion_contradicting_holding(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
lotus = make_opinion_with_holding["lotus_majority"]
explanation = oracle.explain_contradiction(lotus.holdings[6])
assert (
"<the java api> is like <the lotus menu command hierarchy>"
in str(explanation).lower()
)
def test_explain_opinion_contradicting_rule(self, make_opinion_with_holding):
oracle = make_opinion_with_holding["oracle_majority"]
lotus = make_opinion_with_holding["lotus_majority"]
explanation = oracle.explain_contradiction(lotus.holdings[6].rule)
expected = "<the java api> is like <the lotus menu command hierarchy>"
assert expected in str(explanation).lower()
def test_contradiction_of_decision(
self, make_opinion_with_holding, make_decision_with_holding
):
assert make_opinion_with_holding["oracle_majority"].contradicts(
make_decision_with_holding["lotus"]
)
def test_explain_opinion_contradicting_decision(
self, make_opinion_with_holding, make_decision_with_holding
):
oracle_majority = make_opinion_with_holding["oracle_majority"]
lotus = make_decision_with_holding["lotus"]
explanation = oracle_majority.explain_contradiction(lotus)
assert "contradicts" in str(explanation).lower()
def test_no_explanation_of_contradiction(self, make_opinion_with_holding):
watt = make_opinion_with_holding["watt_majority"]
holdings = watt.holdings[:2]
assert isinstance(holdings, HoldingGroup)
assert watt.implies(holdings)
explanation = watt.explain_contradiction(holdings)
assert explanation is None
def test_error_contradiction_with_procedure(self, make_opinion, make_procedure):
with pytest.raises(TypeError):
make_opinion["watt_majority"].contradicts(make_procedure["c1"])