rag-wright 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. rag_wright/__init__.py +13 -0
  2. rag_wright/api/__init__.py +33 -0
  3. rag_wright/api/config.py +59 -0
  4. rag_wright/api/discover.py +70 -0
  5. rag_wright/api/documents.py +39 -0
  6. rag_wright/api/ids.py +31 -0
  7. rag_wright/api/invoke.py +99 -0
  8. rag_wright/api/kg.py +61 -0
  9. rag_wright/api/mcp.py +94 -0
  10. rag_wright/api/usage.py +30 -0
  11. rag_wright/api/workspace.py +85 -0
  12. rag_wright/capabilities/__init__.py +8 -0
  13. rag_wright/capabilities/answer_generator.py +427 -0
  14. rag_wright/capabilities/ard.py +286 -0
  15. rag_wright/capabilities/assertion_extraction.py +79 -0
  16. rag_wright/capabilities/chunk_read.py +58 -0
  17. rag_wright/capabilities/chunk_write.py +163 -0
  18. rag_wright/capabilities/claim_extraction.py +153 -0
  19. rag_wright/capabilities/clause_exception_linking.py +117 -0
  20. rag_wright/capabilities/compliance_judgment.py +322 -0
  21. rag_wright/capabilities/compliance_store.py +87 -0
  22. rag_wright/capabilities/contract_kg_serve.py +156 -0
  23. rag_wright/capabilities/contract_kg_store.py +251 -0
  24. rag_wright/capabilities/dg_extraction.py +585 -0
  25. rag_wright/capabilities/disambiguation.py +163 -0
  26. rag_wright/capabilities/document_parse.py +87 -0
  27. rag_wright/capabilities/document_scope.py +49 -0
  28. rag_wright/capabilities/embedding.py +164 -0
  29. rag_wright/capabilities/embedding_profiles.py +43 -0
  30. rag_wright/capabilities/entity_resolution.py +154 -0
  31. rag_wright/capabilities/fusion.py +64 -0
  32. rag_wright/capabilities/graph_extraction.py +243 -0
  33. rag_wright/capabilities/graph_query.py +73 -0
  34. rag_wright/capabilities/graph_storage.py +111 -0
  35. rag_wright/capabilities/highlight_serve.py +142 -0
  36. rag_wright/capabilities/hybrid_search.py +65 -0
  37. rag_wright/capabilities/invoke.py +31 -0
  38. rag_wright/capabilities/jev_decision.py +38 -0
  39. rag_wright/capabilities/manifests.py +872 -0
  40. rag_wright/capabilities/okf_navigate.py +456 -0
  41. rag_wright/capabilities/parsing.py +286 -0
  42. rag_wright/capabilities/property_boosted_retrieval.py +125 -0
  43. rag_wright/capabilities/query_function_classifier.py +94 -0
  44. rag_wright/capabilities/query_understanding.py +109 -0
  45. rag_wright/capabilities/registry.py +262 -0
  46. rag_wright/capabilities/remote_encoders.py +94 -0
  47. rag_wright/capabilities/requirement_extraction.py +247 -0
  48. rag_wright/capabilities/reranking.py +123 -0
  49. rag_wright/capabilities/retrieval_core.py +126 -0
  50. rag_wright/capabilities/rlm_chunking.py +808 -0
  51. rag_wright/capabilities/rlm_synthesis.py +316 -0
  52. rag_wright/capabilities/scan_quality.py +136 -0
  53. rag_wright/capabilities/span_relevance_judgment.py +191 -0
  54. rag_wright/capabilities/vision_to_text.py +85 -0
  55. rag_wright/capabilities/vlm_ocr.py +85 -0
  56. rag_wright/contracts/__init__.py +6 -0
  57. rag_wright/contracts/chunk.py +79 -0
  58. rag_wright/contracts/compliance.py +303 -0
  59. rag_wright/contracts/contract_meta.py +27 -0
  60. rag_wright/contracts/extraction.py +130 -0
  61. rag_wright/contracts/function.py +167 -0
  62. rag_wright/contracts/function_routing.py +91 -0
  63. rag_wright/contracts/highlight.py +74 -0
  64. rag_wright/contracts/identifiers.py +153 -0
  65. rag_wright/contracts/jurisdiction.py +96 -0
  66. rag_wright/contracts/ontology.py +142 -0
  67. rag_wright/contracts/property.py +201 -0
  68. rag_wright/contracts/provenance.py +78 -0
  69. rag_wright/contracts/query_intent.py +53 -0
  70. rag_wright/contracts/span.py +76 -0
  71. rag_wright/contracts/value_match.py +84 -0
  72. rag_wright/corpus/__init__.py +0 -0
  73. rag_wright/corpus/canonicalize.py +116 -0
  74. rag_wright/corpus/cuad.py +153 -0
  75. rag_wright/corpus/cuad_ingestion.py +72 -0
  76. rag_wright/corpus/document_parser.py +299 -0
  77. rag_wright/corpus/edgar.py +231 -0
  78. rag_wright/corpus/gcs_ingestion.py +120 -0
  79. rag_wright/corpus/http.py +110 -0
  80. rag_wright/corpus/selection.py +152 -0
  81. rag_wright/mcp/__init__.py +11 -0
  82. rag_wright/mcp/compliance_server.py +299 -0
  83. rag_wright/mcp/intra_document_qa_server.py +170 -0
  84. rag_wright/mcp/relational_qa_server.py +171 -0
  85. rag_wright/mcp/session_store.py +64 -0
  86. rag_wright/mcp/typed_property_retrieval_server.py +191 -0
  87. rag_wright/models/__init__.py +8 -0
  88. rag_wright/models/profiles.py +331 -0
  89. rag_wright/models/seam.py +497 -0
  90. rag_wright/models/tag_structured.py +285 -0
  91. rag_wright/models/tracing.py +179 -0
  92. rag_wright/models/usage.py +102 -0
  93. rag_wright/okf/__init__.py +11 -0
  94. rag_wright/okf/compile.py +292 -0
  95. rag_wright/okf/document.py +47 -0
  96. rag_wright/okf/enrich.py +176 -0
  97. rag_wright/okf/links.py +190 -0
  98. rag_wright/okf/lint.py +105 -0
  99. rag_wright/ontology/__init__.py +6 -0
  100. rag_wright/ontology/_generated_template_meta.py +60 -0
  101. rag_wright/ontology/_generated_vocab.py +52 -0
  102. rag_wright/ontology/clause_template.py +964 -0
  103. rag_wright/ontology/codegen.py +84 -0
  104. rag_wright/ontology/compliance_bridge.ttl +186 -0
  105. rag_wright/ontology/contract_bridge.ttl +2685 -0
  106. rag_wright/ontology/contract_taxonomy.py +24 -0
  107. rag_wright/ontology/derive.py +58 -0
  108. rag_wright/ontology/loader.py +435 -0
  109. rag_wright/ontology/packs/ftc_16cfr255.ttl +29 -0
  110. rag_wright/ontology/registry.py +87 -0
  111. rag_wright/ontology/template_introspect.py +100 -0
  112. rag_wright/py.typed +0 -0
  113. rag_wright/reference/__init__.py +2 -0
  114. rag_wright/reference/compliance.py +41 -0
  115. rag_wright/reference/contract_seam.py +123 -0
  116. rag_wright/skills/__init__.py +7 -0
  117. rag_wright/skills/claim_extraction/SKILL.md +47 -0
  118. rag_wright/skills/claim_extraction/__init__.py +1 -0
  119. rag_wright/skills/claim_extraction/template.py +50 -0
  120. rag_wright/skills/compliance_judgment/SKILL.md +59 -0
  121. rag_wright/skills/corpus_ingest/SKILL.md +106 -0
  122. rag_wright/skills/extraction_semantic_judge/SKILL.md +51 -0
  123. rag_wright/skills/extraction_semantic_judge/__init__.py +1 -0
  124. rag_wright/skills/generation/SKILL.md +64 -0
  125. rag_wright/skills/generation/__init__.py +1 -0
  126. rag_wright/skills/generic_compliance_judgment/SKILL.md +58 -0
  127. rag_wright/skills/okf_navigate/SKILL.md +137 -0
  128. rag_wright/skills/requirement_extraction/SKILL.md +47 -0
  129. rag_wright/skills/requirement_extraction/__init__.py +1 -0
  130. rag_wright/skills/requirement_extraction/template.py +50 -0
  131. rag_wright/skills/rlm/SKILL.md +186 -0
  132. rag_wright/skills/rlm/__init__.py +31 -0
  133. rag_wright/skills/rlm/agent.py +292 -0
  134. rag_wright/skills/span_relevance_judgment/SKILL.md +67 -0
  135. rag_wright/skills/vision_to_text/SKILL.md +36 -0
  136. rag_wright/skills/vision_to_text/__init__.py +1 -0
  137. rag_wright/spans/__init__.py +1 -0
  138. rag_wright/spans/boundary.py +78 -0
  139. rag_wright/spans/clause_function_classifier.py +490 -0
  140. rag_wright/spans/clause_kg_extractor.py +337 -0
  141. rag_wright/spans/cuad_labels.py +81 -0
  142. rag_wright/spans/dim_classifier.py +158 -0
  143. rag_wright/spans/dim_fleet.json +411 -0
  144. rag_wright/spans/function_classifier.py +77 -0
  145. rag_wright/spans/function_families.py +62 -0
  146. rag_wright/spans/hybrid_classifier.py +103 -0
  147. rag_wright/spans/legalbert_classifier.py +83 -0
  148. rag_wright/spans/model_capabilities.py +107 -0
  149. rag_wright/spans/new_function_labels.py +111 -0
  150. rag_wright/spans/page_map.py +68 -0
  151. rag_wright/spans/property_extractor.py +365 -0
  152. rag_wright/spans/property_grounding.py +182 -0
  153. rag_wright/spans/reclassify.py +77 -0
  154. rag_wright/spans/scarce_function_labels.py +105 -0
  155. rag_wright/spans/segment.py +341 -0
  156. rag_wright/spans/semantic_judge.py +197 -0
  157. rag_wright/spans/symbolic_validation.py +131 -0
  158. rag_wright/spans/tag_clause_extractor.py +182 -0
  159. rag_wright/store/__init__.py +6 -0
  160. rag_wright/store/arcadedb.py +1135 -0
  161. rag_wright/store/chunk_text.py +66 -0
  162. rag_wright/store/seam.py +213 -0
  163. rag_wright/subgraphs/__init__.py +0 -0
  164. rag_wright/subgraphs/async_ingestion.py +204 -0
  165. rag_wright/subgraphs/compliance_check.py +1042 -0
  166. rag_wright/subgraphs/compliance_ingestion.py +306 -0
  167. rag_wright/subgraphs/contract_ingestion_pipeline.py +999 -0
  168. rag_wright/subgraphs/graph_extraction.py +102 -0
  169. rag_wright/subgraphs/intra_document_qa.py +328 -0
  170. rag_wright/subgraphs/observability.py +140 -0
  171. rag_wright/subgraphs/query_constraint_extraction.py +73 -0
  172. rag_wright/subgraphs/relational_qa.py +165 -0
  173. rag_wright/subgraphs/requirement_extraction.py +137 -0
  174. rag_wright/subgraphs/scaffold.py +65 -0
  175. rag_wright/subgraphs/semantic_chunking.py +183 -0
  176. rag_wright/subgraphs/typed_clause_extraction.py +172 -0
  177. rag_wright/subgraphs/typed_property_retrieval.py +278 -0
  178. rag_wright/util/__init__.py +1 -0
  179. rag_wright/util/concurrent.py +153 -0
  180. rag_wright/util/spacy_model.py +45 -0
  181. rag_wright-0.1.0.dist-info/METADATA +168 -0
  182. rag_wright-0.1.0.dist-info/RECORD +184 -0
  183. rag_wright-0.1.0.dist-info/WHEEL +4 -0
  184. rag_wright-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,411 @@
1
+ {
2
+ "assignment_consent": {
3
+ "floor": 0.88,
4
+ "framework": "laya",
5
+ "model": "A_thematic__temporal_consents",
6
+ "question": {
7
+ "criteria": {
8
+ "consent_required": "assignment requires the other party's prior consent / approval before it is effective",
9
+ "free": "assignment is permitted freely \u2014 no consent and no notice required",
10
+ "notice_only": "assignment is permitted without consent, but the assigning party must give notice to the other"
11
+ },
12
+ "instructions": "May a party assign this agreement, and under what condition?",
13
+ "type": "choice"
14
+ },
15
+ "top_k": 2
16
+ },
17
+ "cap_basis": {
18
+ "floor": 0.5,
19
+ "framework": "setfit",
20
+ "model": "setfit_all_all-mpnet-base-v2_dim_cap_basis__silver_mpnet",
21
+ "top_k": 2
22
+ },
23
+ "carve_out": {
24
+ "floor": 0.67,
25
+ "framework": "setfit",
26
+ "model": "setfit_all_legal-bert-base-uncased_dim_carve_out__silver_v1",
27
+ "top_k": 3
28
+ },
29
+ "claim_scope": {
30
+ "floor": 0.923,
31
+ "framework": "laya",
32
+ "model": "A_thematic__scope_claims",
33
+ "question": {
34
+ "criteria": {
35
+ "broad_based": "covers claims broadly \u2014 both first-party and third-party, or all claims without limitation",
36
+ "first_party": "covers claims between the contracting parties themselves (direct / first-party claims)",
37
+ "third_party": "covers claims brought by third parties (outside the contract) against a party"
38
+ },
39
+ "instructions": "What scope of claims does this clause cover?",
40
+ "type": "choice"
41
+ },
42
+ "top_k": 2
43
+ },
44
+ "coc_consent": {
45
+ "floor": 1.0,
46
+ "framework": "laya",
47
+ "model": "B_mixed__mix4",
48
+ "question": {
49
+ "criteria": {
50
+ "consent_required": "a change of control requires the other party's consent, or triggers a right to approve or terminate",
51
+ "notice_only": "a change of control requires only notice to the other party, not consent",
52
+ "unrestricted": "a change of control is unrestricted \u2014 no consent and no notice required"
53
+ },
54
+ "instructions": "On a change of control, what is required of the affected party?",
55
+ "type": "choice"
56
+ },
57
+ "top_k": 2
58
+ },
59
+ "collateral_type": {
60
+ "abstain": true,
61
+ "framework": "laya",
62
+ "model": "collateral_type__abstain",
63
+ "question": {
64
+ "criteria": {
65
+ "accounts_receivable": "accounts receivable / receivables are the collateral",
66
+ "all_assets": "a blanket lien on substantially all assets",
67
+ "deposit_accounts": "deposit accounts are the collateral",
68
+ "equipment": "equipment is the collateral",
69
+ "fixtures": "fixtures are the collateral",
70
+ "general_intangibles": "general intangibles are the collateral",
71
+ "inventory": "inventory is the collateral",
72
+ "ip": "intellectual property is the collateral",
73
+ "none": "this provision does not state what collateral secures an interest",
74
+ "real_property": "real property / real estate is the collateral"
75
+ },
76
+ "instructions": "What collateral secures the interest in this provision?",
77
+ "type": "choice"
78
+ },
79
+ "top_k": 1
80
+ },
81
+ "condition_type": {
82
+ "abstain": true,
83
+ "framework": "laya",
84
+ "model": "condition_type__abstain",
85
+ "question": {
86
+ "criteria": {
87
+ "closing_condition": "a condition that must be satisfied before closing (a closing condition)",
88
+ "none": "this provision does not state a condition-precedent type"
89
+ },
90
+ "instructions": "What kind of condition precedent does this provision state?",
91
+ "type": "choice"
92
+ },
93
+ "top_k": 1
94
+ },
95
+ "confidentiality_exception": {
96
+ "abstain": true,
97
+ "framework": "laya",
98
+ "model": "confidentiality_exception__abstain",
99
+ "question": {
100
+ "criteria": {
101
+ "independently_developed": "information independently developed without use of the disclosure",
102
+ "none": "this provision does not state a confidentiality exception",
103
+ "prior_possession": "information already in the recipient's possession before disclosure",
104
+ "publicly_available": "information that is or becomes publicly available",
105
+ "required_by_law": "disclosure required by law or a court/government order",
106
+ "third_party_source": "information rightfully received from a third party"
107
+ },
108
+ "instructions": "Which exception to confidentiality does this provision state?",
109
+ "type": "choice"
110
+ },
111
+ "top_k": 1
112
+ },
113
+ "covered_parties": {
114
+ "floor": 0.73,
115
+ "framework": "setfit",
116
+ "model": "setfit_all_legal-bert-base-uncased_dim_covered_parties__v2",
117
+ "top_k": 2
118
+ },
119
+ "covered_subject": {
120
+ "floor": 1.0,
121
+ "framework": "laya",
122
+ "model": "A_thematic__scope_claims",
123
+ "question": {
124
+ "criteria": {
125
+ "copyright": "copyright infringement or copyright-related claims",
126
+ "fraud": "fraud or fraudulent conduct",
127
+ "gross_negligence": "gross negligence",
128
+ "ip_infringement": "intellectual-property infringement generally (patent / IP rights, not specifically copyright or trademark)",
129
+ "trademark": "trademark infringement or trademark-related claims",
130
+ "violation_of_law": "violation of law or non-compliance with legal requirements"
131
+ },
132
+ "instructions": "What subject matter does this indemnity or coverage concern?",
133
+ "type": "choice"
134
+ },
135
+ "top_k": 3
136
+ },
137
+ "damage_type": {
138
+ "floor": 1.0,
139
+ "framework": "laya",
140
+ "model": "A_thematic__scope_claims",
141
+ "question": {
142
+ "criteria": {
143
+ "consequential": "consequential damages \u2014 losses flowing indirectly from the breach (e.g. lost profits, lost business)",
144
+ "incidental": "incidental damages \u2014 costs incurred incidental to the breach",
145
+ "indirect": "indirect damages \u2014 not the direct or immediate result of the breach",
146
+ "punitive": "punitive or exemplary damages \u2014 awarded to punish, beyond compensation",
147
+ "special": "special damages \u2014 arising from special or particular circumstances"
148
+ },
149
+ "instructions": "Which category of damages does this clause address (e.g. exclude or waive)?",
150
+ "type": "choice"
151
+ },
152
+ "top_k": 3
153
+ },
154
+ "dispute_method": {
155
+ "abstain": true,
156
+ "framework": "laya",
157
+ "model": "dispute_method__abstain",
158
+ "question": {
159
+ "criteria": {
160
+ "arbitration": "disputes are resolved by binding arbitration",
161
+ "litigation": "disputes are resolved by litigation in the courts",
162
+ "none": "this provision does not specify how disputes are resolved"
163
+ },
164
+ "instructions": "How are disputes resolved under this provision?",
165
+ "type": "choice"
166
+ },
167
+ "top_k": 1
168
+ },
169
+ "escrow_release_trigger": {
170
+ "abstain": true,
171
+ "framework": "laya",
172
+ "model": "escrow_release_trigger__abstain",
173
+ "question": {
174
+ "criteria": {
175
+ "bankruptcy": "the depositor's bankruptcy or insolvency triggers release",
176
+ "breach": "a material breach by the depositor triggers release",
177
+ "discontinuance": "discontinuance of the product or support triggers release",
178
+ "none": "this provision does not state an escrow release trigger"
179
+ },
180
+ "instructions": "What triggers release from escrow in this provision?",
181
+ "type": "choice"
182
+ },
183
+ "top_k": 1
184
+ },
185
+ "exclusivity_type": {
186
+ "floor": 1.0,
187
+ "framework": "laya",
188
+ "model": "A_thematic__ip_restrictions",
189
+ "question": {
190
+ "criteria": {
191
+ "exclusive": "an exclusive right \u2014 granted to the grantee to the exclusion of everyone, including the grantor",
192
+ "non_exclusive": "a non-exclusive right \u2014 the grantor may grant the same right to others as well",
193
+ "sole": "a sole right \u2014 granted to the grantee and the grantor keeps rights, but no other third party is granted it"
194
+ },
195
+ "instructions": "What kind of license or right does this clause grant?",
196
+ "type": "choice"
197
+ },
198
+ "top_k": 2
199
+ },
200
+ "favorability": {
201
+ "floor": 0.667,
202
+ "framework": "laya",
203
+ "model": "B_mixed__mix1",
204
+ "question": {
205
+ "criteria": {
206
+ "buyer_favorable": "terms tilt toward the buyer / licensee / customer / receiving party \u2014 limiting their liability or obligations, or expanding their rights/remedies",
207
+ "seller_favorable": "terms tilt toward the seller / licensor / provider / vendor / disclosing party \u2014 limiting their liability or obligations, or expanding their rights/remedies"
208
+ },
209
+ "instructions": "Which side does this clause favor?",
210
+ "type": "choice"
211
+ },
212
+ "top_k": 1
213
+ },
214
+ "force_majeure_event": {
215
+ "abstain": true,
216
+ "framework": "laya",
217
+ "model": "force_majeure_event__abstain",
218
+ "question": {
219
+ "criteria": {
220
+ "act_of_god": "acts of God",
221
+ "government_action": "government action, regulation, or acts of a governmental authority",
222
+ "labor_dispute": "labor disputes or strikes",
223
+ "natural_disaster": "natural disasters (flood, earthquake, fire, storm)",
224
+ "none": "this provision does not enumerate a force majeure event",
225
+ "pandemic": "pandemic, epidemic, or disease outbreak",
226
+ "supply_failure": "supplier or utility failure",
227
+ "war": "war, terrorism, or civil unrest"
228
+ },
229
+ "instructions": "Which force majeure event does this provision enumerate?",
230
+ "type": "choice"
231
+ },
232
+ "top_k": 1
233
+ },
234
+ "ip_ownership": {
235
+ "floor": 0.96,
236
+ "framework": "laya",
237
+ "model": "B_mixed__mix1",
238
+ "question": {
239
+ "criteria": {
240
+ "assigned": "IP is assigned or transferred to the other party \u2014 ownership moves away from the creator",
241
+ "joint": "IP is jointly owned by both parties",
242
+ "retained": "the creating / original party retains ownership of the IP"
243
+ },
244
+ "instructions": "Who owns the intellectual property under this clause?",
245
+ "type": "choice"
246
+ },
247
+ "top_k": 2
248
+ },
249
+ "law_multiplicity": {
250
+ "floor": 1.0,
251
+ "framework": "laya",
252
+ "model": "A_thematic__temporal_consents",
253
+ "question": {
254
+ "criteria": {
255
+ "multiple": "multiple governing laws / jurisdictions apply (e.g. different laws for different parts, parties, or matters)",
256
+ "single": "a single governing law / jurisdiction governs the agreement"
257
+ },
258
+ "instructions": "Does this governing-law clause name one governing law or several?",
259
+ "type": "choice"
260
+ },
261
+ "top_k": 1
262
+ },
263
+ "mfn_scope": {
264
+ "floor": 1.0,
265
+ "framework": "laya",
266
+ "model": "B_mixed__mix3",
267
+ "question": {
268
+ "criteria": {
269
+ "price": "the MFN applies to price only \u2014 the best price given to others must be extended here",
270
+ "price_and_terms": "the MFN applies to both price and terms",
271
+ "terms": "the MFN applies to terms or conditions only, not price"
272
+ },
273
+ "instructions": "What does this most-favored-nation (MFN) clause cover?",
274
+ "type": "choice"
275
+ },
276
+ "top_k": 2
277
+ },
278
+ "mutuality": {
279
+ "floor": 0.67,
280
+ "framework": "setfit",
281
+ "model": "setfit_all_legal-bert-base-uncased_dim_mutuality__silver_v1",
282
+ "top_k": 1
283
+ },
284
+ "nonsolicit_target": {
285
+ "floor": 0.68,
286
+ "framework": "setfit",
287
+ "model": "setfit_all_legal-bert-base-uncased_dim_nonsolicit_target__v2",
288
+ "top_k": 1
289
+ },
290
+ "party_asymmetry": {
291
+ "floor": 0.727,
292
+ "framework": "laya",
293
+ "model": "B_mixed__mix3",
294
+ "question": {
295
+ "criteria": {
296
+ "different_per_party": "the obligation or its terms differ by party (one party bears more, or different, terms than the other)",
297
+ "symmetric": "both parties bear the same obligation, liability, or terms equally / reciprocally"
298
+ },
299
+ "instructions": "Does this clause impose the SAME obligations/terms on both parties, or DIFFERENT ones per party?",
300
+ "type": "choice"
301
+ },
302
+ "top_k": 1
303
+ },
304
+ "procedural": {
305
+ "floor": 1.0,
306
+ "framework": "laya",
307
+ "model": "B_mixed__mix4",
308
+ "question": {
309
+ "criteria": {
310
+ "control_of_defense": "the clause allocates control of the defense \u2014 who directs the litigation and any settlement",
311
+ "duty_to_defend": "the indemnifying party has a duty to defend \u2014 it must provide or assume the legal defense of the claim"
312
+ },
313
+ "instructions": "What indemnification procedure does this clause specify?",
314
+ "type": "choice"
315
+ },
316
+ "top_k": 1
317
+ },
318
+ "renewal_mechanism": {
319
+ "floor": 0.533,
320
+ "framework": "laya",
321
+ "model": "C_singleton__renewal_mechanism",
322
+ "question": {
323
+ "criteria": {
324
+ "auto": "the term renews automatically (auto-renewal / evergreen) unless a party gives notice of non-renewal",
325
+ "requires_notice": "renewal requires an affirmative act \u2014 a party must give notice, or elect, to renew for the term to extend"
326
+ },
327
+ "instructions": "How does this agreement's term renew?",
328
+ "type": "choice"
329
+ },
330
+ "top_k": 1
331
+ },
332
+ "restriction_scope": {
333
+ "floor": 1.0,
334
+ "framework": "laya",
335
+ "model": "A_thematic__ip_restrictions",
336
+ "question": {
337
+ "criteria": {
338
+ "activity": "restricted by activity or line of business only",
339
+ "geographic": "restricted by geography or territory only",
340
+ "geographic_and_activity": "restricted by both geography and activity"
341
+ },
342
+ "instructions": "How is this restrictive covenant (e.g. non-compete) scoped?",
343
+ "type": "choice"
344
+ },
345
+ "top_k": 2
346
+ },
347
+ "right_of_first_type": {
348
+ "abstain": true,
349
+ "framework": "laya",
350
+ "model": "right_of_first_type__abstain",
351
+ "question": {
352
+ "criteria": {
353
+ "none": "this provision does not state a right-of-first type",
354
+ "rofn": "a right of first negotiation (ROFN): the right to negotiate first",
355
+ "rofo": "a right of first offer (ROFO): the right to receive the first offer before others",
356
+ "rofr": "a right of first refusal (ROFR): the right to match a third party's offer"
357
+ },
358
+ "instructions": "Which kind of 'right of first ...' does this provision grant?",
359
+ "type": "choice"
360
+ },
361
+ "top_k": 1
362
+ },
363
+ "royalty_basis": {
364
+ "abstain": true,
365
+ "framework": "laya",
366
+ "model": "royalty_basis__abstain",
367
+ "question": {
368
+ "criteria": {
369
+ "fixed": "a fixed royalty amount",
370
+ "none": "this provision does not state a royalty basis",
371
+ "pct_gross_sales": "a percentage of gross sales / gross revenue",
372
+ "pct_net_sales": "a percentage of net sales / net revenue",
373
+ "per_unit": "a per-unit royalty",
374
+ "tiered": "a tiered or graduated royalty schedule"
375
+ },
376
+ "instructions": "How is the royalty computed in this provision?",
377
+ "type": "choice"
378
+ },
379
+ "top_k": 1
380
+ },
381
+ "termination_right": {
382
+ "floor": 0.84,
383
+ "framework": "laya",
384
+ "model": "A_thematic__temporal_consents",
385
+ "question": {
386
+ "criteria": {
387
+ "either_party": "both parties have the right to terminate the agreement (mutual / either-side termination right)",
388
+ "one_party": "only one specified party has the right to terminate (a unilateral termination right; the other party cannot)"
389
+ },
390
+ "instructions": "Which party has the right to terminate this agreement?",
391
+ "type": "choice"
392
+ },
393
+ "top_k": 1
394
+ },
395
+ "warranty_scope": {
396
+ "floor": 1.0,
397
+ "framework": "laya",
398
+ "model": "A_thematic__ip_restrictions",
399
+ "question": {
400
+ "criteria": {
401
+ "as_is": "goods or services are provided 'AS IS', with warranties disclaimed",
402
+ "express": "provides an express warranty \u2014 an affirmatively stated warranty",
403
+ "implied": "concerns implied warranties (e.g. merchantability, fitness for purpose) \u2014 extending or disclaiming them",
404
+ "non_reliance": "a non-reliance provision \u2014 the party did not rely on representations made outside the contract"
405
+ },
406
+ "instructions": "What does this warranty clause do?",
407
+ "type": "choice"
408
+ },
409
+ "top_k": 2
410
+ }
411
+ }
@@ -0,0 +1,77 @@
1
+ """T56 (FR-R, ADR-0025): local single-label clause-function classifier.
2
+
3
+ A linear head over frozen dense embeddings of an operative span, predicting one of the 41 CUAD clause types
4
+ (`ClauseCategory`) or NONE. Trained on CUAD gold spans (segmented to operative-span granularity, T55), applied
5
+ at inference to ACORD operative spans. Inference is a matmul -- local, deterministic, no LLM call, categorically
6
+ more scalable than an LLM routing call per clause. If the per-type F1 lags, the plan escalates the feature side
7
+ to a fine-tuned encoder (LegalBERT); the head/contract here stay the same.
8
+
9
+ The embedder is an injected seam (`DenseEmbedder`), so training and inference share one representation and the
10
+ hermetic tests run with a stub -- no model load.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+ from typing import Any, Protocol, runtime_checkable
17
+
18
+ import joblib
19
+ from sklearn.linear_model import LogisticRegression
20
+
21
+ NONE_LABEL = "NONE" # the span is none of the 41 CUAD clause types (boilerplate / recital / definition)
22
+
23
+
24
+ @runtime_checkable
25
+ class DenseEmbedder(Protocol):
26
+ """The dense-embedding seam (the T5 `Embedder`'s dense leg): span text -> a fixed-length vector."""
27
+
28
+ def encode_dense(self, text: str) -> list[float]: ...
29
+
30
+
31
+ class FunctionClassifier:
32
+ """A fitted single-label function classifier over span dense embeddings (predicts a CUAD type or NONE)."""
33
+
34
+ def __init__(self, model: LogisticRegression) -> None:
35
+ self._model = model
36
+
37
+ @classmethod
38
+ def train_on_features(
39
+ cls, features: list[list[float]], labels: list[str], **model_kwargs: Any
40
+ ) -> "FunctionClassifier":
41
+ """Fit the linear head on precomputed dense features (the batch path -- callers batch-embed first).
42
+ `class_weight='balanced'` offsets the heavy NONE class; dimension-agnostic (fits the feature width)."""
43
+ if len(features) != len(labels):
44
+ raise ValueError("features and labels must be the same length")
45
+ if not features:
46
+ raise ValueError("need at least one training example")
47
+ params: dict[str, Any] = {"max_iter": 2000, "class_weight": "balanced"}
48
+ params.update(model_kwargs)
49
+ return cls(LogisticRegression(**params).fit(features, labels))
50
+
51
+ @classmethod
52
+ def train(
53
+ cls, texts: list[str], labels: list[str], *, embedder: DenseEmbedder, **model_kwargs: Any
54
+ ) -> "FunctionClassifier":
55
+ """Convenience: embed `texts` one-by-one via the seam, then fit (used by tests / small sets)."""
56
+ if len(texts) != len(labels):
57
+ raise ValueError("texts and labels must be the same length")
58
+ return cls.train_on_features([embedder.encode_dense(t) for t in texts], labels, **model_kwargs)
59
+
60
+ def classify_features(self, features: list[list[float]]) -> list[str]:
61
+ """One label per precomputed feature vector (a CUAD type or NONE). Empty input -> empty output."""
62
+ if not features:
63
+ return []
64
+ return [str(y) for y in self._model.predict(features)]
65
+
66
+ def classify(self, texts: list[str], *, embedder: DenseEmbedder) -> list[str]:
67
+ """Convenience: embed `texts` via the seam, then classify. Empty input -> empty output."""
68
+ if not texts:
69
+ return []
70
+ return self.classify_features([embedder.encode_dense(t) for t in texts])
71
+
72
+ def save(self, path: Path) -> None:
73
+ joblib.dump(self._model, path)
74
+
75
+ @classmethod
76
+ def load(cls, path: Path) -> "FunctionClassifier":
77
+ return cls(joblib.load(path))
@@ -0,0 +1,62 @@
1
+ """Step-5b: confusable-clause FAMILIES for the LLM-hybrid classifier. Derived data-drivenly from the
2
+ LegalBERT model's holdout confusion (>=0.12 of a class's gold spans predicted as the sibling; connected
3
+ components). `ROUTE_FAMILIES` is the dev-validated subset where an LLM (Gemma) beats LegalBERT at the
4
+ sibling distinction -- only these route to the LLM; the rest keep LegalBERT (which is stronger on them, e.g.
5
+ Cap-vs-Uncapped, which Gemma craters). Labels are canonical `FUNCTION_LABELS`; membership is canonicalized
6
+ so it matches the classifier's CUAD-cased output too."""
7
+
8
+ from __future__ import annotations
9
+
10
+ from rag_wright.contracts.function import canonical_function
11
+
12
+ # All confusable families (from the confusion graph). Kept for reference / diagnostics.
13
+ CONFUSABLE_FAMILIES: tuple[tuple[str, ...], ...] = (
14
+ ("Affiliate License-Licensee", "Competitive Restriction Exception", "Exclusivity",
15
+ "Irrevocable Or Perpetual License", "License Grant", "Non-Transferable License"),
16
+ ("Anti-Assignment", "Change Of Control"),
17
+ ("Effective Date", "Expiration Date"),
18
+ ("Unlimited/All-You-Can-Eat-License", "Volume Restriction"),
19
+ ("Affiliate License-Licensor", "IP Ownership Assignment", "Joint IP Ownership"),
20
+ ("Notice Period To Terminate Renewal", "Price Restrictions", "Renewal Term", "Termination For Convenience"),
21
+ ("Most Favored Nation", "No-Solicit Of Customers", "Non-Compete"),
22
+ ("Cap On Liability", "Uncapped Liability"),
23
+ ("Liquidated Damages", "Revenue/Profit Sharing"),
24
+ ("Indemnification", "No-Solicit Of Employees", "Third Party Beneficiary"),
25
+ )
26
+
27
+ # The dev-validated LLM-winning subset: route ONLY these to Gemma (test-half mean non-NONE recall
28
+ # 0.627 -> 0.641; fixes Notice-Period 0->0.33, Irrevocable 0->0.50, No-Solicit 0.17->0.50). The excluded
29
+ # families (liability, control, dates, ...) keep LegalBERT, which is stronger there.
30
+ ROUTE_FAMILIES: tuple[tuple[str, ...], ...] = (
31
+ ("Affiliate License-Licensee", "Competitive Restriction Exception", "Exclusivity",
32
+ "Irrevocable Or Perpetual License", "License Grant", "Non-Transferable License"),
33
+ ("Affiliate License-Licensor", "IP Ownership Assignment", "Joint IP Ownership"),
34
+ ("Notice Period To Terminate Renewal", "Price Restrictions", "Renewal Term", "Termination For Convenience"),
35
+ ("Most Favored Nation", "No-Solicit Of Customers", "Non-Compete"),
36
+ ("Liquidated Damages", "Revenue/Profit Sharing"),
37
+ )
38
+
39
+ # The rare, low-recall classes (all in a ROUTE family) worth rescuing with the LLM. TARGETED routing fires
40
+ # only when one of these is in the LegalBERT top-2 -- so the common/strong classes never route (zero risk to
41
+ # them), and only a fraction of a percent of spans hit the LLM. These went from ~0 to 0.33-0.67 with routing.
42
+ RARE_TARGETS: frozenset[str] = frozenset({
43
+ "Irrevocable Or Perpetual License", "Notice Period To Terminate Renewal", "No-Solicit Of Customers",
44
+ "Non-Transferable License", "Competitive Restriction Exception", "Price Restrictions",
45
+ "Affiliate License-Licensee",
46
+ })
47
+
48
+ for _fam in CONFUSABLE_FAMILIES: # canonical-guard the labels at import
49
+ assert all(canonical_function(c) for c in _fam), f"non-canonical label in family {_fam}"
50
+ assert all(canonical_function(c) for c in RARE_TARGETS), "non-canonical label in RARE_TARGETS"
51
+
52
+ _ROUTE_OF: dict[str, tuple[str, ...]] = {
53
+ canonical_function(c): tuple(canonical_function(x) for x in fam)
54
+ for fam in ROUTE_FAMILIES
55
+ for c in fam
56
+ }
57
+
58
+
59
+ def route_family(label: str) -> tuple[str, ...] | None:
60
+ """The route-family (canonical labels) that `label` belongs to, or None if `label` is not in a routed
61
+ family (case-insensitive: the classifier's CUAD-cased output maps in too). None => keep LegalBERT."""
62
+ return _ROUTE_OF.get(canonical_function(label or ""))
@@ -0,0 +1,103 @@
1
+ """Step-5b: the LLM-hybrid function classifier. LegalBERT predicts every span (fast, local); when its top-2
2
+ are confusable SIBLINGS in a dev-validated ROUTE family, the span is routed to a Gemma confirm among that
3
+ family (the LLM distinguishes those clause types better -- Notice-Period, Irrevocable, No-Solicit went from
4
+ ~0 to 0.33-0.67). Non-routed spans keep LegalBERT (stronger on the other families, e.g. Cap-vs-Uncapped).
5
+ Only ~3% of spans route, so the LLM cost is small. Confirmer defaults to GEMMA (benchmarked); injectable for
6
+ tests. The routed LLM calls in one `classify` batch run CONCURRENTLY (the standing eval/serve concurrency rule).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ from typing import Optional, Protocol, runtime_checkable
13
+
14
+ from pydantic import BaseModel
15
+
16
+ from rag_wright.contracts.function import canonical_function
17
+ from rag_wright.models.profiles import ModelRole, model_for
18
+ from rag_wright.models.seam import build_structured
19
+ from rag_wright.spans.function_families import route_family
20
+
21
+ NONE_LABEL = "NONE"
22
+
23
+
24
+ class _FamilyLabel(BaseModel):
25
+ label: str
26
+
27
+
28
+ _PROMPT = (
29
+ "A clause classifier is unsure which of these RELATED clause types a span is:\n- {opts}\n\n"
30
+ "Decide which ONE it actually is by its operative meaning, or NONE if it is none of them. Respond with "
31
+ "EXACTLY one of the type strings above, or NONE.\n\nSpan:\n{text}"
32
+ )
33
+
34
+
35
+ @runtime_checkable
36
+ class FamilyConfirmer(Protocol):
37
+ def __call__(self, text: str, candidates: tuple[str, ...]) -> str: ...
38
+
39
+
40
+ class SeamFamilyConfirmer:
41
+ """The real confirmer: structured output on GENERAL (Gemma). Returns a validated family label or NONE
42
+ (an off-family / hallucinated answer -> NONE). Retries a transient bare `None`."""
43
+
44
+ def __init__(self, model_id: Optional[str] = None, *, retries: int = 3) -> None:
45
+ self._runnable = build_structured(model_id or model_for(ModelRole.GENERAL), _FamilyLabel)
46
+ self._retries = retries
47
+
48
+ def __call__(self, text: str, candidates: tuple[str, ...]) -> str:
49
+ prompt = _PROMPT.format(opts="\n- ".join(candidates), text=text[:2000])
50
+ for _ in range(self._retries):
51
+ try:
52
+ v = self._runnable.invoke(prompt)
53
+ except Exception: # noqa: BLE001 - transient provider/parse error; retry
54
+ continue
55
+ if v is not None:
56
+ canon = canonical_function(v.label)
57
+ return canon if canon in candidates else NONE_LABEL
58
+ return NONE_LABEL
59
+
60
+
61
+ class HybridFunctionClassifier:
62
+ """LegalBERT + selective LLM fallback on confusable-sibling spans. `classify` returns canonical labels.
63
+
64
+ `targets` (optional) narrows routing to TARGETED mode: route only when a target class is in the LegalBERT
65
+ top-2 (and the top-2 are siblings in a route family). This rescues the rare targets while leaving the
66
+ common/strong classes untouched -- fewer LLM calls, zero risk to what already works. `targets=None` routes
67
+ every sibling-ambiguous span in a route family."""
68
+
69
+ def __init__(self, bert_classifier, confirmer: Optional[FamilyConfirmer] = None, *,
70
+ targets: Optional[frozenset[str]] = None, concurrency: int = 8):
71
+ self._bert = bert_classifier
72
+ self._confirm = confirmer if confirmer is not None else SeamFamilyConfirmer()
73
+ self._targets = targets
74
+ self._concurrency = concurrency
75
+
76
+ def classify(self, texts: list[str], *, batch_size: int = 32) -> list[str]:
77
+ if not texts:
78
+ return []
79
+ topk = self._bert.classify_topk(texts, k=2, batch_size=batch_size)
80
+ labels = [canonical_function(row[0]) or row[0] for row in topk] # LegalBERT top-1 (canonical)
81
+ routed = [] # (index, family) where the top-2 are siblings in a ROUTE family
82
+ for i, row in enumerate(topk):
83
+ fam = route_family(labels[i])
84
+ top2 = canonical_function(row[1]) if len(row) > 1 else None
85
+ if fam is not None and top2 in fam:
86
+ if self._targets is not None and labels[i] not in self._targets and top2 not in self._targets:
87
+ continue # TARGETED: skip unless a rare target is one of the top-2
88
+ routed.append((i, fam))
89
+ if not routed:
90
+ return labels
91
+
92
+ async def _run():
93
+ sem = asyncio.Semaphore(self._concurrency)
94
+
95
+ async def _one(i, fam):
96
+ async with sem:
97
+ return i, await asyncio.to_thread(self._confirm, texts[i], fam)
98
+
99
+ return await asyncio.gather(*[_one(i, fam) for i, fam in routed])
100
+
101
+ for i, lab in asyncio.run(_run()):
102
+ labels[i] = lab
103
+ return labels