daichodo-validate 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.
- daichodo_validate-0.1.0/.gitignore +8 -0
- daichodo_validate-0.1.0/LICENSE +21 -0
- daichodo_validate-0.1.0/PKG-INFO +84 -0
- daichodo_validate-0.1.0/README.md +63 -0
- daichodo_validate-0.1.0/pyproject.toml +31 -0
- daichodo_validate-0.1.0/src/daichodo_validate/__init__.py +125 -0
- daichodo_validate-0.1.0/src/daichodo_validate/py.typed +0 -0
- daichodo_validate-0.1.0/tests/check-digit-vectors.json +2462 -0
- daichodo_validate-0.1.0/tests/test_validate.py +99 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daichodo
|
|
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,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: daichodo-validate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Validate Japanese corporate numbers (法人番号) and qualified invoice registration numbers (登録番号). Zero dependencies, no network.
|
|
5
|
+
Project-URL: Homepage, https://daichodo.com
|
|
6
|
+
Project-URL: Source, https://github.com/daichodo/daichodo-python
|
|
7
|
+
Project-URL: Issues, https://github.com/daichodo/daichodo-python/issues
|
|
8
|
+
Author-email: Daichodo <dev@daichodo.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: corporate-number,invoice,japan,validation,インボイス,法人番号,適格請求書
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: Japanese
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# daichodo-validate
|
|
23
|
+
|
|
24
|
+
法人番号と適格請求書発行事業者の登録番号を検証します。依存関係なし、通信なし、APIキー不要。
|
|
25
|
+
|
|
26
|
+
Validate Japanese corporate numbers (法人番号) and qualified invoice
|
|
27
|
+
registration numbers (登録番号). Zero dependencies, no network, no API key.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install daichodo-validate
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from daichodo_validate import validate_registration_number
|
|
35
|
+
|
|
36
|
+
validate_registration_number("T1010001153225")
|
|
37
|
+
# ValidationResult(valid=True, corporate_number='1010001153225')
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 日本語
|
|
41
|
+
|
|
42
|
+
検査用数字の計算式は国税庁が公表している仕様に基づくため、サービスへの接続は不要です。
|
|
43
|
+
|
|
44
|
+
判定できるのは**形式として正しいか**であり、**実際に登録されているか**ではありません。
|
|
45
|
+
登録の有無や有効期間を確認するには [daichodo.com](https://daichodo.com) の API を
|
|
46
|
+
ご利用ください。
|
|
47
|
+
|
|
48
|
+
### 個人事業主の登録番号に検査用数字はありません
|
|
49
|
+
|
|
50
|
+
個人事業主の登録番号は法人番号から導出されないため、形式以外に検証できる要素が
|
|
51
|
+
ありません。
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
validate_registration_number("T1234567890123")
|
|
55
|
+
# ValidationResult(valid=True, reason='not derived from a 法人番号',
|
|
56
|
+
# corporate_number=None)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
これらは**有効**です。登録簿の約半数は個人事業主であるため、無効として扱うと確認対象の
|
|
60
|
+
半分を誤って弾くことになります。
|
|
61
|
+
|
|
62
|
+
## English
|
|
63
|
+
|
|
64
|
+
The check-digit rules come from the National Tax Agency's published
|
|
65
|
+
specification, so this needs no service behind it.
|
|
66
|
+
|
|
67
|
+
It tells you whether a number is **well-formed** — not whether it is
|
|
68
|
+
**registered**. For registration status and validity dates you need the API at
|
|
69
|
+
[daichodo.com](https://daichodo.com).
|
|
70
|
+
|
|
71
|
+
### Sole traders have no check digit
|
|
72
|
+
|
|
73
|
+
Registration numbers for sole traders are not derived from a 法人番号, so there
|
|
74
|
+
is nothing to verify beyond the format. They are **valid**. Roughly half the
|
|
75
|
+
register is sole traders, so treating them as invalid would reject half of
|
|
76
|
+
everything you look at.
|
|
77
|
+
|
|
78
|
+
## ライセンス / Licence
|
|
79
|
+
|
|
80
|
+
MIT.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
出典:国税庁法人番号公表サイト(国税庁)(https://www.houjin-bangou.nta.go.jp/)を加工して作成
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# daichodo-validate
|
|
2
|
+
|
|
3
|
+
法人番号と適格請求書発行事業者の登録番号を検証します。依存関係なし、通信なし、APIキー不要。
|
|
4
|
+
|
|
5
|
+
Validate Japanese corporate numbers (法人番号) and qualified invoice
|
|
6
|
+
registration numbers (登録番号). Zero dependencies, no network, no API key.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install daichodo-validate
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from daichodo_validate import validate_registration_number
|
|
14
|
+
|
|
15
|
+
validate_registration_number("T1010001153225")
|
|
16
|
+
# ValidationResult(valid=True, corporate_number='1010001153225')
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 日本語
|
|
20
|
+
|
|
21
|
+
検査用数字の計算式は国税庁が公表している仕様に基づくため、サービスへの接続は不要です。
|
|
22
|
+
|
|
23
|
+
判定できるのは**形式として正しいか**であり、**実際に登録されているか**ではありません。
|
|
24
|
+
登録の有無や有効期間を確認するには [daichodo.com](https://daichodo.com) の API を
|
|
25
|
+
ご利用ください。
|
|
26
|
+
|
|
27
|
+
### 個人事業主の登録番号に検査用数字はありません
|
|
28
|
+
|
|
29
|
+
個人事業主の登録番号は法人番号から導出されないため、形式以外に検証できる要素が
|
|
30
|
+
ありません。
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
validate_registration_number("T1234567890123")
|
|
34
|
+
# ValidationResult(valid=True, reason='not derived from a 法人番号',
|
|
35
|
+
# corporate_number=None)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
これらは**有効**です。登録簿の約半数は個人事業主であるため、無効として扱うと確認対象の
|
|
39
|
+
半分を誤って弾くことになります。
|
|
40
|
+
|
|
41
|
+
## English
|
|
42
|
+
|
|
43
|
+
The check-digit rules come from the National Tax Agency's published
|
|
44
|
+
specification, so this needs no service behind it.
|
|
45
|
+
|
|
46
|
+
It tells you whether a number is **well-formed** — not whether it is
|
|
47
|
+
**registered**. For registration status and validity dates you need the API at
|
|
48
|
+
[daichodo.com](https://daichodo.com).
|
|
49
|
+
|
|
50
|
+
### Sole traders have no check digit
|
|
51
|
+
|
|
52
|
+
Registration numbers for sole traders are not derived from a 法人番号, so there
|
|
53
|
+
is nothing to verify beyond the format. They are **valid**. Roughly half the
|
|
54
|
+
register is sole traders, so treating them as invalid would reject half of
|
|
55
|
+
everything you look at.
|
|
56
|
+
|
|
57
|
+
## ライセンス / Licence
|
|
58
|
+
|
|
59
|
+
MIT.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
出典:国税庁法人番号公表サイト(国税庁)(https://www.houjin-bangou.nta.go.jp/)を加工して作成
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "daichodo-validate"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Validate Japanese corporate numbers (法人番号) and qualified invoice registration numbers (登録番号). Zero dependencies, no network."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
authors = [{ name = "Daichodo", email = "dev@daichodo.com" }]
|
|
9
|
+
keywords = ["法人番号", "適格請求書", "インボイス", "japan", "corporate-number", "invoice", "validation"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Natural Language :: Japanese",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
17
|
+
"Typing :: Typed",
|
|
18
|
+
]
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://daichodo.com"
|
|
23
|
+
Source = "https://github.com/daichodo/daichodo-python"
|
|
24
|
+
Issues = "https://github.com/daichodo/daichodo-python/issues"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["hatchling"]
|
|
28
|
+
build-backend = "hatchling.build"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["src/daichodo_validate"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Validate Japanese corporate numbers (法人番号) and qualified invoice
|
|
2
|
+
registration numbers (登録番号).
|
|
3
|
+
|
|
4
|
+
Zero dependencies, no network, no API key. The check-digit rules come from the
|
|
5
|
+
National Tax Agency's published specification, so this needs no service behind
|
|
6
|
+
it — which is why it is given away.
|
|
7
|
+
|
|
8
|
+
It tells you whether a number is **well-formed**, not whether it is
|
|
9
|
+
**registered**. For that you need a lookup: https://daichodo.com
|
|
10
|
+
|
|
11
|
+
This is a copy of the implementation the Daichodo API runs, not a
|
|
12
|
+
reimplementation. Both assert against the same `check-digit-vectors.json`, so
|
|
13
|
+
the two cannot drift apart without a test failing on one side or the other.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
20
|
+
CORPORATE_NUMBER = re.compile(r"^\d{13}$")
|
|
21
|
+
REGISTRATION_NUMBER = re.compile(r"^T\d{13}$")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ValidationResult:
|
|
26
|
+
value: str
|
|
27
|
+
valid: bool
|
|
28
|
+
reason: str | None = None
|
|
29
|
+
corporate_number: str | None = None
|
|
30
|
+
|
|
31
|
+
def as_dict(self) -> dict:
|
|
32
|
+
return {
|
|
33
|
+
"value": self.value,
|
|
34
|
+
"valid": self.valid,
|
|
35
|
+
"reason": self.reason,
|
|
36
|
+
"corporate_number": self.corporate_number,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_digit(base: str) -> int:
|
|
41
|
+
"""Compute the 法人番号 check digit for a 12-digit base.
|
|
42
|
+
|
|
43
|
+
Per the NTA specification:
|
|
44
|
+
|
|
45
|
+
検査用数字 = 9 - (Σ(n=1..12) Pn × Qn) mod 9
|
|
46
|
+
|
|
47
|
+
where `Pn` is the nth digit counting from the RIGHT of the 12-digit body,
|
|
48
|
+
and `Qn` is 1 for odd n and 2 for even n.
|
|
49
|
+
|
|
50
|
+
The right-to-left ordering is the part that is easy to get backwards, and a
|
|
51
|
+
reversed implementation still produces a plausible digit for roughly one
|
|
52
|
+
number in nine — so it passes casual testing and fails in production.
|
|
53
|
+
"""
|
|
54
|
+
if len(base) != 12 or not base.isdigit():
|
|
55
|
+
raise ValueError("check digit is computed over exactly 12 digits")
|
|
56
|
+
|
|
57
|
+
total = sum(
|
|
58
|
+
int(digit) * (2 if (index + 1) % 2 == 0 else 1)
|
|
59
|
+
for index, digit in enumerate(reversed(base))
|
|
60
|
+
)
|
|
61
|
+
return 9 - (total % 9)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_corporate_number(value: str) -> ValidationResult:
|
|
65
|
+
"""Validate a 13-digit 法人番号, including its check digit."""
|
|
66
|
+
cleaned = _clean(value)
|
|
67
|
+
|
|
68
|
+
if not CORPORATE_NUMBER.match(cleaned):
|
|
69
|
+
return ValidationResult(value, False, "must be exactly 13 digits")
|
|
70
|
+
|
|
71
|
+
expected = check_digit(cleaned[1:])
|
|
72
|
+
if int(cleaned[0]) != expected:
|
|
73
|
+
return ValidationResult(
|
|
74
|
+
value, False, f"check digit is {cleaned[0]}, expected {expected}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return ValidationResult(value, True, corporate_number=cleaned)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def validate_registration_number(value: str) -> ValidationResult:
|
|
81
|
+
"""Validate a 適格請求書発行事業者 登録番号 (`T` + 13 digits).
|
|
82
|
+
|
|
83
|
+
For corporations the 13 digits are the 法人番号, so the check digit applies.
|
|
84
|
+
Individuals are assigned a number that is not derived from a 法人番号 and
|
|
85
|
+
carries no verifiable check digit — format is all that can be asserted, and
|
|
86
|
+
claiming otherwise would reject roughly half the register.
|
|
87
|
+
"""
|
|
88
|
+
cleaned = _clean(value).upper()
|
|
89
|
+
|
|
90
|
+
if not REGISTRATION_NUMBER.match(cleaned):
|
|
91
|
+
return ValidationResult(value, False, "must be 'T' followed by 13 digits")
|
|
92
|
+
|
|
93
|
+
body = cleaned[1:]
|
|
94
|
+
expected = check_digit(body[1:])
|
|
95
|
+
if int(body[0]) == expected:
|
|
96
|
+
return ValidationResult(value, True, corporate_number=body)
|
|
97
|
+
|
|
98
|
+
# Format-valid but not a corporate number. Sole traders live here, so this
|
|
99
|
+
# is not an error — only an absence of a corporate number to join on.
|
|
100
|
+
return ValidationResult(value, True, reason="not derived from a 法人番号")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _clean(value: str) -> str:
|
|
104
|
+
"""Strip the separators people paste from invoices and spreadsheets."""
|
|
105
|
+
return re.sub(r"[\s\-‐-ー―]", "", value.strip())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"ValidationResult",
|
|
110
|
+
"check_digit",
|
|
111
|
+
"validate_corporate_number",
|
|
112
|
+
"validate_registration_number",
|
|
113
|
+
"is_valid",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def is_valid(value: str) -> bool:
|
|
118
|
+
"""True if the value is a well-formed number of either kind."""
|
|
119
|
+
cleaned = _clean(value).upper()
|
|
120
|
+
checker = (
|
|
121
|
+
validate_registration_number
|
|
122
|
+
if cleaned.startswith("T")
|
|
123
|
+
else validate_corporate_number
|
|
124
|
+
)
|
|
125
|
+
return checker(value).valid
|
|
File without changes
|