ynab-amazon-categorizer 2.2.2__tar.gz → 2.2.4__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 (40) hide show
  1. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/CHANGELOG.md +18 -0
  2. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/DEVELOPMENT.md +1 -1
  3. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/PKG-INFO +2 -2
  4. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/README.md +1 -1
  5. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/_version.py +3 -3
  6. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/amazon_parser.py +16 -2
  7. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/cli.py +3 -6
  8. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/PKG-INFO +2 -2
  9. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_amazon_parser.py +53 -0
  10. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_cli_functions.py +55 -0
  11. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/uv.lock +1 -1
  12. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.env.example +0 -0
  13. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.git_archival.txt +0 -0
  14. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.gitattributes +0 -0
  15. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.github/workflows/release.yml +0 -0
  16. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.gitignore +0 -0
  17. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/.pre-commit-config.yaml +0 -0
  18. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/AGENTS.md +0 -0
  19. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/CLAUDE.md +0 -0
  20. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/LICENSE +0 -0
  21. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/pyproject.toml +0 -0
  22. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/setup.cfg +0 -0
  23. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/__init__.py +0 -0
  24. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/__main__.py +0 -0
  25. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/config.py +0 -0
  26. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/exceptions.py +0 -0
  27. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/memo_generator.py +0 -0
  28. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/transaction_matcher.py +0 -0
  29. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer/ynab_client.py +0 -0
  30. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/SOURCES.txt +0 -0
  31. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/dependency_links.txt +0 -0
  32. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/entry_points.txt +0 -0
  33. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/requires.txt +0 -0
  34. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/src/ynab_amazon_categorizer.egg-info/top_level.txt +0 -0
  35. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/__init__.py +0 -0
  36. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_amazon_parser_real.py +0 -0
  37. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_config.py +0 -0
  38. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_memo_generator.py +0 -0
  39. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_transaction_matcher.py +0 -0
  40. {ynab_amazon_categorizer-2.2.2 → ynab_amazon_categorizer-2.2.4}/tests/test_ynab_client.py +0 -0
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.4] - 2026-05-13
11
+
12
+ ### Fixed
13
+
14
+ - Order totals with thousands separators (e.g. `$1,234.56`) are now parsed correctly
15
+ - Single-category matched orders now show all item names in the suggested memo, not just the first
16
+ - Transaction amount is displayed with the correct sign (no spurious negation)
17
+
18
+ ### Changed
19
+
20
+ - Minimum supported Python version clarified to 3.12+ in README and DEVELOPMENT.md
21
+
22
+ ## [2.2.3] - 2026-05-05
23
+
24
+ ### Fixed
25
+
26
+ - Footer and nav boilerplate from the Amazon orders page (copyright lines, address blocks, credit card promos, accessibility hints) is no longer extracted as order items
27
+
10
28
  ## [2.2.2] - 2026-04-23
11
29
 
12
30
  ### Fixed
@@ -4,7 +4,7 @@ Practical commands and conventions for working on this repository.
4
4
 
5
5
  ## Prerequisites
6
6
 
7
- - Python 3.10+
7
+ - Python 3.12+
8
8
  - [uv](https://docs.astral.sh/uv/)
9
9
 
10
10
  ## Setup
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ynab-amazon-categorizer
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Automatically categorize Amazon transactions in YNAB with rich item information
5
5
  Author: dizzlkheinz
6
6
  License: GPL-3.0
@@ -49,7 +49,7 @@ When you paste in the text from your Amazon order page:
49
49
 
50
50
  ## Prerequisites
51
51
 
52
- - Python 3.12+ (recommended; 3.10+ supported)
52
+ - Python 3.12+
53
53
  - [uv](https://docs.astral.sh/uv/) (recommended) or standard Python tooling
54
54
  - YNAB account with API access
55
55
 
@@ -15,7 +15,7 @@ When you paste in the text from your Amazon order page:
15
15
 
16
16
  ## Prerequisites
17
17
 
18
- - Python 3.12+ (recommended; 3.10+ supported)
18
+ - Python 3.12+
19
19
  - [uv](https://docs.astral.sh/uv/) (recommended) or standard Python tooling
20
20
  - YNAB account with API access
21
21
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.2.2'
22
- __version_tuple__ = version_tuple = (2, 2, 2)
21
+ __version__ = version = '2.2.4'
22
+ __version_tuple__ = version_tuple = (2, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g993627dd7'
24
+ __commit_id__ = commit_id = 'g034b5e27c'
@@ -49,14 +49,14 @@ class AmazonParser:
49
49
  orders = []
50
50
 
51
51
  # Find all order blocks using regex
52
- order_pattern = r"Order placed\s*([A-Za-z]+ \d+, \d{4})\s*Total\s*\$(\d+\.?\d*)\s*.*?Order # (\d{3}-\d{7}-\d{7})"
52
+ order_pattern = r"Order placed\s*([A-Za-z]+ \d+, \d{4})\s*Total\s*\$([0-9][0-9,]*(?:\.[0-9]{1,2})?)\s*.*?Order # (\d{3}-\d{7}-\d{7})"
53
53
  order_matches = list(
54
54
  re.finditer(order_pattern, orders_text, re.DOTALL | re.IGNORECASE)
55
55
  )
56
56
 
57
57
  for idx, match in enumerate(order_matches):
58
58
  order_date = match.group(1).strip()
59
- order_total = float(match.group(2))
59
+ order_total = float(match.group(2).replace(",", ""))
60
60
  order_id = match.group(3)
61
61
 
62
62
  # Find the content after this order until the next order or end
@@ -91,6 +91,15 @@ class AmazonParser:
91
91
 
92
92
  def extract_items_from_content(self, order_content: str) -> list[str]:
93
93
  """Extract item names from order content."""
94
+ # Trim at page footer sentinels to avoid extracting navigation/legal boilerplate
95
+ footer_sentinel = re.search(
96
+ r"©\s*\d{4}|To move between items",
97
+ order_content,
98
+ re.IGNORECASE,
99
+ )
100
+ if footer_sentinel:
101
+ order_content = order_content[: footer_sentinel.start()]
102
+
94
103
  candidates: list[str] = []
95
104
  lines = order_content.split("\n")
96
105
 
@@ -152,6 +161,11 @@ class AmazonParser:
152
161
  "browse",
153
162
  "prime",
154
163
  "shipping",
164
+ "mastercard",
165
+ "your brand",
166
+ "registry & gift",
167
+ "attract and engage",
168
+ "interest-based",
155
169
  ]
156
170
  if not any(word in cleaned_line.lower() for word in skip_words):
157
171
  candidates.append(cleaned_line)
@@ -427,12 +427,9 @@ def resolve_memo(
427
427
  if item_details:
428
428
  if isinstance(item_details, dict) and "items" in item_details:
429
429
  # Auto-matched order data - format as: Item Name\n Order Link
430
- items_list = item_details["items"]
431
430
  items_text = (
432
- items_list[0]
433
- if isinstance(items_list, list) and items_list
434
- else "Amazon Purchase"
435
- )
431
+ generate_split_summary_memo(matching_order) if matching_order else ""
432
+ ) or "Amazon Purchase"
436
433
  order_id_value = item_details["order_id"]
437
434
  order_link = memo_generator.generate_amazon_order_link(
438
435
  order_id_value if isinstance(order_id_value, str) else None
@@ -639,7 +636,7 @@ def process_transaction(
639
636
  print(f" ID: {transaction_id}")
640
637
  print(f" Date: {date}")
641
638
  print(f" Payee: {payee}")
642
- print(f" Amount: {-amount_float:.2f}")
639
+ print(f" Amount: {amount_float:.2f}")
643
640
  if original_memo:
644
641
  print(f" Original Memo: {original_memo}")
645
642
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ynab-amazon-categorizer
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: Automatically categorize Amazon transactions in YNAB with rich item information
5
5
  Author: dizzlkheinz
6
6
  License: GPL-3.0
@@ -49,7 +49,7 @@ When you paste in the text from your Amazon order page:
49
49
 
50
50
  ## Prerequisites
51
51
 
52
- - Python 3.12+ (recommended; 3.10+ supported)
52
+ - Python 3.12+
53
53
  - [uv](https://docs.astral.sh/uv/) (recommended) or standard Python tooling
54
54
  - YNAB account with API access
55
55
 
@@ -24,6 +24,22 @@ def test_parse_simple_order() -> None:
24
24
  assert "Fancy Feast" in order.items[0]
25
25
 
26
26
 
27
+ def test_parse_comma_formatted_total() -> None:
28
+ """Totals with thousands separators are parsed as the full amount."""
29
+ order_text = """
30
+ Order placed January 1, 2026
31
+ Total $1,234.56
32
+ Order # 702-8237239-1234567
33
+ Premium Product Name With Enough Words To Parse
34
+ """
35
+
36
+ parser = AmazonParser()
37
+ orders = parser.parse_orders_page(order_text)
38
+
39
+ assert len(orders) == 1
40
+ assert orders[0].total == 1234.56
41
+
42
+
27
43
  def test_parse_empty_order_text() -> None:
28
44
  """Test parsing empty order text returns empty list."""
29
45
  parser = AmazonParser()
@@ -222,3 +238,40 @@ Now arriving today 5:15 p.m. - 8:15 p.m.
222
238
  items = parser.extract_items_from_content(order_content)
223
239
  assert all("arriving" not in i.lower() for i in items)
224
240
  assert any("Psyllium" in i for i in items)
241
+
242
+
243
+ def test_footer_boilerplate_not_extracted() -> None:
244
+ """Page footer and Amazon nav boilerplate must not appear as items."""
245
+ parser = AmazonParser()
246
+
247
+ order_content = """
248
+ Tampax Pearl Tampons, Plastic Applicator, Light Absorbency, 50 Count
249
+ Buy it again
250
+ Protect & Build Your Brand
251
+ Amazon.ca Rewards Mastercard
252
+ Registry & Gift List
253
+ Find, attract and engage customers
254
+ Interest-Based Ads
255
+ © 1996-2026, Amazon.com, Inc. or its affiliates
256
+ Amazon.com.ca ULC | 40 King Street W 47th Floor, Toronto, Ontario, Canada, M5H 3Y2
257
+ Influencers & Associates
258
+ To move between items, use your keyboard's up or down arrows.
259
+ """
260
+
261
+ items = parser.extract_items_from_content(order_content)
262
+
263
+ assert len(items) == 1
264
+ assert "Tampax" in items[0]
265
+ for word in [
266
+ "Mastercard",
267
+ "Registry",
268
+ "attract",
269
+ "Interest-Based",
270
+ "affiliates",
271
+ "ULC",
272
+ "Influencers",
273
+ "keyboard",
274
+ ]:
275
+ assert not any(word.lower() in item.lower() for item in items), (
276
+ f"Boilerplate '{word}' leaked into items"
277
+ )
@@ -14,6 +14,8 @@ from ynab_amazon_categorizer.cli import (
14
14
  fetch_amazon_transactions,
15
15
  generate_split_summary_memo,
16
16
  print_config_summary,
17
+ process_transaction,
18
+ resolve_memo,
17
19
  )
18
20
  from ynab_amazon_categorizer.config import Config
19
21
  from ynab_amazon_categorizer.memo_generator import MemoGenerator
@@ -168,6 +170,23 @@ def test_build_split_payload_with_order() -> None:
168
170
  assert "Widget B" in result["memo"]
169
171
 
170
172
 
173
+ def test_resolve_memo_keeps_all_matched_items(
174
+ monkeypatch: pytest.MonkeyPatch,
175
+ ) -> None:
176
+ """Single-category matched orders keep all parsed items in the suggested memo."""
177
+ order = Order()
178
+ order.order_id = "702-1234567-7654321"
179
+ order.items = ["Widget A", "Widget B"]
180
+
181
+ monkeypatch.setattr("builtins.input", lambda _prompt: "")
182
+
183
+ result = resolve_memo(order, "", MemoGenerator("amazon.com"))
184
+
185
+ assert "Widget A" in result
186
+ assert "Widget B" in result
187
+ assert "702-1234567-7654321" in result
188
+
189
+
171
190
  # --- print_config_summary tests ---
172
191
 
173
192
 
@@ -297,6 +316,42 @@ def test_fetch_amazon_transactions_includes_manual() -> None:
297
316
  assert result[0]["id"] == "t1"
298
317
 
299
318
 
319
+ # --- process_transaction display tests ---
320
+
321
+
322
+ def test_process_transaction_displays_inflow_amount_without_negating(
323
+ monkeypatch: pytest.MonkeyPatch,
324
+ capsys: pytest.CaptureFixture[str],
325
+ ) -> None:
326
+ """Accepted inflows display with their actual positive sign."""
327
+ transaction = {
328
+ "id": "t1",
329
+ "date": "2025-01-15",
330
+ "payee_name": "Amazon",
331
+ "amount": 10000,
332
+ "memo": "",
333
+ }
334
+ responses = iter(["y", "s"])
335
+ monkeypatch.setattr("builtins.input", lambda _prompt: next(responses))
336
+
337
+ result = process_transaction(
338
+ transaction,
339
+ 0,
340
+ 1,
341
+ None,
342
+ MemoGenerator(),
343
+ Mock(),
344
+ Mock(),
345
+ {},
346
+ {},
347
+ )
348
+
349
+ captured = capsys.readouterr().out
350
+ assert result is True
351
+ assert "Found inflow transaction: Amazon $10.00" in captured
352
+ assert "Amount: 10.00" in captured
353
+
354
+
300
355
  # --- generate_split_summary_memo tests ---
301
356
 
302
357
 
@@ -486,6 +486,6 @@ requires-dist = [
486
486
  { name = "python-dotenv", specifier = ">=1.2.1" },
487
487
  { name = "requests", specifier = ">=2.32.5" },
488
488
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.0" },
489
- { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.14" },
489
+ { name = "ty", marker = "extra == 'dev'", specifier = "==0.0.14" },
490
490
  ]
491
491
  provides-extras = ["dev"]