fix(verify_pdf): exclude currency amounts from ASCII date checks (#724)

* fix(verify-pdf): distinguish currency amounts from date ranges

* docs: reference PR 724 in unreleased fix

---------

Co-authored-by: Mads Lorentzen <madslorentzen17@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-11-10 20:03:21 +01:00
1 parent c75041c012
commit ec447a8073
2 files changed
+28 -2

No files matched your search

+10
View File
@@ -121,6 +121,16 @@ class FindNonAsciiDateRangesTests(unittest.TestCase):
with self.subTest(text=text):
self.assertEqual(find_non_ascii_date_ranges(text), [])
def test_attached_currency_symbols_make_numeric_ranges_quantities(self):
# The real PDF CLI previously reported these budget amounts as dates.
# Currency symbols identify amounts even when both values resemble years.
for symbol in ("€", "£", "$", "¥"):
for text in (f"Budget {symbol}2000–{symbol}5000 per month",
f"Budget 1500{symbol}–2000{symbol} per month",
f"Budget {symbol}2000–{symbol}2024 per month"):
with self.subTest(text=text):
self.assertEqual(find_non_ascii_date_ranges(text), [])
def test_numerically_formatted_date_ranges_are_still_caught(self):
# A composite token (slashed, dotted, hyphenated) is not a bare number,
# so the quantity rule does not apply and the year rule still fires.
+18 -2
View File
@@ -155,6 +155,18 @@ def _bare_number(token):
return core
def _currency_amount(token):
"""A numeric amount with an attached Unicode currency symbol, or None."""
core = token.strip(_OUTER_PUNCTUATION)
if core and unicodedata.category(core[0]) == "Sc":
core = core[1:]
elif core and unicodedata.category(core[-1]) == "Sc":
core = core[:-1]
else:
return None
return core if _BARE_NUMBER.fullmatch(core) else None
_TWO_DIGIT_YEAR = re.compile(r"\d{2}")
@@ -165,13 +177,17 @@ def _quantity_range(line, dash_index):
(`2016-17`, `2019-20`): the short form is how academic years and seasons
are written, and it was caught before the quantity rule existed. Anything
else with bare numbers on both sides is a quantity (`1500-2000`,
`2000-5000`) and is left alone.
`2000-5000`) and is left alone. An attached currency symbol makes a
numeric pair an amount range, even when both amounts resemble years.
"""
before = _TOKEN_BEFORE.search(line[:dash_index])
after = _TOKEN_AFTER.search(line[dash_index + 1 :])
if not before or not after:
return False
left, right = _bare_number(before.group(1)), _bare_number(after.group(1))
left_money, right_money = _currency_amount(before.group(1)), _currency_amount(after.group(1))
left, right = left_money or _bare_number(before.group(1)), right_money or _bare_number(after.group(1))
if left_money is not None or right_money is not None:
return left is not None and right is not None
if left is None or right is None:
return False
if _BARE_YEAR.fullmatch(left) and (_BARE_YEAR.fullmatch(right) or _TWO_DIGIT_YEAR.fullmatch(right)):