ai-lls-lib 1.0.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.
- ai_lls_lib/__init__.py +27 -0
- ai_lls_lib/cli/__init__.py +3 -0
- ai_lls_lib/cli/__main__.py +29 -0
- ai_lls_lib/cli/aws_client.py +115 -0
- ai_lls_lib/cli/commands/__init__.py +3 -0
- ai_lls_lib/cli/commands/admin.py +174 -0
- ai_lls_lib/cli/commands/cache.py +142 -0
- ai_lls_lib/cli/commands/test_stack.py +216 -0
- ai_lls_lib/cli/commands/verify.py +111 -0
- ai_lls_lib/core/__init__.py +3 -0
- ai_lls_lib/core/cache.py +106 -0
- ai_lls_lib/core/models.py +77 -0
- ai_lls_lib/core/processor.py +135 -0
- ai_lls_lib/core/verifier.py +95 -0
- ai_lls_lib/testing/__init__.py +3 -0
- ai_lls_lib/testing/fixtures.py +104 -0
- ai_lls_lib-1.0.0.dist-info/METADATA +220 -0
- ai_lls_lib-1.0.0.dist-info/RECORD +20 -0
- ai_lls_lib-1.0.0.dist-info/WHEEL +4 -0
- ai_lls_lib-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
"""
|
2
|
+
Phone verification logic - checks line type and DNC status
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import re
|
6
|
+
from datetime import datetime, timezone
|
7
|
+
from typing import Optional
|
8
|
+
import httpx
|
9
|
+
import phonenumbers
|
10
|
+
from aws_lambda_powertools import Logger
|
11
|
+
from .models import PhoneVerification, LineType, VerificationSource
|
12
|
+
from .cache import DynamoDBCache
|
13
|
+
|
14
|
+
logger = Logger()
|
15
|
+
|
16
|
+
|
17
|
+
class PhoneVerifier:
|
18
|
+
"""Verifies phone numbers for line type and DNC status"""
|
19
|
+
|
20
|
+
def __init__(self, cache: DynamoDBCache):
|
21
|
+
self.cache = cache
|
22
|
+
self.dnc_api_key = os.environ.get("DNC_API_KEY", "")
|
23
|
+
self.phone_api_key = os.environ.get("PHONE_VERIFY_API_KEY", "")
|
24
|
+
self.http_client = httpx.Client(timeout=10.0)
|
25
|
+
|
26
|
+
def normalize_phone(self, phone: str) -> str:
|
27
|
+
"""Normalize phone to E.164 format"""
|
28
|
+
try:
|
29
|
+
# Parse with US as default country
|
30
|
+
parsed = phonenumbers.parse(phone, "US")
|
31
|
+
if not phonenumbers.is_valid_number(parsed):
|
32
|
+
raise ValueError(f"Invalid phone number: {phone}")
|
33
|
+
|
34
|
+
# Format as E.164
|
35
|
+
return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
|
36
|
+
except Exception as e:
|
37
|
+
logger.error(f"Phone normalization failed: {str(e)}")
|
38
|
+
raise ValueError(f"Invalid phone format: {phone}")
|
39
|
+
|
40
|
+
def verify_sync(self, phone: str) -> PhoneVerification:
|
41
|
+
"""Synchronous verification for Lambda handlers"""
|
42
|
+
normalized = self.normalize_phone(phone)
|
43
|
+
|
44
|
+
# Check cache first
|
45
|
+
cached = self.cache.get(normalized)
|
46
|
+
if cached:
|
47
|
+
return cached
|
48
|
+
|
49
|
+
# Call external APIs
|
50
|
+
line_type = self._check_line_type_sync(normalized)
|
51
|
+
dnc_status = self._check_dnc_sync(normalized)
|
52
|
+
|
53
|
+
result = PhoneVerification(
|
54
|
+
phone_number=normalized,
|
55
|
+
line_type=line_type,
|
56
|
+
dnc=dnc_status,
|
57
|
+
cached=False,
|
58
|
+
verified_at=datetime.now(timezone.utc),
|
59
|
+
source=VerificationSource.API
|
60
|
+
)
|
61
|
+
|
62
|
+
# Store in cache
|
63
|
+
self.cache.set(normalized, result)
|
64
|
+
|
65
|
+
return result
|
66
|
+
|
67
|
+
def _check_line_type_sync(self, phone: str) -> LineType:
|
68
|
+
"""Check if phone is mobile/landline/voip"""
|
69
|
+
# TODO: Implement actual API call to phone verification service
|
70
|
+
# Would use self.phone_api_key to authenticate
|
71
|
+
logger.info(f"Checking line type for {phone[:6]}***")
|
72
|
+
|
73
|
+
# Stub implementation based on last digit
|
74
|
+
last_digit = phone[-1] if phone else '5'
|
75
|
+
if last_digit in ['2', '0']:
|
76
|
+
return LineType.LANDLINE
|
77
|
+
else:
|
78
|
+
return LineType.MOBILE
|
79
|
+
|
80
|
+
def _check_dnc_sync(self, phone: str) -> bool:
|
81
|
+
"""Check if phone is on DNC list"""
|
82
|
+
# TODO: Implement actual DNC API call
|
83
|
+
# Would use self.dnc_api_key or os.environ.get("DNC_CHECK_API_KEY")
|
84
|
+
logger.info(f"Checking DNC status for {phone[:6]}***")
|
85
|
+
|
86
|
+
# Stub implementation based on last digit:
|
87
|
+
# - Ends in 1 or 0: on DNC list
|
88
|
+
# - Otherwise: not on DNC
|
89
|
+
last_digit = phone[-1] if phone else '5'
|
90
|
+
return last_digit in ['1', '0']
|
91
|
+
|
92
|
+
def __del__(self):
|
93
|
+
"""Cleanup HTTP client"""
|
94
|
+
if hasattr(self, 'http_client'):
|
95
|
+
self.http_client.close()
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
Test fixtures and utilities for ai-lls-lib
|
3
|
+
"""
|
4
|
+
from datetime import datetime, timedelta, timezone
|
5
|
+
from typing import List, Dict, Any
|
6
|
+
from ai_lls_lib.core.models import PhoneVerification, LineType, VerificationSource
|
7
|
+
|
8
|
+
# Sample phone numbers for testing
|
9
|
+
# Using 201-555-01XX format which is designated for testing
|
10
|
+
TEST_PHONES = {
|
11
|
+
"valid_mobile": "+12015550153", # Ends in 3 - mobile, not on DNC
|
12
|
+
"valid_landline": "+12015550152", # Ends in 2 - landline, not on DNC
|
13
|
+
"dnc_mobile": "+12015550151", # Ends in 1 - mobile, on DNC
|
14
|
+
"dnc_landline": "+12015550150", # Ends in 0 - landline, on DNC
|
15
|
+
"invalid": "not-a-phone",
|
16
|
+
"missing_country": "2015550123",
|
17
|
+
"international": "+442071234567",
|
18
|
+
}
|
19
|
+
|
20
|
+
def create_test_verification(
|
21
|
+
phone: str = TEST_PHONES["valid_mobile"],
|
22
|
+
line_type: LineType = LineType.MOBILE,
|
23
|
+
dnc: bool = False,
|
24
|
+
cached: bool = False,
|
25
|
+
source: VerificationSource = VerificationSource.API
|
26
|
+
) -> PhoneVerification:
|
27
|
+
"""Create a test PhoneVerification object"""
|
28
|
+
return PhoneVerification(
|
29
|
+
phone_number=phone,
|
30
|
+
line_type=line_type,
|
31
|
+
dnc=dnc,
|
32
|
+
cached=cached,
|
33
|
+
verified_at=datetime.now(timezone.utc),
|
34
|
+
source=source
|
35
|
+
)
|
36
|
+
|
37
|
+
def create_test_csv_content(phones: List[str] = None) -> str:
|
38
|
+
"""Create CSV content for testing bulk processing"""
|
39
|
+
if phones is None:
|
40
|
+
phones = [
|
41
|
+
TEST_PHONES["valid_mobile"],
|
42
|
+
TEST_PHONES["valid_landline"],
|
43
|
+
TEST_PHONES["dnc_mobile"],
|
44
|
+
]
|
45
|
+
|
46
|
+
lines = ["name,phone,email"]
|
47
|
+
for i, phone in enumerate(phones):
|
48
|
+
lines.append(f"Test User {i},{phone},test{i}@example.com")
|
49
|
+
|
50
|
+
return "\n".join(lines)
|
51
|
+
|
52
|
+
def create_dynamodb_item(phone: str, line_type: LineType = LineType.MOBILE, dnc: bool = False) -> Dict[str, Any]:
|
53
|
+
"""Create a DynamoDB item for testing"""
|
54
|
+
ttl = int((datetime.now(timezone.utc) + timedelta(days=30)).timestamp())
|
55
|
+
|
56
|
+
return {
|
57
|
+
"phone_number": phone,
|
58
|
+
"line_type": line_type.value, # Store as string in DynamoDB
|
59
|
+
"dnc": dnc,
|
60
|
+
"cached": True,
|
61
|
+
"verified_at": datetime.now(timezone.utc).isoformat(),
|
62
|
+
"source": VerificationSource.CACHE.value, # Store as string in DynamoDB
|
63
|
+
"ttl": ttl
|
64
|
+
}
|
65
|
+
|
66
|
+
def create_sqs_message(file_id: str, bucket: str, key: str, user_id: str) -> Dict[str, Any]:
|
67
|
+
"""Create an SQS message for bulk processing"""
|
68
|
+
return {
|
69
|
+
"file_id": file_id,
|
70
|
+
"bucket": bucket,
|
71
|
+
"key": key,
|
72
|
+
"user_id": user_id,
|
73
|
+
"created_at": datetime.now(timezone.utc).isoformat()
|
74
|
+
}
|
75
|
+
|
76
|
+
def create_api_gateway_event(
|
77
|
+
phone: str = None,
|
78
|
+
user_id: str = "test-user",
|
79
|
+
method: str = "GET",
|
80
|
+
path: str = "/verify"
|
81
|
+
) -> Dict[str, Any]:
|
82
|
+
"""Create an API Gateway event for Lambda testing"""
|
83
|
+
event = {
|
84
|
+
"httpMethod": method,
|
85
|
+
"path": path,
|
86
|
+
"headers": {
|
87
|
+
"Authorization": "Bearer test-token"
|
88
|
+
},
|
89
|
+
"requestContext": {
|
90
|
+
"authorizer": {
|
91
|
+
"lambda": {
|
92
|
+
"principal_id": user_id,
|
93
|
+
"claims": {
|
94
|
+
"email": f"{user_id}@example.com"
|
95
|
+
}
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
if phone:
|
102
|
+
event["queryStringParameters"] = {"p": phone}
|
103
|
+
|
104
|
+
return event
|
@@ -0,0 +1,220 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: ai-lls-lib
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Landline Scrubber core library - phone verification and DNC checking
|
5
|
+
Author: LandlineScrubber Team
|
6
|
+
Requires-Python: >=3.12,<4.0
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
10
|
+
Requires-Dist: aws-lambda-powertools (>=2.30.0,<3.0.0)
|
11
|
+
Requires-Dist: boto3 (>=1.34.0,<2.0.0)
|
12
|
+
Requires-Dist: click (>=8.1.0,<9.0.0)
|
13
|
+
Requires-Dist: httpx (>=0.25.0,<0.26.0)
|
14
|
+
Requires-Dist: phonenumbers (>=8.13.0,<9.0.0)
|
15
|
+
Requires-Dist: pydantic (>=2.5.0,<3.0.0)
|
16
|
+
Requires-Dist: rich (>=14.0,<15.0)
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
|
19
|
+
# AI LLS Library
|
20
|
+
|
21
|
+
Core business logic library and CLI tools for Landline Scrubber - phone verification and DNC checking.
|
22
|
+
|
23
|
+
## Features
|
24
|
+
|
25
|
+
- Phone number normalization (E.164 format)
|
26
|
+
- Line type detection (mobile/landline/voip)
|
27
|
+
- DNC (Do Not Call) list checking
|
28
|
+
- DynamoDB caching with 30-day TTL
|
29
|
+
- Bulk CSV processing
|
30
|
+
- Infrastructure-aware CLI for admin operations
|
31
|
+
- AWS Lambda PowerTools integration
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
|
35
|
+
```bash
|
36
|
+
# Install library with Poetry
|
37
|
+
poetry install
|
38
|
+
|
39
|
+
# Install CLI globally
|
40
|
+
pip install -e .
|
41
|
+
```
|
42
|
+
|
43
|
+
## Library Usage
|
44
|
+
|
45
|
+
### Single Phone Verification
|
46
|
+
|
47
|
+
```python
|
48
|
+
from ai_lls_lib import PhoneVerifier, DynamoDBCache
|
49
|
+
|
50
|
+
cache = DynamoDBCache(table_name="phone-cache")
|
51
|
+
verifier = PhoneVerifier(cache)
|
52
|
+
|
53
|
+
result = verifier.verify_sync("+15551234567")
|
54
|
+
print(f"Line type: {result.line_type}")
|
55
|
+
print(f"DNC: {result.dnc}")
|
56
|
+
print(f"From cache: {result.cached}")
|
57
|
+
```
|
58
|
+
|
59
|
+
### Bulk Processing
|
60
|
+
|
61
|
+
```python
|
62
|
+
from ai_lls_lib import BulkProcessor, PhoneVerifier, DynamoDBCache
|
63
|
+
|
64
|
+
cache = DynamoDBCache(table_name="phone-cache")
|
65
|
+
verifier = PhoneVerifier(cache)
|
66
|
+
processor = BulkProcessor(verifier)
|
67
|
+
|
68
|
+
results = processor.process_csv_sync("/path/to/phones.csv")
|
69
|
+
processor.generate_results_csv(
|
70
|
+
original_path="/path/to/phones.csv",
|
71
|
+
results=results,
|
72
|
+
output_path="/path/to/results.csv"
|
73
|
+
)
|
74
|
+
```
|
75
|
+
|
76
|
+
## CLI Usage
|
77
|
+
|
78
|
+
The `ai-lls` CLI provides infrastructure-aware administrative tools:
|
79
|
+
|
80
|
+
### Verification Commands
|
81
|
+
```bash
|
82
|
+
# Verify single phone
|
83
|
+
ai-lls verify phone +15551234567 --stack landline-api
|
84
|
+
|
85
|
+
# Bulk verify CSV
|
86
|
+
ai-lls verify bulk input.csv -o output.csv --stack landline-api
|
87
|
+
```
|
88
|
+
|
89
|
+
### Cache Management
|
90
|
+
```bash
|
91
|
+
# Show cache statistics
|
92
|
+
ai-lls cache stats --stack landline-api
|
93
|
+
|
94
|
+
# Get cached entry
|
95
|
+
ai-lls cache get +15551234567 --stack landline-api
|
96
|
+
|
97
|
+
# Invalidate cache entry
|
98
|
+
ai-lls cache invalidate +15551234567 --stack landline-api
|
99
|
+
|
100
|
+
# Clear old entries
|
101
|
+
ai-lls cache clear --older-than 20 --stack landline-api
|
102
|
+
```
|
103
|
+
|
104
|
+
### Administrative Commands
|
105
|
+
```bash
|
106
|
+
# Manage user credits
|
107
|
+
ai-lls admin user-credits user123 --add 100
|
108
|
+
ai-lls admin user-credits user123 --set 500
|
109
|
+
|
110
|
+
# List API keys
|
111
|
+
ai-lls admin api-keys --user user123
|
112
|
+
|
113
|
+
# Check queue status
|
114
|
+
ai-lls admin queue-stats
|
115
|
+
|
116
|
+
# View secrets (masked)
|
117
|
+
ai-lls admin secrets --stack landline-api
|
118
|
+
```
|
119
|
+
|
120
|
+
### Test Stack Management
|
121
|
+
```bash
|
122
|
+
# Deploy test stack
|
123
|
+
ai-lls test-stack deploy
|
124
|
+
|
125
|
+
# Check status
|
126
|
+
ai-lls test-stack status
|
127
|
+
|
128
|
+
# Run integration tests
|
129
|
+
ai-lls test-stack test
|
130
|
+
|
131
|
+
# Delete test stack
|
132
|
+
ai-lls test-stack delete
|
133
|
+
```
|
134
|
+
|
135
|
+
## Project Structure
|
136
|
+
|
137
|
+
```
|
138
|
+
ai-lls-lib/
|
139
|
+
├── src/ai_lls_lib/
|
140
|
+
│ ├── core/ # Business logic (infrastructure-agnostic)
|
141
|
+
│ │ ├── models.py # Pydantic models
|
142
|
+
│ │ ├── verifier.py # Phone verification
|
143
|
+
│ │ ├── processor.py # Bulk processing
|
144
|
+
│ │ └── cache.py # DynamoDB cache
|
145
|
+
│ ├── cli/ # Infrastructure-aware CLI
|
146
|
+
│ │ ├── __main__.py # Entry point
|
147
|
+
│ │ ├── commands/ # Command modules
|
148
|
+
│ │ └── aws_client.py # AWS operations
|
149
|
+
│ └── testing/ # Test utilities
|
150
|
+
│ └── fixtures.py # Test data
|
151
|
+
├── tests/
|
152
|
+
│ ├── unit/ # Mocked tests
|
153
|
+
│ └── integration/ # AWS integration tests
|
154
|
+
└── test-stack.yaml # Test infrastructure
|
155
|
+
```
|
156
|
+
|
157
|
+
## Testing
|
158
|
+
|
159
|
+
```bash
|
160
|
+
# Run unit tests (mocked AWS)
|
161
|
+
poetry run pytest tests/unit -v
|
162
|
+
|
163
|
+
# Deploy test stack for integration tests
|
164
|
+
ai-lls test-stack deploy
|
165
|
+
|
166
|
+
# Run integration tests (requires test stack)
|
167
|
+
TEST_STACK_NAME=ai-lls-lib-test poetry run pytest tests/integration -v
|
168
|
+
|
169
|
+
# All tests with coverage
|
170
|
+
poetry run pytest --cov=src --cov-report=html
|
171
|
+
|
172
|
+
# Clean up
|
173
|
+
ai-lls test-stack delete
|
174
|
+
```
|
175
|
+
|
176
|
+
## Development
|
177
|
+
|
178
|
+
### Current Stub Implementation
|
179
|
+
|
180
|
+
For demo purposes, verification uses stub logic based on last digit:
|
181
|
+
- Ends in 3: mobile, not on DNC
|
182
|
+
- Ends in 2: landline, not on DNC
|
183
|
+
- Ends in 1: mobile, on DNC
|
184
|
+
- Ends in 0: landline, on DNC
|
185
|
+
- Otherwise: mobile, not on DNC
|
186
|
+
|
187
|
+
TODO markers indicate where real API integration will be added.
|
188
|
+
|
189
|
+
### Code Quality
|
190
|
+
|
191
|
+
```bash
|
192
|
+
# Format code
|
193
|
+
poetry run black src/ tests/
|
194
|
+
poetry run isort src/ tests/
|
195
|
+
|
196
|
+
# Type checking
|
197
|
+
poetry run mypy src/
|
198
|
+
|
199
|
+
# Run pre-commit hooks
|
200
|
+
pre-commit run --all-files
|
201
|
+
```
|
202
|
+
|
203
|
+
## Environment Variables
|
204
|
+
|
205
|
+
- `DNC_API_KEY` - DNC verification API key
|
206
|
+
- `DNC_CHECK_API_KEY` - Alternative DNC service
|
207
|
+
- `PHONE_VERIFY_API_KEY` - Line type verification
|
208
|
+
- `AWS_REGION` - AWS region (default: us-east-1)
|
209
|
+
- `AWS_PROFILE` - AWS profile for CLI operations
|
210
|
+
|
211
|
+
## License
|
212
|
+
|
213
|
+
Proprietary - All rights reserved
|
214
|
+
|
215
|
+
## Release Process
|
216
|
+
|
217
|
+
This library uses semantic versioning and publishes to:
|
218
|
+
- TestPyPI on dev branch pushes (pre-release versions)
|
219
|
+
- PyPI on main branch pushes (stable releases)
|
220
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
ai_lls_lib/__init__.py,sha256=XfIG376ImlT_OH0R75BNpoqcxsx0fIp6cSFpQw_XhL8,584
|
2
|
+
ai_lls_lib/cli/__init__.py,sha256=m9qjZTW1jpENwXAUeuRrlP0b66BWRcqSO28MSjvOyCs,74
|
3
|
+
ai_lls_lib/cli/__main__.py,sha256=DH9x08k6GWBSqVWspHgWTxGM7NkTBZ2OS2KrVW-XQaA,671
|
4
|
+
ai_lls_lib/cli/aws_client.py,sha256=YcCWCpTNOW9JPLxSNLRy5-F5HPKguJJk7dPNrPqhJv0,3952
|
5
|
+
ai_lls_lib/cli/commands/__init__.py,sha256=_kROrYuR_p2i110c0OvNeArfEFQbn15zR1c3pdeZOoo,28
|
6
|
+
ai_lls_lib/cli/commands/admin.py,sha256=bNBJi2fZBP0J40JQP6HP7NYadNmI214iII1TeLhooyE,6687
|
7
|
+
ai_lls_lib/cli/commands/cache.py,sha256=vWt0vy4L9CEgUEWUzfdehU6u43PE8vUvHx7xxg4e5tw,5680
|
8
|
+
ai_lls_lib/cli/commands/test_stack.py,sha256=rNq4mhRXX7Ixo67kSoEPWlxqgXCzM9e2PR96qTAG7pE,7378
|
9
|
+
ai_lls_lib/cli/commands/verify.py,sha256=tKHkSghrtnqkjAQGvTs-5uDUgstQO0HMW04EddRPgJU,4253
|
10
|
+
ai_lls_lib/core/__init__.py,sha256=QUaeQHIyvknkgMxIbfXRo1a5jSQpiJkB84dId5ubuEk,36
|
11
|
+
ai_lls_lib/core/cache.py,sha256=MubgyAF3y7BBF9am39Ni98NgikZ9UBZUG-KtbE3XWX4,3711
|
12
|
+
ai_lls_lib/core/models.py,sha256=ABRYaeMCWahQh4WdbkCxt3AO0-EvPWZwlL-JITQSnEM,2381
|
13
|
+
ai_lls_lib/core/processor.py,sha256=s49fVlZt6lnfWNgboiHsC6ruman4oIXzgVqzuwExKBQ,4916
|
14
|
+
ai_lls_lib/core/verifier.py,sha256=fglRDoFgrQyX4UuYknZLYX4t9HlpTS2HMVsfszSNXQ4,3257
|
15
|
+
ai_lls_lib/testing/__init__.py,sha256=RUxRYBzzPCPS15Umb6bUrE6rL5BQXBQf4SJM2E3ffrg,39
|
16
|
+
ai_lls_lib/testing/fixtures.py,sha256=_n6bbr95LnQf9Dvu1qKs2HsvHEA7AAbe59B75qxE10w,3310
|
17
|
+
ai_lls_lib-1.0.0.dist-info/METADATA,sha256=q4xSigc2_m2Jmv45SfXFfH9dBbFOVDcfIBa0Uzaw48w,5344
|
18
|
+
ai_lls_lib-1.0.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
19
|
+
ai_lls_lib-1.0.0.dist-info/entry_points.txt,sha256=Pi0V_HBViEKGFbNQKatl5lhhnHHBXlxaom-5gH9gXZ0,55
|
20
|
+
ai_lls_lib-1.0.0.dist-info/RECORD,,
|