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.
@@ -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,3 @@
1
+ """
2
+ Testing utilities and fixtures
3
+ """
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ ai-lls=ai_lls_lib.cli.__main__:main
3
+