dpdp-scrub 0.0.1__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.
Files changed (31) hide show
  1. dpdp_scrub-0.0.1/LICENSE +21 -0
  2. dpdp_scrub-0.0.1/PKG-INFO +164 -0
  3. dpdp_scrub-0.0.1/README.md +142 -0
  4. dpdp_scrub-0.0.1/pyproject.toml +35 -0
  5. dpdp_scrub-0.0.1/setup.cfg +4 -0
  6. dpdp_scrub-0.0.1/src/dpdp_scrub/__init__.py +23 -0
  7. dpdp_scrub-0.0.1/src/dpdp_scrub/data.py +41 -0
  8. dpdp_scrub-0.0.1/src/dpdp_scrub/detectors.py +311 -0
  9. dpdp_scrub-0.0.1/src/dpdp_scrub/integrations.py +73 -0
  10. dpdp_scrub-0.0.1/src/dpdp_scrub/policies.py +27 -0
  11. dpdp_scrub-0.0.1/src/dpdp_scrub/scrubber.py +158 -0
  12. dpdp_scrub-0.0.1/src/dpdp_scrub/spans.py +29 -0
  13. dpdp_scrub-0.0.1/src/dpdp_scrub/validators/__init__.py +28 -0
  14. dpdp_scrub-0.0.1/src/dpdp_scrub/validators/gstin.py +48 -0
  15. dpdp_scrub-0.0.1/src/dpdp_scrub/validators/luhn.py +31 -0
  16. dpdp_scrub-0.0.1/src/dpdp_scrub/validators/pan.py +47 -0
  17. dpdp_scrub-0.0.1/src/dpdp_scrub/validators/verhoeff.py +71 -0
  18. dpdp_scrub-0.0.1/src/dpdp_scrub/vault.py +94 -0
  19. dpdp_scrub-0.0.1/src/dpdp_scrub.egg-info/PKG-INFO +164 -0
  20. dpdp_scrub-0.0.1/src/dpdp_scrub.egg-info/SOURCES.txt +29 -0
  21. dpdp_scrub-0.0.1/src/dpdp_scrub.egg-info/dependency_links.txt +1 -0
  22. dpdp_scrub-0.0.1/src/dpdp_scrub.egg-info/requires.txt +5 -0
  23. dpdp_scrub-0.0.1/src/dpdp_scrub.egg-info/top_level.txt +1 -0
  24. dpdp_scrub-0.0.1/tests/test_context.py +67 -0
  25. dpdp_scrub-0.0.1/tests/test_detect.py +99 -0
  26. dpdp_scrub-0.0.1/tests/test_detect_contact.py +83 -0
  27. dpdp_scrub-0.0.1/tests/test_detect_docs.py +52 -0
  28. dpdp_scrub-0.0.1/tests/test_integrations.py +87 -0
  29. dpdp_scrub-0.0.1/tests/test_policies.py +71 -0
  30. dpdp_scrub-0.0.1/tests/test_scrub.py +64 -0
  31. dpdp_scrub-0.0.1/tests/test_validators.py +141 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dpdp-scrub contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: dpdp-scrub
3
+ Version: 0.0.1
4
+ Summary: Detect, validate, and redact Indian PII (Aadhaar, PAN, GSTIN, UPI, and more) — self-hosted, checksum-validated, DPDP-ready.
5
+ Author: Palkush Dave
6
+ License: MIT
7
+ Keywords: pii,dpdp,aadhaar,pan,gstin,privacy,redaction,india
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Security
13
+ Classifier: Topic :: Text Processing
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8; extra == "dev"
19
+ Requires-Dist: ruff>=0.4; extra == "dev"
20
+ Requires-Dist: mypy>=1.10; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # dpdp-scrub
24
+
25
+ **Detect, validate, and redact Indian PII before it reaches an LLM — self-hosted, checksum-validated, DPDP-ready.**
26
+
27
+ Your org keeps the keys; the LLM gets the placeholders.
28
+
29
+ ```
30
+ IN : Sir aadhaar 2345 6789 0124 hai, call karo 9876543210 pe, PAN ABCPE1234F
31
+ LLM : Sir aadhaar XXXX XXXX 0124 hai, call karo <PHONE_IN_1> pe, PAN <PAN_1>
32
+ BACK: Done! 9876543210 pe call schedule ho gaya. ← rehydrated, agent-side
33
+ ```
34
+
35
+ ## Why this exists
36
+
37
+ Every Indian company wiring LLMs into support, sales, and ops is streaming
38
+ Aadhaar numbers, PANs, GSTINs, UPI IDs, and phone numbers to third-party model
39
+ providers — and copying them into logs, vector DBs, and fine-tuning sets.
40
+ The DPDP Act's substantive obligations become enforceable in **May 2027**
41
+ (penalties up to ₹250 crore); UIDAI's Aadhaar-masking rules and RBI's card
42
+ rules already apply.
43
+
44
+ The redaction loop itself is well-established engineering (Presidio, Google
45
+ DLP). What didn't exist is **eyes that can see Indian PII** — validated,
46
+ context-aware, and open. Google Cloud DLP ships 3 Indian infoTypes, AWS
47
+ Comprehend 4, Presidio 6. dpdp-scrub detects **15**, most with mathematical
48
+ or reference-data validation:
49
+
50
+ | Entity | Validation |
51
+ |---|---|
52
+ | Aadhaar / Aadhaar VID | **Verhoeff checksum** + first-digit rule |
53
+ | GSTIN | **mod-36 check character** + embedded-PAN structure + state code |
54
+ | Credit/debit card | **Luhn checksum** |
55
+ | PAN | holder-type structure rule |
56
+ | UPI ID | handle verified against **known PSP list** (`@ybl`, `@oksbi`, …) |
57
+ | IFSC, phone, email, voter ID, passport | structural + context scoring |
58
+ | Vehicle registration | state/RTO code verified against reference list (incl. BH series) |
59
+ | Bank account, EPF UAN, ABHA | **context-required** (surface only with evidence like "A/c", "UAN") |
60
+
61
+ The context engine is what separates a phone number from an order ID:
62
+
63
+ ```
64
+ order 9876543210 ka status batao → nothing (suppressed by "order")
65
+ call karo 9876543210 pe → PHONE_IN 0.99 (boosted by "call")
66
+ mera a/c 34512345678901 hai → BANK_ACCOUNT (earned by "a/c")
67
+ code 34512345678901 likha hai → nothing
68
+ ```
69
+
70
+ ## Quick start
71
+
72
+ ```python
73
+ from dpdp_scrub import Scrubber
74
+
75
+ s = Scrubber()
76
+
77
+ result = s.scrub("mera PAN ABCPE1234F hai, call 9876543210", session_id="ticket-42")
78
+ result.text # "mera PAN <PAN_1> hai, call <PHONE_IN_1>"
79
+ result.spans # typed spans: entity, offsets, confidence
80
+
81
+ reply = call_your_llm(result.text) # provider never sees real values
82
+ s.rehydrate(reply, "ticket-42") # real values restored, your side only
83
+ ```
84
+
85
+ Detection only:
86
+
87
+ ```python
88
+ from dpdp_scrub import detect
89
+ detect("payment 9876543210@ybl pe karo")
90
+ # [Span(entity='UPI_ID', start=8, end=22, ..., confidence=0.92)]
91
+ ```
92
+
93
+ Zero runtime dependencies. Python 3.9+.
94
+
95
+ ## Trust model
96
+
97
+ ```
98
+ Ring 0: the vault (your Redis/memory) — real values, session-scoped, droppable
99
+ Ring 1: your org's services — placeholders + scoped right to resolve
100
+ Ring 2: LLM providers, logs, vector DBs — placeholders only, no resolution path
101
+ ```
102
+
103
+ - Same value → same placeholder within a session (multi-turn consistency).
104
+ - `vault.drop(session)` makes every old copy of its placeholders permanently
105
+ unresolvable — retroactive redaction for logs you already wrote.
106
+ - No cloud, no telemetry, no phoning home. This is a library, not a service:
107
+ **your data never leaves your infrastructure.**
108
+
109
+ Per-entity actions are policy: `tokenize` (reversible), `mask` (UIDAI-format
110
+ `XXXX XXXX 0124` for Aadhaar, last-4 for cards), `redact` (ABHA health IDs by
111
+ default), `ignore`.
112
+
113
+ ## Benchmark
114
+
115
+ IndicPII-Bench v0 (in `bench/`): 500 synthetic messages — Hinglish, English,
116
+ Devanagari, OCR-noise spacing, multi-entity — with 20% traps (order IDs,
117
+ invoice refs, phones-in-txn-context). Presidio runs at its best
118
+ configuration — its IN_* recognizers explicitly registered (they are shipped
119
+ but **not loaded by default**). F1, value-level matching:
120
+
121
+ | entity | dpdp-scrub | presidio |
122
+ |---|---|---|
123
+ | AADHAAR | **1.000** | 1.000 |
124
+ | GSTIN | **1.000** | 0.971 |
125
+ | PAN | **1.000** | 0.383 |
126
+ | PHONE_IN | **1.000** | 0.602 |
127
+ | UPI_ID | **1.000** | 0.000 |
128
+ | IFSC | **1.000** | 0.000 |
129
+ | EMAIL | **1.000** | 1.000 |
130
+ | **OVERALL** | **1.000** | 0.595 |
131
+ | trap false positives | **0/100** | 200/100 |
132
+
133
+ Honest caveats: this is our own v0 corpus (templated, synthetic, Hinglish-
134
+ heavy) — a perfect self-score mainly means the corpus is still too easy, and
135
+ it will get harder (Devanagari, OCR noise, multi-entity). Presidio was not
136
+ built for code-mixed text; the 107 trap FPs come from it having no context
137
+ suppression (e.g. any 10-digit number near "txn" flags as phone). Reproduce:
138
+ `python3 bench/compare.py 500`.
139
+
140
+ ## Status
141
+
142
+ Pre-alpha, moving fast. Built so far: 15 entities, checksum validators,
143
+ context-scoring engine, scrub/rehydrate loop with session vault — 86 tests,
144
+ all synthetic data. Roadmap:
145
+
146
+ - [ ] YAML policy profiles (`dpdp-strict`, `uidai-mask`, `audit-only`) + audit log
147
+ - [ ] `scrub_json()` for structured payloads
148
+ - [ ] Redis vault backend
149
+ - [ ] LiteLLM / LangChain / Presidio adapters
150
+ - [ ] **IndicPII-Bench** — a public benchmark for Indian PII detection
151
+ - [ ] Indic-script & code-mixed NER for names/addresses (AI4Bharat IndicNER)
152
+
153
+ ## Contributing
154
+
155
+ The reference-data files are designed for one-line PRs: new UPI PSP handles,
156
+ RTO codes, context words (English/Hindi/regional). Entity requests welcome —
157
+ include format, any checksum, and public documentation.
158
+
159
+ All test data must be synthetic (checksum-generated) or published samples.
160
+ Never commit real PII, including your own.
161
+
162
+ ## License
163
+
164
+ MIT
@@ -0,0 +1,142 @@
1
+ # dpdp-scrub
2
+
3
+ **Detect, validate, and redact Indian PII before it reaches an LLM — self-hosted, checksum-validated, DPDP-ready.**
4
+
5
+ Your org keeps the keys; the LLM gets the placeholders.
6
+
7
+ ```
8
+ IN : Sir aadhaar 2345 6789 0124 hai, call karo 9876543210 pe, PAN ABCPE1234F
9
+ LLM : Sir aadhaar XXXX XXXX 0124 hai, call karo <PHONE_IN_1> pe, PAN <PAN_1>
10
+ BACK: Done! 9876543210 pe call schedule ho gaya. ← rehydrated, agent-side
11
+ ```
12
+
13
+ ## Why this exists
14
+
15
+ Every Indian company wiring LLMs into support, sales, and ops is streaming
16
+ Aadhaar numbers, PANs, GSTINs, UPI IDs, and phone numbers to third-party model
17
+ providers — and copying them into logs, vector DBs, and fine-tuning sets.
18
+ The DPDP Act's substantive obligations become enforceable in **May 2027**
19
+ (penalties up to ₹250 crore); UIDAI's Aadhaar-masking rules and RBI's card
20
+ rules already apply.
21
+
22
+ The redaction loop itself is well-established engineering (Presidio, Google
23
+ DLP). What didn't exist is **eyes that can see Indian PII** — validated,
24
+ context-aware, and open. Google Cloud DLP ships 3 Indian infoTypes, AWS
25
+ Comprehend 4, Presidio 6. dpdp-scrub detects **15**, most with mathematical
26
+ or reference-data validation:
27
+
28
+ | Entity | Validation |
29
+ |---|---|
30
+ | Aadhaar / Aadhaar VID | **Verhoeff checksum** + first-digit rule |
31
+ | GSTIN | **mod-36 check character** + embedded-PAN structure + state code |
32
+ | Credit/debit card | **Luhn checksum** |
33
+ | PAN | holder-type structure rule |
34
+ | UPI ID | handle verified against **known PSP list** (`@ybl`, `@oksbi`, …) |
35
+ | IFSC, phone, email, voter ID, passport | structural + context scoring |
36
+ | Vehicle registration | state/RTO code verified against reference list (incl. BH series) |
37
+ | Bank account, EPF UAN, ABHA | **context-required** (surface only with evidence like "A/c", "UAN") |
38
+
39
+ The context engine is what separates a phone number from an order ID:
40
+
41
+ ```
42
+ order 9876543210 ka status batao → nothing (suppressed by "order")
43
+ call karo 9876543210 pe → PHONE_IN 0.99 (boosted by "call")
44
+ mera a/c 34512345678901 hai → BANK_ACCOUNT (earned by "a/c")
45
+ code 34512345678901 likha hai → nothing
46
+ ```
47
+
48
+ ## Quick start
49
+
50
+ ```python
51
+ from dpdp_scrub import Scrubber
52
+
53
+ s = Scrubber()
54
+
55
+ result = s.scrub("mera PAN ABCPE1234F hai, call 9876543210", session_id="ticket-42")
56
+ result.text # "mera PAN <PAN_1> hai, call <PHONE_IN_1>"
57
+ result.spans # typed spans: entity, offsets, confidence
58
+
59
+ reply = call_your_llm(result.text) # provider never sees real values
60
+ s.rehydrate(reply, "ticket-42") # real values restored, your side only
61
+ ```
62
+
63
+ Detection only:
64
+
65
+ ```python
66
+ from dpdp_scrub import detect
67
+ detect("payment 9876543210@ybl pe karo")
68
+ # [Span(entity='UPI_ID', start=8, end=22, ..., confidence=0.92)]
69
+ ```
70
+
71
+ Zero runtime dependencies. Python 3.9+.
72
+
73
+ ## Trust model
74
+
75
+ ```
76
+ Ring 0: the vault (your Redis/memory) — real values, session-scoped, droppable
77
+ Ring 1: your org's services — placeholders + scoped right to resolve
78
+ Ring 2: LLM providers, logs, vector DBs — placeholders only, no resolution path
79
+ ```
80
+
81
+ - Same value → same placeholder within a session (multi-turn consistency).
82
+ - `vault.drop(session)` makes every old copy of its placeholders permanently
83
+ unresolvable — retroactive redaction for logs you already wrote.
84
+ - No cloud, no telemetry, no phoning home. This is a library, not a service:
85
+ **your data never leaves your infrastructure.**
86
+
87
+ Per-entity actions are policy: `tokenize` (reversible), `mask` (UIDAI-format
88
+ `XXXX XXXX 0124` for Aadhaar, last-4 for cards), `redact` (ABHA health IDs by
89
+ default), `ignore`.
90
+
91
+ ## Benchmark
92
+
93
+ IndicPII-Bench v0 (in `bench/`): 500 synthetic messages — Hinglish, English,
94
+ Devanagari, OCR-noise spacing, multi-entity — with 20% traps (order IDs,
95
+ invoice refs, phones-in-txn-context). Presidio runs at its best
96
+ configuration — its IN_* recognizers explicitly registered (they are shipped
97
+ but **not loaded by default**). F1, value-level matching:
98
+
99
+ | entity | dpdp-scrub | presidio |
100
+ |---|---|---|
101
+ | AADHAAR | **1.000** | 1.000 |
102
+ | GSTIN | **1.000** | 0.971 |
103
+ | PAN | **1.000** | 0.383 |
104
+ | PHONE_IN | **1.000** | 0.602 |
105
+ | UPI_ID | **1.000** | 0.000 |
106
+ | IFSC | **1.000** | 0.000 |
107
+ | EMAIL | **1.000** | 1.000 |
108
+ | **OVERALL** | **1.000** | 0.595 |
109
+ | trap false positives | **0/100** | 200/100 |
110
+
111
+ Honest caveats: this is our own v0 corpus (templated, synthetic, Hinglish-
112
+ heavy) — a perfect self-score mainly means the corpus is still too easy, and
113
+ it will get harder (Devanagari, OCR noise, multi-entity). Presidio was not
114
+ built for code-mixed text; the 107 trap FPs come from it having no context
115
+ suppression (e.g. any 10-digit number near "txn" flags as phone). Reproduce:
116
+ `python3 bench/compare.py 500`.
117
+
118
+ ## Status
119
+
120
+ Pre-alpha, moving fast. Built so far: 15 entities, checksum validators,
121
+ context-scoring engine, scrub/rehydrate loop with session vault — 86 tests,
122
+ all synthetic data. Roadmap:
123
+
124
+ - [ ] YAML policy profiles (`dpdp-strict`, `uidai-mask`, `audit-only`) + audit log
125
+ - [ ] `scrub_json()` for structured payloads
126
+ - [ ] Redis vault backend
127
+ - [ ] LiteLLM / LangChain / Presidio adapters
128
+ - [ ] **IndicPII-Bench** — a public benchmark for Indian PII detection
129
+ - [ ] Indic-script & code-mixed NER for names/addresses (AI4Bharat IndicNER)
130
+
131
+ ## Contributing
132
+
133
+ The reference-data files are designed for one-line PRs: new UPI PSP handles,
134
+ RTO codes, context words (English/Hindi/regional). Entity requests welcome —
135
+ include format, any checksum, and public documentation.
136
+
137
+ All test data must be synthetic (checksum-generated) or published samples.
138
+ Never commit real PII, including your own.
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dpdp-scrub"
7
+ version = "0.0.1"
8
+ description = "Detect, validate, and redact Indian PII (Aadhaar, PAN, GSTIN, UPI, and more) — self-hosted, checksum-validated, DPDP-ready."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Palkush Dave" }]
13
+ keywords = ["pii", "dpdp", "aadhaar", "pan", "gstin", "privacy", "redaction", "india"]
14
+ classifiers = [
15
+ "Development Status :: 2 - Pre-Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Security",
20
+ "Topic :: Text Processing",
21
+ ]
22
+ dependencies = []
23
+
24
+ [project.optional-dependencies]
25
+ dev = ["pytest>=8", "ruff>=0.4", "mypy>=1.10"]
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+
33
+ [tool.ruff]
34
+ line-length = 100
35
+ src = ["src", "tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """dpdp-scrub: detect, validate, and redact Indian PII.
2
+
3
+ Self-hosted, checksum-validated, DPDP-ready. The LLM never sees the real value;
4
+ your systems still do.
5
+ """
6
+
7
+ __version__ = "0.0.1"
8
+
9
+ from . import validators
10
+ from .detectors import detect
11
+ from .scrubber import Scrubber, ScrubResult
12
+ from .spans import Span
13
+ from .vault import MemoryVault
14
+
15
+ __all__ = [
16
+ "validators",
17
+ "detect",
18
+ "Scrubber",
19
+ "ScrubResult",
20
+ "MemoryVault",
21
+ "Span",
22
+ "__version__",
23
+ ]
@@ -0,0 +1,41 @@
1
+ """Reference data for detectors.
2
+
3
+ These are public identifiers of payment infrastructure (PSP handles), not PII.
4
+ Community-maintainable: adding a new UPI handle is a one-line PR.
5
+ """
6
+
7
+ # Known UPI PSP handles (the part after '@' in a VPA). Not exhaustive —
8
+ # curated from NPCI's ecosystem. The handle list is what separates a UPI ID
9
+ # from an email-shaped string, so keep entries lowercase and dot-free.
10
+ UPI_PSP_HANDLES = frozenset(
11
+ {
12
+ # PhonePe
13
+ "ybl", "ibl", "axl",
14
+ # Google Pay
15
+ "okaxis", "okhdfcbank", "okicici", "oksbi",
16
+ # Paytm
17
+ "paytm", "ptyes", "ptaxis", "pthdfc", "ptsbi",
18
+ # Amazon Pay
19
+ "apl", "yapl", "rapl",
20
+ # WhatsApp Pay
21
+ "waaxis", "wahdfcbank", "waicici", "wasbi",
22
+ # Bank / app handles
23
+ "upi", "sbi", "hdfcbank", "icici", "axisbank", "barodampay",
24
+ "fbl", "idfcbank", "indus", "kotak", "dbs", "yesbank", "rbl",
25
+ "aubank", "bandhan", "federal", "uboi", "cnrb", "pnb", "boi",
26
+ # Apps
27
+ "freecharge", "mobikwik", "airtel", "jio", "slice", "jupiteraxis",
28
+ "naviaxis", "niyoicici", "tapicici", "timecosmos", "zoicici",
29
+ }
30
+ )
31
+
32
+ # RTO state/UT codes for vehicle registration plates, plus the BH (Bharat)
33
+ # series. Includes legacy codes still on the road (OR, UA, TS).
34
+ VEHICLE_STATE_CODES = frozenset(
35
+ {
36
+ "AN", "AP", "AR", "AS", "BR", "CG", "CH", "DD", "DL", "DN", "GA",
37
+ "GJ", "HP", "HR", "JH", "JK", "KA", "KL", "LA", "LD", "MH", "ML",
38
+ "MN", "MP", "MZ", "NL", "OD", "OR", "PB", "PY", "RJ", "SK", "TN",
39
+ "TR", "TS", "TG", "UK", "UA", "UP", "WB",
40
+ }
41
+ )