numclassify 0.4.0__tar.gz → 0.4.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 (46) hide show
  1. {numclassify-0.4.0 → numclassify-0.4.1}/PKG-INFO +28 -17
  2. {numclassify-0.4.0 → numclassify-0.4.1}/README.md +27 -16
  3. numclassify-0.4.1/docs/api.md +141 -0
  4. numclassify-0.4.1/docs/changelog.md +124 -0
  5. numclassify-0.4.1/docs/cli.md +58 -0
  6. numclassify-0.4.1/docs/index.md +79 -0
  7. {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.html +1 -1
  8. {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.js +7 -13
  9. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/__init__.py +1 -1
  10. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/digital.py +1 -1
  11. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/divisors.py +3 -1
  12. {numclassify-0.4.0 → numclassify-0.4.1}/pyproject.toml +1 -1
  13. {numclassify-0.4.0 → numclassify-0.4.1}/tests/test_registry.py +38 -0
  14. numclassify-0.4.0/docs/api.md +0 -28
  15. numclassify-0.4.0/docs/changelog.md +0 -3
  16. numclassify-0.4.0/docs/cli.md +0 -22
  17. numclassify-0.4.0/docs/index.md +0 -23
  18. {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/ci.yml +0 -0
  19. {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/docs.yml +0 -0
  20. {numclassify-0.4.0 → numclassify-0.4.1}/.github/workflows/publish.yml +0 -0
  21. {numclassify-0.4.0 → numclassify-0.4.1}/.gitignore +0 -0
  22. {numclassify-0.4.0 → numclassify-0.4.1}/CHANGELOG.md +0 -0
  23. {numclassify-0.4.0 → numclassify-0.4.1}/CONTRIBUTING.md +0 -0
  24. {numclassify-0.4.0 → numclassify-0.4.1}/LICENSE +0 -0
  25. {numclassify-0.4.0 → numclassify-0.4.1}/SECURITY.md +0 -0
  26. {numclassify-0.4.0 → numclassify-0.4.1}/docs/extra/saffron.css +0 -0
  27. {numclassify-0.4.0 → numclassify-0.4.1}/docs/playground.css +0 -0
  28. {numclassify-0.4.0 → numclassify-0.4.1}/examples/basic_usage.py +0 -0
  29. {numclassify-0.4.0 → numclassify-0.4.1}/examples/custom_type.py +0 -0
  30. {numclassify-0.4.0 → numclassify-0.4.1}/examples/find_perfect_numbers.py +0 -0
  31. {numclassify-0.4.0 → numclassify-0.4.1}/examples/random_classify.py +0 -0
  32. {numclassify-0.4.0 → numclassify-0.4.1}/examples/stream_large_range.py +0 -0
  33. {numclassify-0.4.0 → numclassify-0.4.1}/mkdocs.yml +0 -0
  34. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/__main__.py +0 -0
  35. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/__init__.py +0 -0
  36. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/combinatorial.py +0 -0
  37. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/exam_types.py +0 -0
  38. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/figurate.py +0 -0
  39. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/number_theory.py +0 -0
  40. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/powers.py +0 -0
  41. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/primes.py +0 -0
  42. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/recreational.py +0 -0
  43. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_core/sequences.py +0 -0
  44. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/_registry.py +0 -0
  45. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/cli.py +0 -0
  46. {numclassify-0.4.0 → numclassify-0.4.1}/numclassify/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numclassify
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: The most comprehensive Python library for number classification - 3000+ number types
5
5
  Project-URL: Homepage, https://github.com/aratrikghosh2011-tech/numclassify
6
6
  Project-URL: Repository, https://github.com/aratrikghosh2011-tech/numclassify
@@ -67,9 +67,15 @@ import numclassify as nc
67
67
  nc.is_prime(17) # True
68
68
  nc.is_perfect(28) # True
69
69
 
70
- # Classify a single number — returns number, true properties, and a score
70
+ # Classify a single number
71
71
  nc.classify(1729)
72
- # {'number': 1729, 'true_properties': ['Taxicab', 'Carmichael', ...], 'score': 22}
72
+ # {
73
+ # 'number': 1729,
74
+ # 'score': 22, # total true properties (incl. figurate)
75
+ # 'notable_score': 18, # score excluding polygonal figurate noise
76
+ # 'true_properties': ['Taxicab', 'Carmichael', ...],
77
+ # 'categories': {'primes': [...], 'sequences': [...], ...}
78
+ # }
73
79
 
74
80
  # Batch classify
75
81
  nc.classify_batch([6, 28, 496])
@@ -79,9 +85,12 @@ nc.find_by_property(start=1, end=1000, Perfect=True)
79
85
  # [6, 28, 496]
80
86
 
81
87
  # Stream over large ranges without loading everything into memory
82
- for result in nc.stream(1, 1_000_000):
83
- if result['score'] > 30:
84
- print(result)
88
+ for result in nc.stream(1, 1_000_000, min_score=20):
89
+ print(result)
90
+
91
+ # Stream only numbers with a specific property
92
+ for result in nc.stream(1, 10_000, has_property="prime"):
93
+ print(result)
85
94
 
86
95
  # All true properties of a number
87
96
  nc.get_true_properties(1729)
@@ -131,17 +140,18 @@ numclassify info armstrong
131
140
 
132
141
  | Category | Count | Examples |
133
142
  |---|---|---|
134
- | Polygonal figurate | ~998 | Triangular, Square, Pentagonal, Chiliagonal |
143
+ | Polygonal figurate | ~1003 | Triangular, Square, Pentagonal, Chiliagonal |
135
144
  | Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
136
- | Prime families | 41 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
137
- | Digital invariants | 10 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
145
+ | Prime families | 40 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
146
+ | Digital invariants | 13 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
138
147
  | Divisor-based | 27 | Perfect, Abundant, Weird, Amicable, Practical |
139
- | Sequences | 15 | Fibonacci, Lucas, Catalan, Bell, Padovan |
148
+ | Sequences | 16 | Fibonacci, Lucas, Catalan, Bell, Padovan |
140
149
  | Powers | 13 | Perfect Square, Taxicab, Sum of Two Squares |
141
150
  | Number theory | 14 | Evil, Carmichael, Keith, Autobiographical |
142
151
  | Combinatorial | 10 | Factorial, Primorial, Subfactorial |
143
- | Recreational | 5 | Kaprekar, Automorphic, Palindrome |
144
- | **Total** | **3000+** | |
152
+ | Recreational | 6 | Kaprekar, Automorphic, Palindrome |
153
+ | Exam types | 8 | Armstrong, Strong, Sunny, Buzz, Magic, Unique |
154
+ | **Total** | **2140+** | |
145
155
 
146
156
  ---
147
157
 
@@ -169,18 +179,19 @@ See [`examples/`](examples/) for runnable scripts covering all major features.
169
179
 
170
180
  | Function | Description |
171
181
  |---|---|
172
- | `classify(n)` | Returns `{number, true_properties, score}` |
182
+ | `classify(n)` | Returns `{number, score, notable_score, true_properties, categories}` |
173
183
  | `classify_batch(numbers)` | Classify a list; returns list of dicts |
174
184
  | `random_number(max_n)` | Classify a randomly selected number |
175
185
  | `find_by_property(start, end, **filters)` | Numbers in range matching property filters |
176
- | `stream(start, end)` | Generator — memory-safe range classification |
186
+ | `stream(start, end, min_score, has_property)` | Generator — memory-safe range classification |
177
187
  | `get_all_properties(n)` | Dict of every type mapped to True/False |
178
188
  | `get_true_properties(n)` | List of True property names only |
179
189
  | `print_properties(n)` | Pretty-print property table to stdout |
180
190
  | `count_properties(n)` | Count of True properties |
181
- | `most_special_in_range(lo, hi)` | Number in range with the most True properties |
182
- | `find_in_range(fn, lo, hi)` | Numbers where `fn` returns True |
183
- | `find_all_in_range(lo, hi)` | Dict mapping each number to its true properties |
191
+ | `most_special_in_range(lo, hi, verbose)` | Number in range with the most True properties |
192
+ | `find_in_range(fn, lo, hi)` | Numbers where callable `fn` returns True |
193
+ | `find_any_in_range(predicates, lo, hi)` | Integers in range satisfying at least one predicate |
194
+ | `find_all_in_range(predicates, lo, hi)` | Integers in range satisfying all predicates |
184
195
  | `register` | Decorator to add custom number types |
185
196
  | `is_prime(n)` | Convenience boolean |
186
197
  | `is_armstrong(n)` | Convenience boolean |
@@ -39,9 +39,15 @@ import numclassify as nc
39
39
  nc.is_prime(17) # True
40
40
  nc.is_perfect(28) # True
41
41
 
42
- # Classify a single number — returns number, true properties, and a score
42
+ # Classify a single number
43
43
  nc.classify(1729)
44
- # {'number': 1729, 'true_properties': ['Taxicab', 'Carmichael', ...], 'score': 22}
44
+ # {
45
+ # 'number': 1729,
46
+ # 'score': 22, # total true properties (incl. figurate)
47
+ # 'notable_score': 18, # score excluding polygonal figurate noise
48
+ # 'true_properties': ['Taxicab', 'Carmichael', ...],
49
+ # 'categories': {'primes': [...], 'sequences': [...], ...}
50
+ # }
45
51
 
46
52
  # Batch classify
47
53
  nc.classify_batch([6, 28, 496])
@@ -51,9 +57,12 @@ nc.find_by_property(start=1, end=1000, Perfect=True)
51
57
  # [6, 28, 496]
52
58
 
53
59
  # Stream over large ranges without loading everything into memory
54
- for result in nc.stream(1, 1_000_000):
55
- if result['score'] > 30:
56
- print(result)
60
+ for result in nc.stream(1, 1_000_000, min_score=20):
61
+ print(result)
62
+
63
+ # Stream only numbers with a specific property
64
+ for result in nc.stream(1, 10_000, has_property="prime"):
65
+ print(result)
57
66
 
58
67
  # All true properties of a number
59
68
  nc.get_true_properties(1729)
@@ -103,17 +112,18 @@ numclassify info armstrong
103
112
 
104
113
  | Category | Count | Examples |
105
114
  |---|---|---|
106
- | Polygonal figurate | ~998 | Triangular, Square, Pentagonal, Chiliagonal |
115
+ | Polygonal figurate | ~1003 | Triangular, Square, Pentagonal, Chiliagonal |
107
116
  | Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
108
- | Prime families | 41 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
109
- | Digital invariants | 10 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
117
+ | Prime families | 40 | Twin, Mersenne, Sophie Germain, Wilson, Safe |
118
+ | Digital invariants | 13 | Armstrong, Spy, Harshad, Disarium, Happy, Neon |
110
119
  | Divisor-based | 27 | Perfect, Abundant, Weird, Amicable, Practical |
111
- | Sequences | 15 | Fibonacci, Lucas, Catalan, Bell, Padovan |
120
+ | Sequences | 16 | Fibonacci, Lucas, Catalan, Bell, Padovan |
112
121
  | Powers | 13 | Perfect Square, Taxicab, Sum of Two Squares |
113
122
  | Number theory | 14 | Evil, Carmichael, Keith, Autobiographical |
114
123
  | Combinatorial | 10 | Factorial, Primorial, Subfactorial |
115
- | Recreational | 5 | Kaprekar, Automorphic, Palindrome |
116
- | **Total** | **3000+** | |
124
+ | Recreational | 6 | Kaprekar, Automorphic, Palindrome |
125
+ | Exam types | 8 | Armstrong, Strong, Sunny, Buzz, Magic, Unique |
126
+ | **Total** | **2140+** | |
117
127
 
118
128
  ---
119
129
 
@@ -141,18 +151,19 @@ See [`examples/`](examples/) for runnable scripts covering all major features.
141
151
 
142
152
  | Function | Description |
143
153
  |---|---|
144
- | `classify(n)` | Returns `{number, true_properties, score}` |
154
+ | `classify(n)` | Returns `{number, score, notable_score, true_properties, categories}` |
145
155
  | `classify_batch(numbers)` | Classify a list; returns list of dicts |
146
156
  | `random_number(max_n)` | Classify a randomly selected number |
147
157
  | `find_by_property(start, end, **filters)` | Numbers in range matching property filters |
148
- | `stream(start, end)` | Generator — memory-safe range classification |
158
+ | `stream(start, end, min_score, has_property)` | Generator — memory-safe range classification |
149
159
  | `get_all_properties(n)` | Dict of every type mapped to True/False |
150
160
  | `get_true_properties(n)` | List of True property names only |
151
161
  | `print_properties(n)` | Pretty-print property table to stdout |
152
162
  | `count_properties(n)` | Count of True properties |
153
- | `most_special_in_range(lo, hi)` | Number in range with the most True properties |
154
- | `find_in_range(fn, lo, hi)` | Numbers where `fn` returns True |
155
- | `find_all_in_range(lo, hi)` | Dict mapping each number to its true properties |
163
+ | `most_special_in_range(lo, hi, verbose)` | Number in range with the most True properties |
164
+ | `find_in_range(fn, lo, hi)` | Numbers where callable `fn` returns True |
165
+ | `find_any_in_range(predicates, lo, hi)` | Integers in range satisfying at least one predicate |
166
+ | `find_all_in_range(predicates, lo, hi)` | Integers in range satisfying all predicates |
156
167
  | `register` | Decorator to add custom number types |
157
168
  | `is_prime(n)` | Convenience boolean |
158
169
  | `is_armstrong(n)` | Convenience boolean |
@@ -0,0 +1,141 @@
1
+ # API Reference
2
+
3
+ ## classify(n)
4
+
5
+ Classifies a single integer. Returns a dict:
6
+
7
+ ```python
8
+ {
9
+ "number": 1729,
10
+ "score": 22, # total true properties (includes all figurate types)
11
+ "notable_score": 18, # score excluding polygonal figurate noise — better for display
12
+ "true_properties": ["Taxicab", "Carmichael", ...], # sorted by category then name
13
+ "categories": {
14
+ "primes": ["Carmichael"],
15
+ "sequences": ["..."],
16
+ ...
17
+ }
18
+ }
19
+ ```
20
+
21
+ `notable_score` excludes `figurate` and `figurate_centered` hits. Use it when displaying
22
+ a score to humans — otherwise n=1 (which is the first k-gonal number for every k) shows
23
+ a score of 2000+.
24
+
25
+ ## classify_batch(numbers)
26
+
27
+ Classify a list of integers. Returns a list of dicts in the same format as `classify()`.
28
+
29
+ ```python
30
+ nc.classify_batch([6, 28, 496])
31
+ ```
32
+
33
+ ## random_number(max_n=10000)
34
+
35
+ Classify a random integer from 1 to `max_n`.
36
+
37
+ ## find_by_property(start, end, **filters)
38
+
39
+ Find integers in `[start, end]` matching keyword property filters.
40
+
41
+ ```python
42
+ nc.find_by_property(start=1, end=1000, Perfect=True)
43
+ # [6, 28, 496]
44
+ ```
45
+
46
+ ## stream(start, end, min_score=None, has_property=None)
47
+
48
+ Memory-safe generator over a range. Yields one result dict per integer.
49
+
50
+ - `min_score` — skip numbers with `notable_score` below this threshold
51
+ - `has_property` — skip numbers that don't have this property as True
52
+
53
+ ```python
54
+ for result in nc.stream(1, 1_000_000, min_score=20):
55
+ print(result)
56
+
57
+ for result in nc.stream(1, 10_000, has_property="prime"):
58
+ print(result)
59
+ ```
60
+
61
+ ## get_all_properties(n)
62
+
63
+ Returns a dict of every registered type mapped to `True` or `False`.
64
+
65
+ ## get_true_properties(n)
66
+
67
+ Returns a list of only the `True` property names.
68
+
69
+ ## print_properties(n)
70
+
71
+ Pretty-prints a formatted table of all True properties to stdout.
72
+
73
+ ## count_properties(n)
74
+
75
+ Returns the count of True properties (equivalent to `score` in `classify()`).
76
+
77
+ ## most_special_in_range(lo, hi, verbose=False)
78
+
79
+ Returns the integer in `[lo, hi]` with the highest `score`. Pass `verbose=True`
80
+ for progress output on large ranges.
81
+
82
+ ## find_in_range(fn, lo, hi)
83
+
84
+ Returns all integers in `[lo, hi]` where callable `fn(n)` returns `True`.
85
+
86
+ ```python
87
+ nc.find_in_range(nc.is_prime, 1, 100)
88
+ ```
89
+
90
+ ## find_any_in_range(funcs_or_names, start, end)
91
+
92
+ Returns all integers in `[start, end]` that satisfy **at least one** of the given predicates.
93
+
94
+ ```python
95
+ # Numbers that are prime OR palindrome in 1..100
96
+ nc.find_any_in_range(["prime", "palindrome"], 1, 100)
97
+
98
+ # Using callables
99
+ nc.find_any_in_range([nc.is_prime, nc.is_armstrong], 1, 500)
100
+ ```
101
+
102
+ ## find_all_in_range(funcs_or_names, start, end)
103
+
104
+ Returns all integers in `[start, end]` that satisfy **every** predicate given.
105
+
106
+ ```python
107
+ # Numbers that are BOTH prime AND palindrome in 1..1000
108
+ nc.find_all_in_range(["prime", "palindrome"], 1, 1000)
109
+ # => [2, 3, 5, 7, 11, 101, 131, 151, ...]
110
+ ```
111
+
112
+ ## register
113
+
114
+ Decorator to add a custom number type. Registered types appear everywhere — `classify()`,
115
+ `find_by_property()`, the CLI, all of it.
116
+
117
+ ```python
118
+ from numclassify import register
119
+
120
+ @register(name="My Type", category="recreational", oeis="A000000",
121
+ description="Numbers divisible by 7 starting with 4")
122
+ def is_my_type(n: int) -> bool:
123
+ return n > 0 and n % 7 == 0 and str(n)[0] == "4"
124
+ ```
125
+
126
+ Parameters:
127
+
128
+ - `name` — display name (required)
129
+ - `category` — category string (required)
130
+ - `oeis` — OEIS sequence ID, e.g. `"A000040"` (optional)
131
+ - `description` — one-line description (optional)
132
+
133
+ ## Convenience booleans
134
+
135
+ ```python
136
+ nc.is_prime(17) # True
137
+ nc.is_armstrong(153) # True
138
+ nc.is_perfect(28) # True
139
+ ```
140
+
141
+ These are thin wrappers around the registered functions, exported for direct use.
@@ -0,0 +1,124 @@
1
+ # Changelog
2
+
3
+ All notable changes to numclassify are documented here.
4
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) — [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ---
7
+
8
+ ## [0.4.1] - 2026-06-14
9
+
10
+ ### Fixed
11
+ - `is_achilles(1)` now returns `False`. Previously returned `True` because
12
+ `_is_perfect_power(1)` incorrectly returned `False` (1 = 1² is a perfect power).
13
+ First Achilles number is 72, per OEIS A052486.
14
+ - `stream(min_score=N)` now filters on `notable_score` instead of raw `score`.
15
+ Previously, figurate inflation caused almost all numbers to pass any threshold.
16
+ - `is_spy(0)` now returns `False`. Spy numbers are defined for positive integers only.
17
+ - Playground version badge now always shows live installed version from PyPI
18
+ instead of the hardcoded version in the HTML source.
19
+ - Fixed `find_any_in_range` and `find_all_in_range` documentation — both functions
20
+ take a list of predicates as the first argument, not just `(lo, hi)`.
21
+
22
+ ---
23
+
24
+ ## [0.4.0] - 2026-06-13
25
+
26
+ ### Fixed
27
+ - `classify()` now returns `notable_score` — score excluding figurate and centered-figurate hits. Prevents misleading inflation for n=1 (first member of every polygonal sequence).
28
+ - `is_unique(n)` now returns `False` for negative `n`.
29
+ - `is_practical(0)` now returns `False`.
30
+ - Removed leaked internal names (`Optional`, `_version`, `_PackageNotFoundError`) from `dir(numclassify)`.
31
+ - Playground now displays `notable_score` instead of raw score.
32
+
33
+ ### Changed
34
+ - Development status updated to `5 - Production/Stable`.
35
+
36
+ ### Added
37
+ - `SECURITY.md` with vulnerability reporting instructions.
38
+
39
+ ---
40
+
41
+ ## [0.3.3] - 2026-06-13
42
+
43
+ ### Added
44
+ - 8 new exam number types: Strong, Sunny, Buzz, Magic, Fascinating, Trimorphic, Twisted Prime, Unique.
45
+ - `classify()` returns a `categories` dict grouping true properties by category.
46
+ - `stream()` accepts `min_score` and `has_property` filter parameters.
47
+ - `most_special_in_range()` accepts `verbose=True` for progress output.
48
+ - `find_any_in_range()` exported to public API.
49
+ - Auto-generated crash tests (every registered type tested on 0, 1, 2, −1).
50
+
51
+ ### Fixed
52
+ - Infinite loop in `is_sum_of_three_squares(0)`: `while n%4==0` never exits for n=0.
53
+ - Dead `else` branch removed from `classify()`.
54
+ - Fixed wrong docstring example in `is_leyland_prime`.
55
+
56
+ ---
57
+
58
+ ## [0.3.2.1] - 2026-06-12
59
+
60
+ ### Fixed
61
+ - Batch classify rejected comma/space input — changed `type="number"` to `type="text"` with `inputmode="numeric"`.
62
+ - Search autocomplete fallback if Pyodide load fails.
63
+
64
+ ### Changed
65
+ - MkDocs site theme updated to saffron (#FF9933).
66
+
67
+ ---
68
+
69
+ ## [0.3.2] - 2026-06-12
70
+
71
+ ### Added
72
+ - Search autocomplete dropdown.
73
+ - Confetti when score > 50.
74
+ - Keyboard shortcuts: `C`, `S`, `N`, `?`.
75
+ - `prefers-reduced-motion` support.
76
+
77
+ ---
78
+
79
+ ## [0.3.1] - 2026-06-12
80
+
81
+ ### Added
82
+ - Version badge on playground (live from installed package).
83
+ - Category-colored property tags.
84
+ - Batch classify mode.
85
+ - Recent history panel (last 20 numbers).
86
+ - Property tooltips, fuzzy search, search pagination.
87
+ - Number of the Day date picker.
88
+ - Light/dark theme toggle.
89
+ - Download results as JSON.
90
+
91
+ ---
92
+
93
+ ## [0.3.0] - 2026-06-12
94
+
95
+ ### Added
96
+ - Pyodide playground (`docs/playground.html`) — real Python in the browser.
97
+ - Auto GitHub Pages deployment on push to main.
98
+ - Version sync via `importlib.metadata`.
99
+ - PyPI badges in README.
100
+
101
+ ---
102
+
103
+ ## [0.2.1] - 2026-05-11
104
+
105
+ ### Added
106
+ - `examples/` folder with 5 runnable scripts.
107
+ - `CONTRIBUTING.md`.
108
+ - Namespace cleanup.
109
+
110
+ ---
111
+
112
+ ## [0.2.0] - 2026-05
113
+
114
+ ### Added
115
+ - OIDC trusted publishing via GitHub Actions.
116
+ - MkDocs Material documentation deployed to GitHub Pages.
117
+ - `py.typed` marker.
118
+ - 60 hand-written tests.
119
+
120
+ ---
121
+
122
+ ## [0.1.0] - 2026-05
123
+
124
+ Initial release.
@@ -0,0 +1,58 @@
1
+ # CLI Reference
2
+
3
+ Install the package and the `numclassify` command is available immediately.
4
+
5
+ ## check
6
+
7
+ Classify a single number.
8
+
9
+ ```bash
10
+ numclassify check 1729
11
+ numclassify check 1729 --json # machine-readable JSON output
12
+ ```
13
+
14
+ ## compare
15
+
16
+ Show properties shared between two numbers and properties unique to each.
17
+
18
+ ```bash
19
+ numclassify compare 6 28
20
+ numclassify compare 6 28 --json
21
+ ```
22
+
23
+ ## find
24
+
25
+ Find numbers of a given type, starting from 1.
26
+
27
+ ```bash
28
+ numclassify find armstrong
29
+ numclassify find prime --limit 20
30
+ ```
31
+
32
+ ## range
33
+
34
+ Classify or filter all integers in a range.
35
+
36
+ ```bash
37
+ numclassify range 1 100
38
+ numclassify range 1 100 --filter prime
39
+ ```
40
+
41
+ ## info
42
+
43
+ Show the definition, category, and OEIS reference for a number type.
44
+
45
+ ```bash
46
+ numclassify info armstrong
47
+ numclassify info taxicab
48
+ ```
49
+
50
+ ## list
51
+
52
+ List all registered number types.
53
+
54
+ ```bash
55
+ numclassify list
56
+ numclassify list --category primes
57
+ numclassify list --category divisors
58
+ ```
@@ -0,0 +1,79 @@
1
+ # numclassify
2
+
3
+ **Given a number, what is it?**
4
+
5
+ Most number-theory libraries — `labmath`, `eulerlib`, `pyntlib` — compute things: factor integers, find GCDs, generate primes. `numclassify` solves a different problem. Hand it an integer and it tells you every named mathematical type that number belongs to, across 2140+ categories, with zero external dependencies.
6
+
7
+ ```
8
+ 153 → Armstrong, Harshad, Triangular, Abundant, ...
9
+ 1729 → Taxicab (Hardy–Ramanujan), Carmichael, Zeisel, ...
10
+ 28 → Perfect, Triangular, Hexagonal, Semiprime, ...
11
+ ```
12
+
13
+ Try it live: **[numclassify Playground](playground.html)**
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install numclassify
21
+ ```
22
+
23
+ Python 3.8–3.13. No external dependencies.
24
+
25
+ ---
26
+
27
+ ## Quick example
28
+
29
+ ```python
30
+ import numclassify as nc
31
+
32
+ nc.classify(1729)
33
+ # {
34
+ # 'number': 1729,
35
+ # 'score': 22,
36
+ # 'notable_score': 18,
37
+ # 'true_properties': ['Taxicab', 'Carmichael', ...],
38
+ # 'categories': {'primes': [...], 'sequences': [...], ...}
39
+ # }
40
+
41
+ nc.is_prime(17) # True
42
+ nc.is_perfect(28) # True
43
+ nc.get_true_properties(6) # ['Perfect', 'Triangular', 'Pronic', ...]
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Categories
49
+
50
+ | Category | Count | Examples |
51
+ |---|---|---|
52
+ | Polygonal figurate | ~1003 | Triangular, Square, Pentagonal |
53
+ | Centered polygonal | ~998 | Centered Triangular, Centered Hexagonal |
54
+ | Prime families | 40 | Twin, Mersenne, Sophie Germain, Safe |
55
+ | Digital invariants | 13 | Armstrong, Harshad, Disarium, Happy |
56
+ | Divisor-based | 27 | Perfect, Abundant, Weird, Practical |
57
+ | Sequences | 16 | Fibonacci, Lucas, Catalan, Bell |
58
+ | Powers | 13 | Perfect Square, Taxicab, Powerful |
59
+ | Number theory | 14 | Evil, Carmichael, Autobiographical |
60
+ | Combinatorial | 10 | Factorial, Primorial, Subfactorial |
61
+ | Recreational | 6 | Kaprekar, Automorphic, Palindrome |
62
+ | Exam types | 8 | Strong, Sunny, Buzz, Magic, Unique |
63
+
64
+ ---
65
+
66
+ ## Add your own types
67
+
68
+ ```python
69
+ from numclassify import register
70
+
71
+ @register(name="My Type", category="recreational")
72
+ def is_my_type(n: int) -> bool:
73
+ return n > 0 and n % 7 == 0
74
+
75
+ import numclassify as nc
76
+ nc.is_my_type(42) # True — available everywhere immediately
77
+ ```
78
+
79
+ See [API Reference](api.md) for the full function list, or the [CLI Reference](cli.md) for command-line usage.
@@ -19,7 +19,7 @@
19
19
  <header>
20
20
  <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme"><span id="theme-icon">&#127769;</span></button>
21
21
  <div class="logo-badge">numclassify playground</div>
22
- <div class="version-badge" id="version-badge" data-version="0.3.2.1">v<span id="version-text">—</span></div>
22
+ <div class="version-badge" id="version-badge" data-version="0.4.1">v<span id="version-text">—</span></div>
23
23
  <h1>Number Intelligence</h1>
24
24
  <p class="subtitle">Classify any integer into 3000+ named mathematical types — powered by real Python in your browser.</p>
25
25
  </header>
@@ -228,18 +228,12 @@ json.dumps(m)
228
228
  console.warn('Could not load category map', e);
229
229
  }
230
230
 
231
- // Fetch version number — prefer local source version as canonical
232
- const badge = $('version-badge');
233
- const srcVersion = badge ? badge.dataset.version : null;
231
+ // Fetch version number — always use live installed version from PyPI
234
232
  try {
235
233
  const pypiVer = await pyodide.runPythonAsync('nc.__version__');
236
- // Use the source version (from pyproject.toml) since PyPI may lag behind
237
- $('version-text').textContent = srcVersion || pypiVer;
238
- if (srcVersion && pypiVer !== srcVersion) {
239
- badge.title = 'Source: v' + srcVersion + ' | PyPI: v' + pypiVer;
240
- }
234
+ $('version-text').textContent = pypiVer;
241
235
  } catch(e) {
242
- $('version-text').textContent = srcVersion || '?';
236
+ $('version-text').textContent = '?';
243
237
  }
244
238
 
245
239
  setProgress(100, 'Ready!');
@@ -283,7 +277,7 @@ async function doClassify(n = null) {
283
277
  const result = await pyodide.runPythonAsync(`
284
278
  import json
285
279
  r = nc.classify(${val})
286
- json.dumps({"number": r["number"], "score": r["score"], "props": r["true_properties"]})
280
+ json.dumps({"number": r["number"], "score": r["notable_score"], "total_score": r["score"], "props": r["true_properties"]})
287
281
  `);
288
282
  const data = JSON.parse(result);
289
283
 
@@ -361,12 +355,12 @@ json.dumps(results)
361
355
  results.forEach(r => {
362
356
  const tags = r.true_properties.slice(0, 8).map(p => p.replace(/_/g, ' ')).join(', ');
363
357
  const more = r.true_properties.length > 8 ? ` +${r.true_properties.length - 8} more` : '';
364
- html += `<tr><td class="b-num" onclick="recallHistory(${r.number})">${r.number}</td><td>${r.score}</td><td class="b-tags">${tags}${more}</td></tr>`;
358
+ html += `<tr><td class="b-num" onclick="recallHistory(${r.number})">${r.number}</td><td>${r.notable_score}</td><td class="b-tags">${tags}${more}</td></tr>`;
365
359
  });
366
360
  html += '</tbody></table>';
367
361
  $('classify-tags').innerHTML = html;
368
362
 
369
- results.forEach(r => saveHistory({ n: r.number, score: r.score }));
363
+ results.forEach(r => saveHistory({ n: r.number, score: r.notable_score }));
370
364
 
371
365
  } catch(e) {
372
366
  toast('Error: ' + e.message.slice(0, 80));
@@ -576,7 +570,7 @@ seed = today.year * 10000 + today.month * 100 + today.day
576
570
  _random.seed(seed)
577
571
  n = _random.randint(1, 9999)
578
572
  r = nc.classify(n)
579
- json.dumps({"number": r["number"], "score": r["score"], "props": r["true_properties"][:12]})
573
+ json.dumps({"number": r["number"], "score": r["notable_score"], "props": r["true_properties"][:12]})
580
574
  `);
581
575
  const data = JSON.parse(result);
582
576
  $('notd-number').textContent = data.number;
@@ -201,7 +201,7 @@ def stream(start: int, end: int, min_score: int = 0, has_property: Optional[str]
201
201
 
202
202
  for n in range(start, end + 1):
203
203
  result = classify(n)
204
- if result["score"] < min_score:
204
+ if result["notable_score"] < min_score:
205
205
  continue
206
206
  if prop_key is not None:
207
207
  props = _gap(n)
@@ -151,7 +151,7 @@ def is_spy(n: int) -> bool:
151
151
  >>> is_spy(124) # sum=7, product=8 → False
152
152
  False
153
153
  """
154
- if n < 0:
154
+ if n <= 0:
155
155
  return False
156
156
  digits = [int(d) for d in str(n)]
157
157
  s = sum(digits)
@@ -97,7 +97,9 @@ def _factorization(n: int) -> List[tuple]:
97
97
 
98
98
  def _is_perfect_power(n: int) -> bool:
99
99
  """Return True if n = k^m for some k>1, m>1."""
100
- if n <= 1:
100
+ if n == 1:
101
+ return True # 1 = 1^k for any k >= 2
102
+ if n <= 0:
101
103
  return False
102
104
  for exp in range(2, n.bit_length() + 1):
103
105
  root = round(n ** (1 / exp))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "numclassify"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "The most comprehensive Python library for number classification - 3000+ number types"
9
9
  authors = [{name = "Aratrik Ghosh", email = "aratrikghosh2011@gmail.com"}]
10
10
  readme = "README.md"
@@ -432,3 +432,41 @@ def test_no_leaked_names():
432
432
  assert "Optional" not in public_names
433
433
  assert "_version" not in public_names
434
434
  assert "_PackageNotFoundError" not in public_names
435
+
436
+
437
+ # ---------------------------------------------------------------------------
438
+ # v0.4.1 regression tests
439
+ # ---------------------------------------------------------------------------
440
+
441
+
442
+ def test_achilles_not_one():
443
+ """1 is not an Achilles number — first is 72."""
444
+ from numclassify._core.divisors import is_achilles
445
+ assert is_achilles(1) is False
446
+ assert is_achilles(72) is True
447
+ assert is_achilles(108) is True
448
+
449
+
450
+ def test_perfect_power_one():
451
+ """1 must be considered a perfect power (1 = 1^2)."""
452
+ from numclassify._core.divisors import _is_perfect_power
453
+ assert _is_perfect_power(1) is True
454
+ assert _is_perfect_power(4) is True
455
+ assert _is_perfect_power(6) is False
456
+
457
+
458
+ def test_stream_min_score_uses_notable():
459
+ """stream min_score must filter on notable_score, not raw score."""
460
+ results = list(nc.stream(1, 10, min_score=40))
461
+ numbers = [r['number'] for r in results]
462
+ assert 4 not in numbers # notable_score=35
463
+ assert 9 not in numbers # notable_score=38
464
+ assert 1 in numbers # notable_score=63
465
+ assert 2 in numbers # notable_score=63
466
+
467
+
468
+ def test_spy_not_zero():
469
+ """is_spy(0) must return False."""
470
+ from numclassify._core.digital import is_spy
471
+ assert is_spy(0) is False
472
+ assert is_spy(1) is True
@@ -1,28 +0,0 @@
1
- # API Reference
2
-
3
- ## classify(n)
4
- Returns dict with number, true_properties list, and score.
5
-
6
- ## classify_batch(numbers)
7
- Classify a list of integers.
8
-
9
- ## random_number(max_n=10000)
10
- Classify a random integer up to max_n.
11
-
12
- ## find_by_property(start, end, **filters)
13
- Find numbers matching property filters.
14
-
15
- ## stream(start, end)
16
- Memory-safe generator over large ranges.
17
-
18
- ## register(name, category, ...)
19
- Decorator to register custom number types. See [examples/custom_type.py](https://github.com/aratrikghosh2011-tech/numclassify/blob/main/examples/custom_type.py).
20
-
21
- ## Utilities
22
- - get_all_properties(n)
23
- - get_true_properties(n)
24
- - print_properties(n)
25
- - count_properties(n)
26
- - most_special_in_range(lo, hi)
27
- - find_in_range(fn, lo, hi)
28
- - is_prime(n), is_armstrong(n), is_perfect(n)
@@ -1,3 +0,0 @@
1
- # Changelog
2
-
3
- See the full changelog on GitHub.
@@ -1,22 +0,0 @@
1
- # CLI Reference
2
-
3
- ## check
4
- numclassify check 1729
5
- numclassify check 1729 --json
6
-
7
- ## compare
8
- numclassify compare 6 28
9
- numclassify compare 6 28 --json
10
-
11
- ## find
12
- numclassify find armstrong --limit 10
13
-
14
- ## range
15
- numclassify range 1 100 --filter prime
16
-
17
- ## info
18
- numclassify info armstrong
19
-
20
- ## list
21
- numclassify list
22
- numclassify list --category primes
@@ -1,23 +0,0 @@
1
- # numclassify
2
-
3
- The most comprehensive Python number classification library - 3000+ types, zero dependencies.
4
-
5
- ## Installation
6
-
7
- pip install numclassify
8
-
9
- ## Quick Start
10
-
11
- import numclassify as nc
12
- nc.classify(1729)
13
- nc.classify_batch([6, 28, 496])
14
- nc.find_by_property(start=1, end=1000, Perfect=True)
15
- nc.random_number()
16
-
17
- ## Why numclassify?
18
-
19
- - 153: Armstrong, Narcissistic, Harshad, Triangular...
20
- - 1729: Taxicab, Carmichael, Dodecagonal...
21
- - 28: Perfect, Triangular, Hexagonal, Keith...
22
-
23
- 3000+ named number types. Instant lookup. Zero dependencies.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes