rulhub 0.1.0__tar.gz

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.
@@ -0,0 +1,88 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environment
7
+ venv/
8
+ .venv/
9
+
10
+ # Environment files (secrets)
11
+ /.env
12
+ .env
13
+ .env.local
14
+ .env.production
15
+ .env.staging
16
+ .env.development
17
+ .env.test
18
+ *.env
19
+ .env.*
20
+ !.env.example
21
+
22
+ # IDE settings
23
+ .vscode/
24
+ .idea/
25
+
26
+ # Migrations
27
+ alembic/versions/*.pyc
28
+ alembic/__pycache__/
29
+
30
+ # Logs
31
+ *.log
32
+
33
+ # OS junk
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+ # Security files
38
+ *.pem
39
+ *.key
40
+ *.crt
41
+ *.p12
42
+ *.pfx
43
+ secrets/
44
+ credentials/
45
+ .secrets
46
+ api_keys.txt
47
+ .password
48
+ .token
49
+
50
+ # Database files (may contain sensitive data)
51
+ *.db-journal
52
+ *.sqlite-journal
53
+
54
+ # Backup files (may contain secrets)
55
+ *.bak
56
+ *.backup
57
+ *.old
58
+ *~
59
+
60
+ # Docker secrets
61
+ .docker/
62
+ docker-compose.override.yml
63
+
64
+ # Temporary files
65
+ temp/
66
+ tmp/
67
+ .tmp/
68
+
69
+ # Editor temp files
70
+ .swp
71
+ .swo
72
+ *~
73
+
74
+ # Node modules
75
+ node_modules/
76
+ admin/node_modules/
77
+ landing/node_modules/
78
+
79
+ # Problematic Windows device filename
80
+ /nul
81
+
82
+ # Playwright artifacts
83
+ playwright-report/
84
+ test-results/
85
+ admin/playwright-report/
86
+ admin/test-results/
87
+
88
+ .vercel
rulhub-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: rulhub
3
+ Version: 0.1.0
4
+ Summary: RulHub SDK — Trade finance compliance API client
5
+ Project-URL: Homepage, https://rulhub.com
6
+ Project-URL: Documentation, https://api.rulhub.com/swagger
7
+ Project-URL: Repository, https://github.com/ripclass/RulEngine
8
+ Project-URL: Issues, https://github.com/ripclass/RulEngine/issues
9
+ Author-email: Enso Intelligence Labs <api@rulhub.com>
10
+ License-Expression: MIT
11
+ Keywords: api,compliance,icc,letter-of-credit,sanctions,trade-finance,ucp600
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Office/Business :: Financial
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.8
26
+ Requires-Dist: requests>=2.28.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # RulHub Python SDK
30
+
31
+ Trade finance compliance API — validate documents against 5,400+ ICC rules with AI-powered semantic matching.
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ pip install rulhub
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ from rulhub import RulHub
43
+
44
+ client = RulHub(api_key="ak_your_key_here")
45
+
46
+ # Validate a letter of credit
47
+ result = client.validate(
48
+ rules="ucp600",
49
+ document_type="lc",
50
+ document={
51
+ "beneficiary_name": "ABC Trading Co",
52
+ "amount": 50000,
53
+ "currency": "USD",
54
+ "goods_description": "1000 MT Thai White Rice 5% Broken",
55
+ "expiry_date": "2026-06-30",
56
+ }
57
+ )
58
+
59
+ print(f"Score: {result['score']:.0%}")
60
+ print(f"Compliant: {result['compliant']}")
61
+
62
+ for d in result["discrepancies"]:
63
+ print(f" [{d['severity']}] {d['finding']}")
64
+ if d.get("recommendation"):
65
+ print(f" Fix: {d['recommendation']}")
66
+ ```
67
+
68
+ ## Validate Multiple Documents
69
+
70
+ ```python
71
+ result = client.validate_set(documents=[
72
+ {"type": "lc", "fields": {"amount": 50000, "currency": "USD"}},
73
+ {"type": "invoice", "fields": {"total_amount": 49500, "currency": "USD"}},
74
+ ])
75
+
76
+ for d in result["cross_document_discrepancies"]:
77
+ print(f" {d['finding']} ({', '.join(d['documents_involved'])})")
78
+ ```
79
+
80
+ ## Sanctions Screening
81
+
82
+ ```python
83
+ result = client.screen_sanctions(entity="Acme Corp", country="IR")
84
+ print(f"Clear: {result['clear']}, Risk: {result['risk_level']}")
85
+ ```
86
+
87
+ ## All Methods
88
+
89
+ | Method | Description |
90
+ |--------|-------------|
91
+ | `validate()` | Single document validation |
92
+ | `validate_set()` | Multi-document cross-doc validation |
93
+ | `screen_sanctions()` | OFAC/EU/UN/UK sanctions screening |
94
+ | `screen_entity()` | UBO/PEP/shell company risk |
95
+ | `screen_export_controls()` | EAR/ITAR/EU dual-use |
96
+ | `screen_tbml()` | FATF money laundering indicators |
97
+ | `screen_route()` | Route sanctions + transport |
98
+ | `search_rules()` | Keyword search across all rules |
99
+ | `lookup_rules()` | Structured rule retrieval |
100
+ | `get_requirements()` | Document field requirements |
101
+ | `get_bank_profiles()` | Bank behavior data |
102
+ | `get_country_requirements()` | Country compliance rules |
103
+ | `get_fta_origin()` | FTA eligibility rules |
104
+ | `get_commodities()` | Commodity compliance |
105
+
106
+ ## Sign Up
107
+
108
+ Free plan: 50 validations/month. No credit card.
109
+
110
+ ```bash
111
+ curl -X POST https://api.rulhub.com/v1/signup \
112
+ -H "Content-Type: application/json" \
113
+ -d '{"email":"you@co.com","password":"secure123","company_name":"Your Co"}'
114
+ ```
115
+
116
+ **API Docs:** https://api.rulhub.com/swagger | **Website:** https://rulhub.com | **By:** Enso Intelligence Labs, Inc.
rulhub-0.1.0/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # RulHub Python SDK
2
+
3
+ Trade finance compliance API — validate documents against 5,400+ ICC rules with AI-powered semantic matching.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install rulhub
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from rulhub import RulHub
15
+
16
+ client = RulHub(api_key="ak_your_key_here")
17
+
18
+ # Validate a letter of credit
19
+ result = client.validate(
20
+ rules="ucp600",
21
+ document_type="lc",
22
+ document={
23
+ "beneficiary_name": "ABC Trading Co",
24
+ "amount": 50000,
25
+ "currency": "USD",
26
+ "goods_description": "1000 MT Thai White Rice 5% Broken",
27
+ "expiry_date": "2026-06-30",
28
+ }
29
+ )
30
+
31
+ print(f"Score: {result['score']:.0%}")
32
+ print(f"Compliant: {result['compliant']}")
33
+
34
+ for d in result["discrepancies"]:
35
+ print(f" [{d['severity']}] {d['finding']}")
36
+ if d.get("recommendation"):
37
+ print(f" Fix: {d['recommendation']}")
38
+ ```
39
+
40
+ ## Validate Multiple Documents
41
+
42
+ ```python
43
+ result = client.validate_set(documents=[
44
+ {"type": "lc", "fields": {"amount": 50000, "currency": "USD"}},
45
+ {"type": "invoice", "fields": {"total_amount": 49500, "currency": "USD"}},
46
+ ])
47
+
48
+ for d in result["cross_document_discrepancies"]:
49
+ print(f" {d['finding']} ({', '.join(d['documents_involved'])})")
50
+ ```
51
+
52
+ ## Sanctions Screening
53
+
54
+ ```python
55
+ result = client.screen_sanctions(entity="Acme Corp", country="IR")
56
+ print(f"Clear: {result['clear']}, Risk: {result['risk_level']}")
57
+ ```
58
+
59
+ ## All Methods
60
+
61
+ | Method | Description |
62
+ |--------|-------------|
63
+ | `validate()` | Single document validation |
64
+ | `validate_set()` | Multi-document cross-doc validation |
65
+ | `screen_sanctions()` | OFAC/EU/UN/UK sanctions screening |
66
+ | `screen_entity()` | UBO/PEP/shell company risk |
67
+ | `screen_export_controls()` | EAR/ITAR/EU dual-use |
68
+ | `screen_tbml()` | FATF money laundering indicators |
69
+ | `screen_route()` | Route sanctions + transport |
70
+ | `search_rules()` | Keyword search across all rules |
71
+ | `lookup_rules()` | Structured rule retrieval |
72
+ | `get_requirements()` | Document field requirements |
73
+ | `get_bank_profiles()` | Bank behavior data |
74
+ | `get_country_requirements()` | Country compliance rules |
75
+ | `get_fta_origin()` | FTA eligibility rules |
76
+ | `get_commodities()` | Commodity compliance |
77
+
78
+ ## Sign Up
79
+
80
+ Free plan: 50 validations/month. No credit card.
81
+
82
+ ```bash
83
+ curl -X POST https://api.rulhub.com/v1/signup \
84
+ -H "Content-Type: application/json" \
85
+ -d '{"email":"you@co.com","password":"secure123","company_name":"Your Co"}'
86
+ ```
87
+
88
+ **API Docs:** https://api.rulhub.com/swagger | **Website:** https://rulhub.com | **By:** Enso Intelligence Labs, Inc.
@@ -0,0 +1,22 @@
1
+ """
2
+ ICC Rule Engine Python SDK
3
+
4
+ A comprehensive Python SDK for interacting with the ICC Rule Engine API.
5
+ """
6
+
7
+ from .client import ICCRuleEngineClient
8
+ from .models import Rule, ValidationRequest, ValidationResult, RuleCondition, ExpectedOutcome
9
+ from .exceptions import ICCRuleEngineError, ValidationError, APIError
10
+
11
+ __version__ = "1.0.0"
12
+ __all__ = [
13
+ "ICCRuleEngineClient",
14
+ "Rule",
15
+ "ValidationRequest",
16
+ "ValidationResult",
17
+ "RuleCondition",
18
+ "ExpectedOutcome",
19
+ "ICCRuleEngineError",
20
+ "ValidationError",
21
+ "APIError"
22
+ ]
@@ -0,0 +1,365 @@
1
+ """
2
+ ICC Rule Engine Python SDK Client
3
+ """
4
+ import asyncio
5
+ from typing import Any, Dict, List, Optional, Union
6
+ from urllib.parse import urljoin
7
+
8
+ import httpx
9
+ from pydantic import ValidationError as PydanticValidationError
10
+
11
+ from .models import (
12
+ Rule,
13
+ ValidationRequest,
14
+ ValidationResult,
15
+ BatchValidationRequest,
16
+ BatchValidationResult,
17
+ HealthStatus,
18
+ MetricsResponse
19
+ )
20
+ from .exceptions import (
21
+ APIError,
22
+ AuthenticationError,
23
+ NetworkError,
24
+ RateLimitError,
25
+ TimeoutError,
26
+ ValidationError
27
+ )
28
+
29
+
30
+ class ICCRuleEngineClient:
31
+ """
32
+ ICC Rule Engine API Client
33
+
34
+ A comprehensive client for interacting with the ICC Rule Engine API.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ base_url: str,
40
+ api_key: str,
41
+ timeout: float = 30.0,
42
+ max_retries: int = 3,
43
+ retry_delay: float = 1.0
44
+ ):
45
+ """
46
+ Initialize the ICC Rule Engine client
47
+
48
+ Args:
49
+ base_url: Base URL of the ICC Rule Engine API
50
+ api_key: API key for authentication
51
+ timeout: Request timeout in seconds
52
+ max_retries: Maximum number of retries for failed requests
53
+ retry_delay: Delay between retries in seconds
54
+ """
55
+ self.base_url = base_url.rstrip("/")
56
+ self.api_key = api_key
57
+ self.timeout = timeout
58
+ self.max_retries = max_retries
59
+ self.retry_delay = retry_delay
60
+
61
+ self._client = httpx.Client(
62
+ timeout=timeout,
63
+ headers={
64
+ "X-API-Key": api_key,
65
+ "Content-Type": "application/json",
66
+ "User-Agent": "ICC-Rule-Engine-SDK/1.0.0"
67
+ }
68
+ )
69
+
70
+ def __enter__(self):
71
+ return self
72
+
73
+ def __exit__(self, exc_type, exc_val, exc_tb):
74
+ self.close()
75
+
76
+ def close(self):
77
+ """Close the HTTP client"""
78
+ self._client.close()
79
+
80
+ def _make_request(
81
+ self,
82
+ method: str,
83
+ endpoint: str,
84
+ json_data: Optional[Dict[str, Any]] = None,
85
+ params: Optional[Dict[str, Any]] = None
86
+ ) -> Dict[str, Any]:
87
+ """Make HTTP request with error handling and retries"""
88
+
89
+ url = urljoin(self.base_url + "/", endpoint.lstrip("/"))
90
+
91
+ for attempt in range(self.max_retries + 1):
92
+ try:
93
+ response = self._client.request(
94
+ method=method,
95
+ url=url,
96
+ json=json_data,
97
+ params=params
98
+ )
99
+
100
+ if response.status_code == 401:
101
+ raise AuthenticationError("Invalid API key")
102
+ elif response.status_code == 429:
103
+ raise RateLimitError("Rate limit exceeded")
104
+ elif response.status_code >= 400:
105
+ error_data = {}
106
+ try:
107
+ error_data = response.json()
108
+ except Exception:
109
+ pass
110
+
111
+ raise APIError(
112
+ message=error_data.get("detail", f"API error: {response.status_code}"),
113
+ status_code=response.status_code,
114
+ response_data=error_data
115
+ )
116
+
117
+ return response.json()
118
+
119
+ except httpx.TimeoutException:
120
+ if attempt == self.max_retries:
121
+ raise TimeoutError("Request timed out")
122
+ except httpx.NetworkError as e:
123
+ if attempt == self.max_retries:
124
+ raise NetworkError(f"Network error: {e}")
125
+ except (APIError, AuthenticationError, RateLimitError):
126
+ raise
127
+
128
+ if attempt < self.max_retries:
129
+ import time
130
+ time.sleep(self.retry_delay * (2 ** attempt))
131
+
132
+ # Health and Status Methods
133
+
134
+ def health_check(self) -> HealthStatus:
135
+ """Check API health status"""
136
+ data = self._make_request("GET", "/health")
137
+ return HealthStatus(**data)
138
+
139
+ def get_metrics(self) -> MetricsResponse:
140
+ """Get API metrics"""
141
+ data = self._make_request("GET", "/metrics/")
142
+ return MetricsResponse(**data)
143
+
144
+ # Rule Management Methods
145
+
146
+ def create_rule(self, rule: Union[Rule, Dict[str, Any]]) -> Rule:
147
+ """Create a new rule"""
148
+ if isinstance(rule, Rule):
149
+ rule_data = rule.model_dump(exclude_unset=True)
150
+ else:
151
+ rule_data = rule
152
+
153
+ data = self._make_request("POST", "/rules/", json_data=rule_data)
154
+ return Rule(**data)
155
+
156
+ def get_rule(self, rule_id: str) -> Rule:
157
+ """Get rule by ID"""
158
+ data = self._make_request("GET", f"/rules/{rule_id}")
159
+ return Rule(**data)
160
+
161
+ def update_rule(self, rule_id: str, rule: Union[Rule, Dict[str, Any]]) -> Rule:
162
+ """Update an existing rule"""
163
+ if isinstance(rule, Rule):
164
+ rule_data = rule.model_dump(exclude_unset=True)
165
+ else:
166
+ rule_data = rule
167
+
168
+ data = self._make_request("PUT", f"/rules/{rule_id}", json_data=rule_data)
169
+ return Rule(**data)
170
+
171
+ def delete_rule(self, rule_id: str) -> Dict[str, str]:
172
+ """Delete a rule"""
173
+ return self._make_request("DELETE", f"/rules/{rule_id}")
174
+
175
+ def list_rules(
176
+ self,
177
+ source: Optional[str] = None,
178
+ article: Optional[str] = None,
179
+ tags: Optional[List[str]] = None,
180
+ severity: Optional[str] = None,
181
+ active: Optional[bool] = None,
182
+ limit: Optional[int] = None,
183
+ skip: Optional[int] = None
184
+ ) -> List[Rule]:
185
+ """List rules with optional filters"""
186
+ params = {}
187
+ if source:
188
+ params["source"] = source
189
+ if article:
190
+ params["article"] = article
191
+ if tags:
192
+ params["tags"] = ",".join(tags)
193
+ if severity:
194
+ params["severity"] = severity
195
+ if active is not None:
196
+ params["active"] = active
197
+ if limit:
198
+ params["limit"] = limit
199
+ if skip:
200
+ params["skip"] = skip
201
+
202
+ data = self._make_request("GET", "/rules/", params=params)
203
+ return [Rule(**rule) for rule in data]
204
+
205
+ # Validation Methods
206
+
207
+ def validate_document(
208
+ self,
209
+ document: Dict[str, Any],
210
+ ruleset: Dict[str, Any]
211
+ ) -> ValidationResult:
212
+ """Validate a single document"""
213
+ request_data = ValidationRequest(document=document, ruleset=ruleset)
214
+ data = self._make_request("POST", "/validate/", json_data=request_data.model_dump())
215
+ return ValidationResult(**data)
216
+
217
+ def validate_batch(
218
+ self,
219
+ documents: List[Dict[str, Any]],
220
+ ruleset: Dict[str, Any],
221
+ parallel: bool = True
222
+ ) -> BatchValidationResult:
223
+ """Validate multiple documents"""
224
+ request_data = BatchValidationRequest(
225
+ documents=documents,
226
+ ruleset=ruleset,
227
+ parallel=parallel
228
+ )
229
+ data = self._make_request("POST", "/validate/batch", json_data=request_data.model_dump())
230
+ return BatchValidationResult(**data)
231
+
232
+ # Convenience Methods
233
+
234
+ def validate_letter_of_credit(
235
+ self,
236
+ document: Dict[str, Any],
237
+ source: str = "UCP600",
238
+ article: Optional[str] = None
239
+ ) -> ValidationResult:
240
+ """Convenience method for validating letters of credit"""
241
+ ruleset = {"source": source}
242
+ if article:
243
+ ruleset["article"] = article
244
+
245
+ return self.validate_document(document, ruleset)
246
+
247
+ def get_rules_by_source(self, source: str) -> List[Rule]:
248
+ """Get all rules for a specific source"""
249
+ return self.list_rules(source=source)
250
+
251
+ def get_active_rules(self) -> List[Rule]:
252
+ """Get all active rules"""
253
+ return self.list_rules(active=True)
254
+
255
+
256
+ class AsyncICCRuleEngineClient:
257
+ """
258
+ Async ICC Rule Engine API Client
259
+
260
+ An async version of the ICC Rule Engine client for high-performance applications.
261
+ """
262
+
263
+ def __init__(
264
+ self,
265
+ base_url: str,
266
+ api_key: str,
267
+ timeout: float = 30.0,
268
+ max_retries: int = 3,
269
+ retry_delay: float = 1.0
270
+ ):
271
+ """
272
+ Initialize the async ICC Rule Engine client
273
+
274
+ Args:
275
+ base_url: Base URL of the ICC Rule Engine API
276
+ api_key: API key for authentication
277
+ timeout: Request timeout in seconds
278
+ max_retries: Maximum number of retries for failed requests
279
+ retry_delay: Delay between retries in seconds
280
+ """
281
+ self.base_url = base_url.rstrip("/")
282
+ self.api_key = api_key
283
+ self.timeout = timeout
284
+ self.max_retries = max_retries
285
+ self.retry_delay = retry_delay
286
+
287
+ self._client = httpx.AsyncClient(
288
+ timeout=timeout,
289
+ headers={
290
+ "X-API-Key": api_key,
291
+ "Content-Type": "application/json",
292
+ "User-Agent": "ICC-Rule-Engine-SDK/1.0.0"
293
+ }
294
+ )
295
+
296
+ async def __aenter__(self):
297
+ return self
298
+
299
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
300
+ await self.aclose()
301
+
302
+ async def aclose(self):
303
+ """Close the HTTP client"""
304
+ await self._client.aclose()
305
+
306
+ async def _make_request(
307
+ self,
308
+ method: str,
309
+ endpoint: str,
310
+ json_data: Optional[Dict[str, Any]] = None,
311
+ params: Optional[Dict[str, Any]] = None
312
+ ) -> Dict[str, Any]:
313
+ """Make async HTTP request with error handling and retries"""
314
+
315
+ url = urljoin(self.base_url + "/", endpoint.lstrip("/"))
316
+
317
+ for attempt in range(self.max_retries + 1):
318
+ try:
319
+ response = await self._client.request(
320
+ method=method,
321
+ url=url,
322
+ json=json_data,
323
+ params=params
324
+ )
325
+
326
+ if response.status_code == 401:
327
+ raise AuthenticationError("Invalid API key")
328
+ elif response.status_code == 429:
329
+ raise RateLimitError("Rate limit exceeded")
330
+ elif response.status_code >= 400:
331
+ error_data = {}
332
+ try:
333
+ error_data = response.json()
334
+ except Exception:
335
+ pass
336
+
337
+ raise APIError(
338
+ message=error_data.get("detail", f"API error: {response.status_code}"),
339
+ status_code=response.status_code,
340
+ response_data=error_data
341
+ )
342
+
343
+ return response.json()
344
+
345
+ except httpx.TimeoutException:
346
+ if attempt == self.max_retries:
347
+ raise TimeoutError("Request timed out")
348
+ except httpx.NetworkError as e:
349
+ if attempt == self.max_retries:
350
+ raise NetworkError(f"Network error: {e}")
351
+ except (APIError, AuthenticationError, RateLimitError):
352
+ raise
353
+
354
+ if attempt < self.max_retries:
355
+ await asyncio.sleep(self.retry_delay * (2 ** attempt))
356
+
357
+ async def validate_document(
358
+ self,
359
+ document: Dict[str, Any],
360
+ ruleset: Dict[str, Any]
361
+ ) -> ValidationResult:
362
+ """Validate a single document (async)"""
363
+ request_data = ValidationRequest(document=document, ruleset=ruleset)
364
+ data = await self._make_request("POST", "/validate/", json_data=request_data.model_dump())
365
+ return ValidationResult(**data)