lanka-kit 1.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,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: lanka-kit
3
+ Version: 1.1.0
4
+ Summary: The Ultimate Sri Lankan Developer Toolkit: NIC, Phone, Banks, Currency, License, Vehicle, Passport, TIN
5
+ Author: Lanka Developer Community
6
+ License: MIT
7
+ Keywords: sri-lanka,nic,identity,verification,lanka,phone-validator,dialog,mobitel,cbsl,banks,lkr,lanka-kit
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: lanka-kit
3
+ Version: 1.1.0
4
+ Summary: The Ultimate Sri Lankan Developer Toolkit: NIC, Phone, Banks, Currency, License, Vehicle, Passport, TIN
5
+ Author: Lanka Developer Community
6
+ License: MIT
7
+ Keywords: sri-lanka,nic,identity,verification,lanka,phone-validator,dialog,mobitel,cbsl,banks,lkr,lanka-kit
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
@@ -0,0 +1,6 @@
1
+ lanka_kit.py
2
+ pyproject.toml
3
+ lanka_kit.egg-info/PKG-INFO
4
+ lanka_kit.egg-info/SOURCES.txt
5
+ lanka_kit.egg-info/dependency_links.txt
6
+ lanka_kit.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ lanka_kit
@@ -0,0 +1,238 @@
1
+ """
2
+ LankaKit
3
+ Sri Lankan utility toolkit for Python.
4
+ """
5
+
6
+ from datetime import date, datetime
7
+ import re
8
+ from typing import Optional, Dict, Any, List
9
+
10
+ DRP_MONTH_DAYS = [
11
+ {"month": 1, "days": 31},
12
+ {"month": 2, "days": 29},
13
+ {"month": 3, "days": 31},
14
+ {"month": 4, "days": 30},
15
+ {"month": 5, "days": 31},
16
+ {"month": 6, "days": 30},
17
+ {"month": 7, "days": 31},
18
+ {"month": 8, "days": 31},
19
+ {"month": 9, "days": 30},
20
+ {"month": 10, "days": 31},
21
+ {"month": 11, "days": 30},
22
+ {"month": 12, "days": 31},
23
+ ]
24
+
25
+ MOBILE_CARRIERS = {
26
+ "070": "Mobitel",
27
+ "071": "Mobitel",
28
+ "072": "Hutch",
29
+ "074": "Dialog",
30
+ "075": "Airtel",
31
+ "076": "Dialog",
32
+ "077": "Dialog",
33
+ "078": "Hutch",
34
+ }
35
+
36
+ BANKS = [
37
+ {"code": "7010", "name": "Commercial Bank of Ceylon PLC", "short_name": "Commercial Bank", "popular": True},
38
+ {"code": "7083", "name": "Bank of Ceylon", "short_name": "BOC", "popular": True},
39
+ {"code": "7135", "name": "People's Bank", "short_name": "People's Bank", "popular": True},
40
+ {"code": "7092", "name": "Hatton National Bank PLC", "short_name": "HNB", "popular": True},
41
+ {"code": "7278", "name": "Sampath Bank PLC", "short_name": "Sampath Bank", "popular": True},
42
+ {"code": "7169", "name": "National Development Bank PLC", "short_name": "NDB Bank", "popular": True},
43
+ {"code": "7214", "name": "Nations Trust Bank PLC", "short_name": "NTB", "popular": True},
44
+ {"code": "7205", "name": "Seylan Bank PLC", "short_name": "Seylan Bank", "popular": True},
45
+ {"code": "7311", "name": "DFCC Bank PLC", "short_name": "DFCC Bank", "popular": True},
46
+ {"code": "7719", "name": "National Savings Bank", "short_name": "NSB", "popular": True},
47
+ {"code": "7463", "name": "Cargills Bank Limited", "short_name": "Cargills Bank", "popular": True},
48
+ {"code": "7074", "name": "HSBC", "short_name": "HSBC", "popular": True},
49
+ {"code": "7056", "name": "Standard Chartered Bank", "short_name": "Standard Chartered", "popular": False},
50
+ ]
51
+
52
+ PROVINCES = {
53
+ "WP": "Western Province",
54
+ "CP": "Central Province",
55
+ "SP": "Southern Province",
56
+ "NP": "Northern Province",
57
+ "EP": "Eastern Province",
58
+ "NW": "North Western Province",
59
+ "NC": "North Central Province",
60
+ "UP": "Uva Province",
61
+ "UVA": "Uva Province",
62
+ "SG": "Sabaragamuwa Province",
63
+ }
64
+
65
+ def is_leap_year(year: int) -> bool:
66
+ return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
67
+
68
+ def sanitize_nic(nic: str) -> str:
69
+ if not isinstance(nic, str):
70
+ return ""
71
+ return re.sub(r"[\s-]", "", nic.strip().upper())
72
+
73
+ def validate_nic(input_nic: str) -> Dict[str, Any]:
74
+ clean = sanitize_nic(input_nic)
75
+ if not clean:
76
+ return {"isValid": False, "error": "NIC cannot be empty", "code": "EMPTY_INPUT"}
77
+
78
+ length = len(clean)
79
+ if length not in (10, 12):
80
+ return {"isValid": False, "error": f"Invalid length ({length}). Must be 10 or 12 characters", "code": "INVALID_LENGTH"}
81
+
82
+ format_type = "OLD" if length == 10 else "NEW"
83
+ year = 0
84
+ days_data = 0
85
+ serial = ""
86
+ check_digit = 0
87
+ is_voter = None
88
+
89
+ if format_type == "OLD":
90
+ if not re.match(r"^\d{9}[VX]$", clean):
91
+ return {"isValid": False, "error": "Old NIC must have 9 digits followed by 'V' or 'X'", "code": "INVALID_OLD_FORMAT"}
92
+ year = 1900 + int(clean[0:2])
93
+ days_data = int(clean[2:5])
94
+ serial = clean[5:8]
95
+ check_digit = int(clean[8])
96
+ is_voter = clean[9] == "V"
97
+ else:
98
+ if not re.match(r"^\d{12}$", clean):
99
+ return {"isValid": False, "error": "New NIC must contain exactly 12 numeric digits", "code": "INVALID_NEW_FORMAT"}
100
+ year = int(clean[0:4])
101
+ days_data = int(clean[4:7])
102
+ serial = clean[7:11]
103
+ check_digit = int(clean[11])
104
+
105
+ gender = "Male"
106
+ day_of_year = days_data
107
+ if day_of_year > 500:
108
+ gender = "Female"
109
+ day_of_year -= 500
110
+
111
+ if day_of_year < 1 or day_of_year > 366:
112
+ return {"isValid": False, "error": f"Day of year ({day_of_year}) out of range (1-366)", "code": "INVALID_DAY_OF_YEAR"}
113
+
114
+ month = 0
115
+ day = 0
116
+ accum = 0
117
+ for m in DRP_MONTH_DAYS:
118
+ if day_of_year <= accum + m["days"]:
119
+ month = m["month"]
120
+ day = day_of_year - accum
121
+ break
122
+ accum += m["days"]
123
+
124
+ if month == 2 and day == 29 and not is_leap_year(year):
125
+ month = 2
126
+ day = 28
127
+
128
+ dob_formatted = f"{year:04d}-{month:02d}-{day:02d}"
129
+ today = date.today()
130
+ age = today.year - year - ((today.month, today.day) < (month, day))
131
+
132
+ equiv = ""
133
+ if format_type == "OLD":
134
+ equiv = f"{year:04d}{days_data:03d}0{serial}{check_digit}"
135
+ else:
136
+ if 1900 <= year < 2000 and len(serial) == 4 and serial.startswith("0"):
137
+ equiv = f"{str(year)[2:]}{days_data:03d}{serial[1:]}{check_digit}V"
138
+
139
+ return {
140
+ "isValid": True,
141
+ "clean": clean,
142
+ "format": format_type,
143
+ "gender": gender,
144
+ "year": year,
145
+ "dayOfYear": day_of_year,
146
+ "dateOfBirth": dob_formatted,
147
+ "age": age,
148
+ "isVoter": is_voter,
149
+ "serial": serial,
150
+ "checkDigit": check_digit,
151
+ "equivalentNic": equiv or None,
152
+ }
153
+
154
+ def validate_phone(phone_input: str) -> Dict[str, Any]:
155
+ if not isinstance(phone_input, str):
156
+ return {"isValid": False, "error": "Input must be a string"}
157
+ clean = re.sub(r"[\s\-\(\)\.]", "", phone_input.strip())
158
+ if clean.startswith("+94"):
159
+ clean = "0" + clean[3:]
160
+ elif clean.startswith("0094"):
161
+ clean = "0" + clean[4:]
162
+ elif clean.startswith("94") and len(clean) == 11:
163
+ clean = "0" + clean[2:]
164
+ elif len(clean) == 9 and clean.startswith("7"):
165
+ clean = "0" + clean
166
+
167
+ if not re.match(r"^0\d{9}$", clean):
168
+ return {"isValid": False, "error": "Phone number must be a valid 10-digit Sri Lankan number"}
169
+
170
+ prefix = clean[0:3]
171
+ carrier = MOBILE_CARRIERS.get(prefix)
172
+ phone_type = "Mobile" if carrier else ("Fixed Line" if prefix.startswith(("011", "081", "091", "031", "021")) else "Other")
173
+
174
+ return {
175
+ "isValid": True,
176
+ "clean": clean,
177
+ "e164": "+94" + clean[1:],
178
+ "carrier": carrier or "Sri Lanka Telecom / Fixed",
179
+ "type": phone_type,
180
+ "national": f"{clean[0:3]} {clean[3:6]} {clean[6:]}",
181
+ }
182
+
183
+ def get_banks() -> List[Dict[str, Any]]:
184
+ return list(BANKS)
185
+
186
+ def lookup_bank(query: str) -> Optional[Dict[str, Any]]:
187
+ if not query:
188
+ return None
189
+ q = query.strip().lower()
190
+ for b in BANKS:
191
+ if b["code"] == q or b["short_name"].lower() == q or b["name"].lower() == q:
192
+ return b
193
+ return None
194
+
195
+ def format_lkr(amount: float, prefix: str = "Rs. ") -> str:
196
+ try:
197
+ val = float(amount)
198
+ sign = "-" if val < 0 else ""
199
+ return f"{sign}{prefix}{abs(val):,.2f}"
200
+ except (ValueError, TypeError):
201
+ return "Rs. 0.00"
202
+
203
+ def validate_vehicle_plate(input_str: str) -> Dict[str, Any]:
204
+ if not isinstance(input_str, str):
205
+ return {"isValid": False, "error": "Input must be a string"}
206
+ clean = re.sub(r"\s+", " ", input_str.strip().upper())
207
+ m = re.match(r"^(?:([A-Z]{2,3})\s+)?([A-Z]{2,3})[\s-]?(\d{4})$", clean)
208
+ if m:
209
+ prov = m.group(1)
210
+ prefix = m.group(2)
211
+ num = m.group(3)
212
+ cat = "Motor Car" if prefix.startswith("C") else ("Motor Cycle" if prefix.startswith("B") else ("Three Wheeler" if prefix.startswith("A") else "General"))
213
+ clean_plate = f"{prov} {prefix}-{num}" if prov else f"{prefix}-{num}"
214
+ return {
215
+ "isValid": True,
216
+ "clean": clean_plate,
217
+ "category": cat,
218
+ "province": PROVINCES.get(prov) if prov else None,
219
+ }
220
+ return {"isValid": False, "error": "Invalid Sri Lankan vehicle plate format"}
221
+
222
+ def validate_passport(input_str: str) -> Dict[str, Any]:
223
+ if not isinstance(input_str, str):
224
+ return {"isValid": False, "error": "Input must be a string"}
225
+ clean = re.sub(r"[\s-]", "", input_str.strip().upper())
226
+ if re.match(r"^N\d{7}$", clean):
227
+ return {"isValid": True, "clean": clean, "type": "Regular (N-Series)"}
228
+ if re.match(r"^PA\d{7}$", clean):
229
+ return {"isValid": True, "clean": clean, "type": "Biometric (PA-Series)"}
230
+ return {"isValid": False, "error": "Invalid Sri Lankan passport number format"}
231
+
232
+ def validate_tin(input_str: str) -> Dict[str, Any]:
233
+ if not isinstance(input_str, str):
234
+ return {"isValid": False, "error": "Input must be a string"}
235
+ clean = re.sub(r"[\s-]", "", input_str.strip())
236
+ if re.match(r"^\d{9,10}$", clean):
237
+ return {"isValid": True, "clean": clean, "digits": len(clean)}
238
+ return {"isValid": False, "error": "Invalid Sri Lankan TIN. Expected 9 or 10 digits"}
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "lanka-kit"
7
+ version = "1.1.0"
8
+ description = "The Ultimate Sri Lankan Developer Toolkit: NIC, Phone, Banks, Currency, License, Vehicle, Passport, TIN"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Lanka Developer Community" }
14
+ ]
15
+ keywords = [
16
+ "sri-lanka",
17
+ "nic",
18
+ "identity",
19
+ "verification",
20
+ "lanka",
21
+ "phone-validator",
22
+ "dialog",
23
+ "mobitel",
24
+ "cbsl",
25
+ "banks",
26
+ "lkr",
27
+ "lanka-kit"
28
+ ]
29
+ classifiers = [
30
+ "Programming Language :: Python :: 3",
31
+ "License :: OSI Approved :: MIT License",
32
+ "Operating System :: OS Independent",
33
+ ]
34
+
35
+ [tool.setuptools]
36
+ py-modules = ["lanka_kit"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+