numbr 2.1.1__tar.gz → 3.2.3__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.
- {numbr-2.1.1 → numbr-3.2.3}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
- {numbr-2.1.1 → numbr-3.2.3}/.github/workflows/ci.yml +2 -2
- {numbr-2.1.1 → numbr-3.2.3}/.github/workflows/publish.yml +31 -0
- numbr-3.2.3/CHANGELOG.md +5 -0
- numbr-3.2.3/PKG-INFO +430 -0
- numbr-3.2.3/README.md +402 -0
- {numbr-2.1.1 → numbr-3.2.3}/RELEASING.md +12 -11
- numbr-3.2.3/assets/png/numbr-lockup-endorsed-1118x344.png +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/pyproject.toml +5 -11
- {numbr-2.1.1 → numbr-3.2.3}/src/numbr/__init__.py +40 -5
- numbr-3.2.3/src/numbr/engine.py +2072 -0
- numbr-3.2.3/tests/test_decimals.py +163 -0
- numbr-3.2.3/tests/test_diagnostics_and_batch.py +206 -0
- numbr-3.2.3/tests/test_notation_and_normalization.py +185 -0
- numbr-3.2.3/tests/test_structured_extraction.py +159 -0
- numbr-2.1.1/CHANGELOG.md +0 -44
- numbr-2.1.1/PKG-INFO +0 -175
- numbr-2.1.1/README.md +0 -141
- numbr-2.1.1/assets/png/numbr-primary-symbol-320x320.png +0 -0
- numbr-2.1.1/assets/png/numbr-primary-wordmark-1200x360.png +0 -0
- numbr-2.1.1/assets/png/numbr-primary-wordmark-python-no-brandmark-654x169.png +0 -0
- numbr-2.1.1/src/numbr/engine.py +0 -1211
- {numbr-2.1.1 → numbr-3.2.3}/.gitattributes +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.github/pull_request_template.md +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.github/release.yml +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.github/workflows/codeql.yml +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/.gitignore +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/CODE_OF_CONDUCT.md +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/CONTRIBUTING.md +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/LICENSE +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/SECURITY.md +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/assets/png/organization/author-lock-up.png +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/assets/png/organization/author-mark.png +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/assets/png/organization/author-wordmark.png +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/tests/test_cardinals.py +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/tests/test_extraction.py +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/tests/test_ordinals.py +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/tests/test_public_api.py +0 -0
- {numbr-2.1.1 → numbr-3.2.3}/tests/test_roman.py +0 -0
|
@@ -16,7 +16,7 @@ jobs:
|
|
|
16
16
|
strategy:
|
|
17
17
|
fail-fast: false
|
|
18
18
|
matrix:
|
|
19
|
-
python-version: ["3.
|
|
19
|
+
python-version: ["3.13"]
|
|
20
20
|
steps:
|
|
21
21
|
- name: Check out repository
|
|
22
22
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
run: |
|
|
56
56
|
python -m pip install --force-reinstall dist/*.whl
|
|
57
57
|
cd /tmp
|
|
58
|
-
python -I -c "import numbr; assert numbr.Cast('Ⅳ', 'Ordinal Word') == 'fourth'"
|
|
58
|
+
python -I -c "import numbr; assert numbr.__version__ == '3.2.3'; assert numbr.Cast('Ⅳ', 'Ordinal Word') == 'fourth'"
|
|
59
59
|
- name: Upload distributions
|
|
60
60
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
61
61
|
with:
|
|
@@ -8,8 +8,39 @@ permissions:
|
|
|
8
8
|
contents: read
|
|
9
9
|
|
|
10
10
|
jobs:
|
|
11
|
+
check-version:
|
|
12
|
+
name: Check whether version is already published
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
outputs:
|
|
15
|
+
exists: ${{ steps.pypi.outputs.exists }}
|
|
16
|
+
steps:
|
|
17
|
+
- name: Check PyPI for the tagged version
|
|
18
|
+
id: pypi
|
|
19
|
+
shell: bash
|
|
20
|
+
env:
|
|
21
|
+
TAG_NAME: ${{ github.event.release.tag_name }}
|
|
22
|
+
run: |
|
|
23
|
+
version="${TAG_NAME#v}"
|
|
24
|
+
status=$(curl --silent --show-error --output /dev/null --write-out "%{http_code}" \
|
|
25
|
+
"https://pypi.org/pypi/numbr/${version}/json")
|
|
26
|
+
case "$status" in
|
|
27
|
+
200)
|
|
28
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
29
|
+
echo "numbr ${version} is already published; skipping package publication."
|
|
30
|
+
;;
|
|
31
|
+
404)
|
|
32
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
33
|
+
;;
|
|
34
|
+
*)
|
|
35
|
+
echo "Unexpected response from PyPI: HTTP ${status}" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
11
40
|
build:
|
|
12
41
|
name: Build release distributions
|
|
42
|
+
needs: check-version
|
|
43
|
+
if: needs.check-version.outputs.exists != 'true'
|
|
13
44
|
runs-on: ubuntu-latest
|
|
14
45
|
steps:
|
|
15
46
|
- name: Check out released tag
|
numbr-3.2.3/CHANGELOG.md
ADDED
numbr-3.2.3/PKG-INFO
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: numbr
|
|
3
|
+
Version: 3.2.3
|
|
4
|
+
Summary: Parse, diagnose, batch-convert, extract, and normalize English numbers, exact decimals, scientific and accounting notation, and Roman numerals.
|
|
5
|
+
Project-URL: Homepage, https://github.com/doydl-technologies/numbr
|
|
6
|
+
Project-URL: Documentation, https://github.com/doydl-technologies/numbr#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/doydl-technologies/numbr/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/doydl-technologies/numbr/blob/main/CHANGELOG.md
|
|
9
|
+
Author: DOYDL Technologies
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: accounting,batch-conversion,decimals,diagnostics,nlp,normalization,numbers,ordinals,parser,roman-numerals,scientific-notation
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: <3.14,>=3.13
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
26
|
+
Requires-Dist: twine>=5.1; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<img src="https://raw.githubusercontent.com/doydl-technologies/numbr/main/assets/png/numbr-lockup-endorsed-1118x344.png" alt="numbr" width="700">
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
Parses, extracts, normalizes, and converts English number words, integers, exact decimals, scientific and accounting notation, ordinal digits, and Roman numerals.
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<a href="https://pypi.org/project/numbr/"><img src="https://img.shields.io/pypi/v/numbr" alt="PyPI version"></a>
|
|
40
|
+
<a href="https://pypi.org/project/numbr/"><img src="https://img.shields.io/pypi/pyversions/numbr" alt="Supported Python versions"></a>
|
|
41
|
+
<a href="https://pepy.tech/project/numbr"><img src="https://static.pepy.tech/badge/numbr" alt="Downloads"></a>
|
|
42
|
+
<a href="https://doydl.com"><img src="https://img.shields.io/badge/Powered%20by-DOYDL%20Technologies-blue" alt="Powered by DOYDL Technologies"></a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
## Table of contents
|
|
46
|
+
|
|
47
|
+
- [What numbr does](#what-numbr-does)
|
|
48
|
+
- [Installation](#installation)
|
|
49
|
+
- [Quick start](#quick-start)
|
|
50
|
+
- [Choosing the right API](#choosing-the-right-api)
|
|
51
|
+
- [Exact decimal values](#exact-decimal-values)
|
|
52
|
+
- [Representations](#representations)
|
|
53
|
+
- [Public API](#public-api)
|
|
54
|
+
- [General conversion](#general-conversion)
|
|
55
|
+
- [Scientific and accounting notation](#scientific-and-accounting-notation)
|
|
56
|
+
- [Structured extraction](#structured-extraction)
|
|
57
|
+
- [Whole-text normalization](#whole-text-normalization)
|
|
58
|
+
- [Rich parsing diagnostics](#rich-parsing-diagnostics)
|
|
59
|
+
- [Batch conversion](#batch-conversion)
|
|
60
|
+
- [Parsing and formatting](#parsing-and-formatting)
|
|
61
|
+
- [Development](#development)
|
|
62
|
+
- [License](#license)
|
|
63
|
+
|
|
64
|
+
## What numbr does
|
|
65
|
+
|
|
66
|
+
`numbr` gives Python applications one consistent way to understand numbers
|
|
67
|
+
written in different forms. It can recognize that `"forty-two"`, `"42"`, and
|
|
68
|
+
`"XLII"` represent the same value, then convert them to the representation an
|
|
69
|
+
application needs.
|
|
70
|
+
|
|
71
|
+
The package is useful when numeric data arrives as human-written text rather
|
|
72
|
+
than a clean integer or decimal. Common examples include form responses,
|
|
73
|
+
documents, transcripts, scraped text, NLP pipelines, and imported business
|
|
74
|
+
data.
|
|
75
|
+
|
|
76
|
+
With `numbr`, an application can:
|
|
77
|
+
|
|
78
|
+
- identify the representation of one numeric value;
|
|
79
|
+
- convert between digits, English number words, ordinals, and Roman numerals;
|
|
80
|
+
- parse exact decimals, scientific notation, and accounting notation;
|
|
81
|
+
- find every numeric expression in a larger passage of text;
|
|
82
|
+
- replace numeric expressions while leaving the surrounding text intact;
|
|
83
|
+
- receive structured explanations for invalid input; and
|
|
84
|
+
- apply the same conversion or diagnostic operation to an iterable of values.
|
|
85
|
+
|
|
86
|
+
`numbr` is deterministic and rule-based. It does not use a language model,
|
|
87
|
+
network service, locale database, or heuristic confidence score. The same
|
|
88
|
+
supported input produces the same result on every platform.
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
Install the latest release from PyPI:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m pip install numbr
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Or install the current development version from GitHub:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m pip install 'numbr @ git+ssh://git@github.com/doydl-technologies/numbr.git'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Python 3.13 is required. The package has no runtime dependencies.
|
|
105
|
+
|
|
106
|
+
## Quick start
|
|
107
|
+
|
|
108
|
+
Import `numbr`, then choose an operation based on whether the input is one
|
|
109
|
+
value, a passage of text, or a collection of values.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import numbr
|
|
113
|
+
|
|
114
|
+
# Identify how one value is written.
|
|
115
|
+
numbr.Type("forty-second")
|
|
116
|
+
# "Ordinal Word"
|
|
117
|
+
|
|
118
|
+
# Convert one value to a requested representation.
|
|
119
|
+
numbr.Cast("forty-second", "Cardinal Number")
|
|
120
|
+
# 42
|
|
121
|
+
|
|
122
|
+
numbr.Cast("1.050", "Cardinal Word")
|
|
123
|
+
# "one point zero five zero"
|
|
124
|
+
|
|
125
|
+
# Collect normalized values from a larger passage.
|
|
126
|
+
numbr.extractNumericValue("She placed 2nd with 1.05 points")
|
|
127
|
+
# [2, Decimal("1.05")]
|
|
128
|
+
|
|
129
|
+
# Collect values together with their original text and source positions.
|
|
130
|
+
numbr.extractNumericEntities("She placed 2nd with 1.05 points")
|
|
131
|
+
# [NumericMatch(text='2nd', value=2, representation='Ordinal Number', ...),
|
|
132
|
+
# NumericMatch(text='1.05', value=Decimal('1.05'), representation='Decimal Number', ...)]
|
|
133
|
+
|
|
134
|
+
# Rewrite recognized expressions without changing the surrounding sentence.
|
|
135
|
+
numbr.normalizeNumericText("She placed forty-second with 1.20e3 points")
|
|
136
|
+
# "She placed 42nd with 1200 points"
|
|
137
|
+
|
|
138
|
+
# Apply one conversion to several values.
|
|
139
|
+
numbr.batchCast(["one", "2", "III"], "Cardinal Number")
|
|
140
|
+
# [1, 2, 3]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `Decimal(...)` text shown above is Python's representation of the returned
|
|
144
|
+
value; the quick-start example does not need to import `Decimal`.
|
|
145
|
+
|
|
146
|
+
## Choosing the right API
|
|
147
|
+
|
|
148
|
+
| Goal | API | Result |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| Identify how one value is written | `Type(value)` | A representation name such as `"Ordinal Word"`, or `None` |
|
|
151
|
+
| Convert one value | `Cast(value, target)` | The value in the requested representation |
|
|
152
|
+
| Collect only values from text | `extractNumericValue(text)` | A list of normalized `int` or `Decimal` values |
|
|
153
|
+
| Collect values and source details | `extractNumericEntities(text)` | A list of `NumericMatch` records with text, type, value, and position |
|
|
154
|
+
| Rewrite numbers inside text | `normalizeNumericText(text)` | A new string with recognized expressions normalized |
|
|
155
|
+
| Explain why parsing failed | `diagnoseNumeric(value)` | A `ParseResult` containing either a value or structured diagnostics |
|
|
156
|
+
| Convert many inputs | `batchCast(values, target)` | A list of converted values using the selected error policy |
|
|
157
|
+
| Diagnose many inputs | `batchDiagnose(values, target)` | One index-stable `ParseResult` per input |
|
|
158
|
+
|
|
159
|
+
Conversion operates on one numeric value. Extraction searches a larger string
|
|
160
|
+
and returns the numbers it contains. Normalization also searches a larger
|
|
161
|
+
string, but returns rewritten text instead of match records.
|
|
162
|
+
|
|
163
|
+
## Exact decimal values
|
|
164
|
+
|
|
165
|
+
Decimal parsing returns [`decimal.Decimal`](https://docs.python.org/3/library/decimal.html#decimal.Decimal)
|
|
166
|
+
instead of `float`. `Decimal` is part of Python's standard library; it is not an
|
|
167
|
+
additional dependency. This avoids binary floating-point rounding and preserves
|
|
168
|
+
meaningful fractional zeros such as the final zero in `"1.050"`.
|
|
169
|
+
|
|
170
|
+
No `Decimal` import is required when the input is a string:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import numbr
|
|
174
|
+
|
|
175
|
+
value = numbr.Cast("1.050", "Decimal Number")
|
|
176
|
+
|
|
177
|
+
value
|
|
178
|
+
# Decimal("1.050")
|
|
179
|
+
|
|
180
|
+
numbr.Cast(value, "Cardinal Word")
|
|
181
|
+
# "one point zero five zero"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Import `Decimal` only when application code needs to construct a decimal value
|
|
185
|
+
directly or perform decimal arithmetic:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from decimal import Decimal
|
|
189
|
+
|
|
190
|
+
import numbr
|
|
191
|
+
|
|
192
|
+
numbr.decimalToAccounting(Decimal("-1234.50"), currency="$")
|
|
193
|
+
# "($1,234.50)"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Representations
|
|
197
|
+
|
|
198
|
+
| Representation | Meaning | Examples | `Type` result |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| Cardinal number | A whole-number quantity written with digits | `42`, `"-42"` | `"Cardinal Number"` |
|
|
201
|
+
| Decimal number | A fixed-point value with a fractional part | `"1.05"`, `Decimal("1.050")` | `"Decimal Number"` |
|
|
202
|
+
| Scientific number | A coefficient multiplied by a power of ten | `"1.20e3"`, `"-4E-2"` | `"Scientific Number"` |
|
|
203
|
+
| Accounting number | A formatted business value whose parentheses indicate a negative amount | `"(1,234.50)"`, `"($50.00)"` | `"Accounting Number"` |
|
|
204
|
+
| Cardinal word | A quantity written with English words | `"forty-two"` | `"Cardinal Word"` |
|
|
205
|
+
| Ordinal number | A position or rank written with digits and a suffix | `"42nd"` | `"Ordinal Number"` |
|
|
206
|
+
| Ordinal word | A position or rank written with English words | `"forty-second"` | `"Ordinal Word"` |
|
|
207
|
+
| Roman numeral | A value written with Roman symbols | `"XLII"`, `"Ⅳ"` | `"Roman Numeral"` |
|
|
208
|
+
|
|
209
|
+
Decimal phrases such as `"one point zero five"` are cardinal-word forms. Digits
|
|
210
|
+
following `point` are interpreted individually so precision and trailing zeros
|
|
211
|
+
are preserved.
|
|
212
|
+
|
|
213
|
+
English-word parsing is intended for numeric expressions, not unrestricted
|
|
214
|
+
natural-language interpretation. Surrounding prose should be passed to an
|
|
215
|
+
extraction or normalization API rather than directly to `Type` or `Cast`.
|
|
216
|
+
|
|
217
|
+
`Cast` accepts the display names and common aliases such as `int`, `decimal`,
|
|
218
|
+
`scientific`, `accounting`, `cardword`, `ordnum`, and `roman`.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
numbr.Type("twenty-first")
|
|
222
|
+
# "Ordinal Word"
|
|
223
|
+
|
|
224
|
+
numbr.Cast("twenty-first", "Cardinal Number")
|
|
225
|
+
# 21
|
|
226
|
+
|
|
227
|
+
numbr.Cast(1994, "Roman Numeral")
|
|
228
|
+
# "MCMXCIV"
|
|
229
|
+
|
|
230
|
+
numbr.Type("-0.004")
|
|
231
|
+
# "Decimal Number"
|
|
232
|
+
|
|
233
|
+
numbr.Cast("-0.004", "Cardinal Word")
|
|
234
|
+
# "negative zero point zero zero four"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Roman output is limited to canonical values from 1 through 3999. English number
|
|
238
|
+
conversion supports values whose integer component has an absolute value less
|
|
239
|
+
than `10**24`.
|
|
240
|
+
|
|
241
|
+
## Public API
|
|
242
|
+
|
|
243
|
+
### General conversion
|
|
244
|
+
|
|
245
|
+
- `Type(value)` detects the representation or returns `None`.
|
|
246
|
+
- `Cast(value, target, *, as_str=False)` converts between representations and
|
|
247
|
+
raises `ValueError` when the input or requested conversion is invalid.
|
|
248
|
+
|
|
249
|
+
Decimal-number conversions return `decimal.Decimal` to avoid binary floating-point
|
|
250
|
+
rounding and to preserve explicitly supplied fractional zeros. Non-integral
|
|
251
|
+
decimals cannot be cast to integer-only cardinal, ordinal, or Roman forms.
|
|
252
|
+
|
|
253
|
+
### Scientific and accounting notation
|
|
254
|
+
|
|
255
|
+
Scientific notation accepts signed coefficients and exponents, including forms
|
|
256
|
+
such as `.5e1` and `5.e2`. Accounting notation uses parentheses for negative
|
|
257
|
+
values and accepts a supported currency token either immediately outside or
|
|
258
|
+
inside the parentheses.
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
numbr.scientificToDecimal("1.20e3")
|
|
262
|
+
# Decimal("1.20E+3")
|
|
263
|
+
|
|
264
|
+
numbr.accountingToDecimal("($1,234.50)")
|
|
265
|
+
# Decimal("-1234.50")
|
|
266
|
+
|
|
267
|
+
numbr.decimalToScientific(Decimal("1230.00"))
|
|
268
|
+
# "1.23e+3"
|
|
269
|
+
|
|
270
|
+
numbr.decimalToAccounting(Decimal("-1234.50"), currency="$")
|
|
271
|
+
# "($1,234.50)"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Both representations participate in `Type`, `Cast`, value extraction, and
|
|
275
|
+
structured extraction.
|
|
276
|
+
|
|
277
|
+
### Structured extraction
|
|
278
|
+
|
|
279
|
+
`extractNumericEntities(text)` returns an immutable `NumericMatch` for every
|
|
280
|
+
recognized expression in source order. Each match provides the original `text`,
|
|
281
|
+
normalized `value`, representation name, `start` and `end` offsets, `span`, and
|
|
282
|
+
`is_ordinal` and `is_negative` flags.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
matches = numbr.extractNumericEntities("She placed forty-second with -0.50 points in round XLII.")
|
|
286
|
+
|
|
287
|
+
matches[0].text, matches[0].value, matches[0].span
|
|
288
|
+
# ("forty-second", 42, (11, 23))
|
|
289
|
+
|
|
290
|
+
matches[1].value
|
|
291
|
+
# Decimal("-0.50")
|
|
292
|
+
|
|
293
|
+
matches[2].representation
|
|
294
|
+
# "Roman Numeral"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Canonical Roman numerals are included by default. Single-letter ASCII forms
|
|
298
|
+
such as `I`, `V`, and `X` are excluded because they are ambiguous in prose; pass
|
|
299
|
+
`include_single_roman=True` to include them. Pass `include_roman=False` to omit
|
|
300
|
+
Roman numerals entirely.
|
|
301
|
+
|
|
302
|
+
### Whole-text normalization
|
|
303
|
+
|
|
304
|
+
`normalizeNumericText(text, style="digits")` replaces every recognized entity
|
|
305
|
+
while preserving all surrounding characters. The default style renders
|
|
306
|
+
cardinals as digits and retains ordinal suffixes. Use `style="words"` to spell
|
|
307
|
+
values in English while retaining ordinal meaning.
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
text = "She placed forty-second with 1.20e3 points and lost ($1,234.50)."
|
|
311
|
+
|
|
312
|
+
numbr.normalizeNumericText(text)
|
|
313
|
+
# "She placed 42nd with 1200 points and lost -1234.50."
|
|
314
|
+
|
|
315
|
+
numbr.normalizeNumericText("Ranked 2nd in round Ⅳ", style="words")
|
|
316
|
+
# "Ranked second in round four"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The Roman-numeral options are the same as those on `extractNumericEntities`.
|
|
320
|
+
Values outside the supported English-word range remain unchanged when using
|
|
321
|
+
the `words` style.
|
|
322
|
+
|
|
323
|
+
### Rich parsing diagnostics
|
|
324
|
+
|
|
325
|
+
`diagnoseNumeric(value, target=None)` returns an immutable `ParseResult`
|
|
326
|
+
instead of raising for invalid input. Successful results contain the normalized
|
|
327
|
+
or converted value and source and target representations. Failed results contain
|
|
328
|
+
one or more `ParseDiagnostic` objects with a stable code, message, source span,
|
|
329
|
+
severity, and suggested correction.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
result = numbr.diagnoseNumeric("22st")
|
|
333
|
+
|
|
334
|
+
result.ok
|
|
335
|
+
# False
|
|
336
|
+
|
|
337
|
+
result.error.code, result.error.span
|
|
338
|
+
# ("invalid_ordinal_suffix", (0, 4))
|
|
339
|
+
|
|
340
|
+
result.error.suggestion
|
|
341
|
+
# "Use '22nd'."
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Diagnostic codes distinguish empty or unsupported inputs, malformed decimal,
|
|
345
|
+
scientific, accounting, ordinal, grouping, word, and Roman forms, unknown
|
|
346
|
+
targets, and unsupported conversions. Call `result.unwrap()` when exception
|
|
347
|
+
semantics are preferred after inspection.
|
|
348
|
+
|
|
349
|
+
### Batch conversion
|
|
350
|
+
|
|
351
|
+
`batchCast(values, target)` applies the same conversion rules as `Cast` to any
|
|
352
|
+
iterable. The `errors` policy controls failed items:
|
|
353
|
+
|
|
354
|
+
- `"raise"` raises `BatchConversionError` with the failing index and diagnostic
|
|
355
|
+
result.
|
|
356
|
+
- `"coerce"` retains the input shape and places `None` at failed positions.
|
|
357
|
+
- `"ignore"` omits failed items.
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
numbr.batchCast(["one", "invalid", "III"], "Cardinal Number", errors="coerce")
|
|
361
|
+
# [1, None, 3]
|
|
362
|
+
|
|
363
|
+
results = numbr.batchDiagnose(["1e3", "bad", "(2.00)"], "Decimal Number")
|
|
364
|
+
[result.ok for result in results]
|
|
365
|
+
# [True, False, True]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Use `batchDiagnose` when every input needs an index-stable structured result,
|
|
369
|
+
regardless of success or failure.
|
|
370
|
+
|
|
371
|
+
### Parsing and formatting
|
|
372
|
+
|
|
373
|
+
- `wordsToInt`
|
|
374
|
+
- `wordsToDecimal`
|
|
375
|
+
- `ordinalWordsToInt`
|
|
376
|
+
- `stringToInt`
|
|
377
|
+
- `stringToDecimal`
|
|
378
|
+
- `scientificToDecimal`
|
|
379
|
+
- `accountingToDecimal`
|
|
380
|
+
- `intToWords`
|
|
381
|
+
- `decimalToWords`
|
|
382
|
+
- `decimalToScientific`
|
|
383
|
+
- `decimalToAccounting`
|
|
384
|
+
- `intToOrdinalWords`
|
|
385
|
+
- `romanToInt`
|
|
386
|
+
- `romanToWords`
|
|
387
|
+
- `intToRoman`
|
|
388
|
+
- `ordinalSuffix`
|
|
389
|
+
- `stripOrdinalSuffix`
|
|
390
|
+
- `extractNumericValue`
|
|
391
|
+
- `extractNumericEntities`
|
|
392
|
+
- `normalizeNumericText`
|
|
393
|
+
- `diagnoseNumeric`
|
|
394
|
+
- `batchDiagnose`
|
|
395
|
+
- `batchCast`
|
|
396
|
+
- `insertSep`
|
|
397
|
+
- `formatDecimal`
|
|
398
|
+
|
|
399
|
+
The direct cross-representation helpers from earlier releases remain available,
|
|
400
|
+
including `cardinalWordToOrdinalNum`, `ordinalNumToCardinalWord`, and related
|
|
401
|
+
functions.
|
|
402
|
+
|
|
403
|
+
Lower-level parsing functions return `None` for invalid input. Decimal conversion
|
|
404
|
+
spells digits after the point individually so that zeros are preserved:
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
numbr.intToWords("1.05")
|
|
408
|
+
# "one point zero five"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Development
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
git clone git@github.com:doydl-technologies/numbr.git
|
|
415
|
+
cd numbr
|
|
416
|
+
python -m venv .venv
|
|
417
|
+
source .venv/bin/activate # .venv/Scripts/activate on MSYS2/Windows
|
|
418
|
+
python -m pip install -e '.[dev]'
|
|
419
|
+
pytest
|
|
420
|
+
ruff check .
|
|
421
|
+
python -m build
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md),
|
|
425
|
+
[CHANGELOG.md](CHANGELOG.md), and [RELEASING.md](RELEASING.md) for project
|
|
426
|
+
policies, release history, and the maintainer release procedure.
|
|
427
|
+
|
|
428
|
+
## License
|
|
429
|
+
|
|
430
|
+
Licensed under the [MIT License](LICENSE).
|