ynab-amazon-categorizer 2.2.0__tar.gz → 2.2.2__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.
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/CHANGELOG.md +14 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/PKG-INFO +2 -2
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/pyproject.toml +1 -1
- ynab_amazon_categorizer-2.2.2/src/ynab_amazon_categorizer/_version.py +24 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/amazon_parser.py +32 -9
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/PKG-INFO +2 -2
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/requires.txt +1 -1
- ynab_amazon_categorizer-2.2.2/tests/test_amazon_parser.py +224 -0
- ynab_amazon_categorizer-2.2.0/src/ynab_amazon_categorizer/_version.py +0 -34
- ynab_amazon_categorizer-2.2.0/tests/test_amazon_parser.py +0 -108
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.env.example +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.git_archival.txt +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.gitattributes +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.github/workflows/release.yml +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.gitignore +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.pre-commit-config.yaml +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/AGENTS.md +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/CLAUDE.md +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/DEVELOPMENT.md +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/LICENSE +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/README.md +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/setup.cfg +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/__init__.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/__main__.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/cli.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/config.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/exceptions.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/memo_generator.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/transaction_matcher.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/ynab_client.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/SOURCES.txt +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/dependency_links.txt +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/entry_points.txt +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer.egg-info/top_level.txt +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/__init__.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_amazon_parser_real.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_cli_functions.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_config.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_memo_generator.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_transaction_matcher.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_ynab_client.py +0 -0
- {ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/uv.lock +0 -0
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.2] - 2026-04-23
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Pin `ty==0.0.14` in dev dependencies so CI uses the same type-checker version as the lock file
|
|
15
|
+
|
|
16
|
+
## [2.2.1] - 2026-04-23
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Cancelled orders no longer cause their items to bleed into the preceding valid order
|
|
21
|
+
- Quantity badge numbers (e.g. trailing ` 2`, ` 4`) are stripped from item names when the bare form also appears, without corrupting size numbers like `Size 10`
|
|
22
|
+
- `"Now arriving today X:XX p.m."` delivery status lines are no longer extracted as item names
|
|
23
|
+
|
|
10
24
|
## [2.2.0] - 2026-02-23
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ynab-amazon-categorizer
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Automatically categorize Amazon transactions in YNAB with rich item information
|
|
5
5
|
Author: dizzlkheinz
|
|
6
6
|
License: GPL-3.0
|
|
@@ -28,7 +28,7 @@ Provides-Extra: dev
|
|
|
28
28
|
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
29
29
|
Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
|
|
30
30
|
Requires-Dist: ruff>=0.15.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ty
|
|
31
|
+
Requires-Dist: ty==0.0.14; extra == "dev"
|
|
32
32
|
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
|
|
33
33
|
Dynamic: license-file
|
|
34
34
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.2.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'g993627dd7'
|
|
@@ -22,6 +22,19 @@ class Order:
|
|
|
22
22
|
class AmazonParser:
|
|
23
23
|
"""Parses Amazon order data from order history pages."""
|
|
24
24
|
|
|
25
|
+
def _remove_cancelled_orders(self, text: str) -> str:
|
|
26
|
+
"""Remove cancelled order blocks so their items don't bleed into adjacent orders."""
|
|
27
|
+
parts = re.split(r"(?=Order placed)", text)
|
|
28
|
+
kept = []
|
|
29
|
+
for part in parts:
|
|
30
|
+
if (
|
|
31
|
+
re.match(r"\s*Order placed", part)
|
|
32
|
+
and "Your order was cancelled" in part
|
|
33
|
+
):
|
|
34
|
+
continue
|
|
35
|
+
kept.append(part)
|
|
36
|
+
return "".join(kept)
|
|
37
|
+
|
|
25
38
|
def parse_orders_page(self, orders_text: str) -> list[Order]:
|
|
26
39
|
"""Parse Amazon orders page text to extract order information.
|
|
27
40
|
|
|
@@ -31,6 +44,8 @@ class AmazonParser:
|
|
|
31
44
|
if not orders_text.strip():
|
|
32
45
|
return []
|
|
33
46
|
|
|
47
|
+
orders_text = self._remove_cancelled_orders(orders_text)
|
|
48
|
+
|
|
34
49
|
orders = []
|
|
35
50
|
|
|
36
51
|
# Find all order blocks using regex
|
|
@@ -76,7 +91,7 @@ class AmazonParser:
|
|
|
76
91
|
|
|
77
92
|
def extract_items_from_content(self, order_content: str) -> list[str]:
|
|
78
93
|
"""Extract item names from order content."""
|
|
79
|
-
|
|
94
|
+
candidates: list[str] = []
|
|
80
95
|
lines = order_content.split("\n")
|
|
81
96
|
|
|
82
97
|
for line in lines:
|
|
@@ -84,10 +99,10 @@ class AmazonParser:
|
|
|
84
99
|
if not line or len(line) < 15:
|
|
85
100
|
continue
|
|
86
101
|
|
|
87
|
-
# Skip common UI elements
|
|
102
|
+
# Skip common UI elements and delivery status lines
|
|
88
103
|
skip_patterns = [
|
|
89
104
|
r"^(Buy it again|Track package|View|Return|Write|Get|Share|Leave|Ask)",
|
|
90
|
-
r"^(Delivered|Arriving|Auto-delivered|Package was)",
|
|
105
|
+
r"^(Delivered|Arriving|Now arriving|Auto-delivered|Package was)",
|
|
91
106
|
r"^(Return items:|Return or replace)",
|
|
92
107
|
r"^\d+\.?\d* out of \d+ stars",
|
|
93
108
|
r"^FREE|^Today by|^Get it|^List:|^Was:|^Limited-time deal",
|
|
@@ -139,15 +154,23 @@ class AmazonParser:
|
|
|
139
154
|
"shipping",
|
|
140
155
|
]
|
|
141
156
|
if not any(word in cleaned_line.lower() for word in skip_words):
|
|
142
|
-
|
|
157
|
+
candidates.append(cleaned_line)
|
|
158
|
+
|
|
159
|
+
# Build lookup set to detect quantity-badge duplicates.
|
|
160
|
+
# Amazon shows "Product Name <qty>" and "Product Name" on adjacent lines when
|
|
161
|
+
# qty > 1. We want to strip the badge only when the bare form also appears.
|
|
162
|
+
candidate_set = set(candidates)
|
|
143
163
|
|
|
144
|
-
# Remove duplicates, keep up to MAX_ITEMS_PER_ORDER
|
|
145
164
|
seen: set[str] = set()
|
|
146
165
|
unique_items: list[str] = []
|
|
147
|
-
for item in
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
166
|
+
for item in candidates:
|
|
167
|
+
stripped = re.sub(r"\s+\d+$", "", item)
|
|
168
|
+
normalized = (
|
|
169
|
+
stripped if (stripped != item and stripped in candidate_set) else item
|
|
170
|
+
)
|
|
171
|
+
if normalized not in seen and len(normalized) > 15:
|
|
172
|
+
seen.add(normalized)
|
|
173
|
+
unique_items.append(normalized)
|
|
151
174
|
if len(unique_items) >= MAX_ITEMS_PER_ORDER:
|
|
152
175
|
break
|
|
153
176
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ynab-amazon-categorizer
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Automatically categorize Amazon transactions in YNAB with rich item information
|
|
5
5
|
Author: dizzlkheinz
|
|
6
6
|
License: GPL-3.0
|
|
@@ -28,7 +28,7 @@ Provides-Extra: dev
|
|
|
28
28
|
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
29
29
|
Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
|
|
30
30
|
Requires-Dist: ruff>=0.15.0; extra == "dev"
|
|
31
|
-
Requires-Dist: ty
|
|
31
|
+
Requires-Dist: ty==0.0.14; extra == "dev"
|
|
32
32
|
Requires-Dist: pre-commit>=4.5.1; extra == "dev"
|
|
33
33
|
Dynamic: license-file
|
|
34
34
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Tests for Amazon order parsing functionality."""
|
|
2
|
+
|
|
3
|
+
from ynab_amazon_categorizer.amazon_parser import AmazonParser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_parse_simple_order() -> None:
|
|
7
|
+
"""Test parsing a simple Amazon order."""
|
|
8
|
+
order_text = """
|
|
9
|
+
Order placed July 31, 2024
|
|
10
|
+
Total $57.57
|
|
11
|
+
Order # 702-8237239-1234567
|
|
12
|
+
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
parser = AmazonParser()
|
|
16
|
+
orders = parser.parse_orders_page(order_text)
|
|
17
|
+
|
|
18
|
+
assert len(orders) == 1
|
|
19
|
+
order = orders[0]
|
|
20
|
+
assert order.order_id == "702-8237239-1234567"
|
|
21
|
+
assert order.total == 57.57
|
|
22
|
+
assert order.date_str == "July 31, 2024"
|
|
23
|
+
assert len(order.items) == 1
|
|
24
|
+
assert "Fancy Feast" in order.items[0]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_parse_empty_order_text() -> None:
|
|
28
|
+
"""Test parsing empty order text returns empty list."""
|
|
29
|
+
parser = AmazonParser()
|
|
30
|
+
orders = parser.parse_orders_page("")
|
|
31
|
+
assert len(orders) == 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_extract_items_from_order_content() -> None:
|
|
35
|
+
"""Test extracting items from Amazon order content."""
|
|
36
|
+
parser = AmazonParser()
|
|
37
|
+
|
|
38
|
+
order_content = """
|
|
39
|
+
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
40
|
+
ACME Brand Premium Dog Treats - Large Size 2 lb bag
|
|
41
|
+
Skip this line
|
|
42
|
+
Buy it again - should be skipped
|
|
43
|
+
Another product: Organic Cat Litter - 20 lbs Natural Clay
|
|
44
|
+
Track package - should be skipped
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
items = parser.extract_items_from_content(order_content)
|
|
48
|
+
|
|
49
|
+
assert len(items) >= 1
|
|
50
|
+
assert "Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)" in items
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_parse_order_without_items_kept() -> None:
|
|
54
|
+
"""Orders without parseable items are still kept (partial orders)."""
|
|
55
|
+
order_text = """
|
|
56
|
+
Order placed January 5, 2025
|
|
57
|
+
Total $15.00
|
|
58
|
+
Order # 702-1111111-2222222
|
|
59
|
+
short
|
|
60
|
+
"""
|
|
61
|
+
parser = AmazonParser()
|
|
62
|
+
orders = parser.parse_orders_page(order_text)
|
|
63
|
+
|
|
64
|
+
# Order is kept even without items
|
|
65
|
+
assert len(orders) == 1
|
|
66
|
+
assert orders[0].order_id == "702-1111111-2222222"
|
|
67
|
+
assert orders[0].total == 15.00
|
|
68
|
+
assert orders[0].items == []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_parse_multiple_orders() -> None:
|
|
72
|
+
"""Multiple orders in same text are parsed correctly."""
|
|
73
|
+
order_text = """
|
|
74
|
+
Order placed July 31, 2024
|
|
75
|
+
Total $57.57
|
|
76
|
+
Order # 702-1111111-1111111
|
|
77
|
+
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
78
|
+
|
|
79
|
+
Order placed August 15, 2024
|
|
80
|
+
Total $89.99
|
|
81
|
+
Order # 702-2222222-2222222
|
|
82
|
+
Wireless Bluetooth Headphones - Over-Ear Noise Cancelling
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
parser = AmazonParser()
|
|
86
|
+
orders = parser.parse_orders_page(order_text)
|
|
87
|
+
|
|
88
|
+
assert len(orders) == 2
|
|
89
|
+
assert orders[0].order_id == "702-1111111-1111111"
|
|
90
|
+
assert orders[0].total == 57.57
|
|
91
|
+
assert orders[1].order_id == "702-2222222-2222222"
|
|
92
|
+
assert orders[1].total == 89.99
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_extract_more_than_three_items() -> None:
|
|
96
|
+
"""More than 3 items can be extracted (old 3-item limit removed)."""
|
|
97
|
+
parser = AmazonParser()
|
|
98
|
+
|
|
99
|
+
order_content = """
|
|
100
|
+
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
101
|
+
ACME Brand Premium Dog Treats - Large Size 2 lb bag
|
|
102
|
+
Another product: Organic Cat Litter - 20 lbs Natural Clay
|
|
103
|
+
Super Premium Fancy Dog Collar with LED - Size Medium
|
|
104
|
+
Deluxe Pet Bed Extra Large Orthopedic Memory Foam 36 inch
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
items = parser.extract_items_from_content(order_content)
|
|
108
|
+
assert len(items) >= 4 # Should extract more than 3 now
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_cancelled_order_items_do_not_bleed() -> None:
|
|
112
|
+
"""Items from a cancelled order must not appear under the preceding valid order."""
|
|
113
|
+
# Order 701-9742314-2110603 ($29.58 capsules) is followed by a cancelled order
|
|
114
|
+
# whose item (Altra sneaker) must not be attributed to the capsules order.
|
|
115
|
+
order_text = """Order placed
|
|
116
|
+
April 22, 2026
|
|
117
|
+
Total
|
|
118
|
+
$29.58
|
|
119
|
+
Ship to
|
|
120
|
+
Shelby and Kalman Sutker
|
|
121
|
+
Order # 701-9742314-2110603
|
|
122
|
+
View order details Invoice
|
|
123
|
+
|
|
124
|
+
Now arriving today 5:15 p.m. - 8:15 p.m.
|
|
125
|
+
|
|
126
|
+
Yogti Psyllium Husk Capsules, 300 Count 2
|
|
127
|
+
Yogti Psyllium Husk Capsules, 300 Count
|
|
128
|
+
Buy it again
|
|
129
|
+
|
|
130
|
+
Track package
|
|
131
|
+
Return or replace items
|
|
132
|
+
Write a product review
|
|
133
|
+
|
|
134
|
+
Order placed
|
|
135
|
+
April 22, 2026
|
|
136
|
+
Order # 701-0552104-1147435
|
|
137
|
+
|
|
138
|
+
Cancelled
|
|
139
|
+
Your order was cancelled. You have not been charged for this order.
|
|
140
|
+
|
|
141
|
+
Altra Men's Lone Peak 9 Trail Running Shoe, Tan, 13 Wide
|
|
142
|
+
Altra Men's Lone Peak 9 Trail Running Shoe, Tan, 13 Wide
|
|
143
|
+
|
|
144
|
+
Order placed
|
|
145
|
+
April 22, 2026
|
|
146
|
+
Total
|
|
147
|
+
$29.73
|
|
148
|
+
Ship to
|
|
149
|
+
Shelby and Kalman Sutker
|
|
150
|
+
Order # 701-5052171-6951446
|
|
151
|
+
View order details Invoice
|
|
152
|
+
|
|
153
|
+
TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub 4
|
|
154
|
+
TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub
|
|
155
|
+
Buy it again
|
|
156
|
+
View your item
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
parser = AmazonParser()
|
|
160
|
+
orders = parser.parse_orders_page(order_text)
|
|
161
|
+
|
|
162
|
+
# Cancelled order must be omitted entirely
|
|
163
|
+
assert len(orders) == 2
|
|
164
|
+
order_ids = [o.order_id for o in orders]
|
|
165
|
+
assert "701-0552104-1147435" not in order_ids
|
|
166
|
+
|
|
167
|
+
capsules_order = next(o for o in orders if o.order_id == "701-9742314-2110603")
|
|
168
|
+
item_text = " ".join(capsules_order.items)
|
|
169
|
+
assert "Altra" not in item_text, "Cancelled-order item bled into adjacent order"
|
|
170
|
+
assert "Psyllium" in item_text
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_quantity_badge_stripped_from_item_name() -> None:
|
|
174
|
+
"""Trailing quantity badge numbers are removed when the bare name also appears."""
|
|
175
|
+
parser = AmazonParser()
|
|
176
|
+
|
|
177
|
+
# Amazon shows "Product Name <qty>" then "Product Name" on successive lines.
|
|
178
|
+
order_content = """
|
|
179
|
+
TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub 4
|
|
180
|
+
TEMPTATIONS Cat Treats, Seafood Medley Flavour, 454g Tub
|
|
181
|
+
Buy it again
|
|
182
|
+
View your item
|
|
183
|
+
Nizoral Anti-Dandruff Shampoo with 2% Ketoconazole, Fresh Scent, 325 ml
|
|
184
|
+
Nizoral Anti-Dandruff Shampoo with 2% Ketoconazole, Fresh Scent, 325 ml
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
items = parser.extract_items_from_content(order_content)
|
|
188
|
+
assert len(items) == 2
|
|
189
|
+
assert all("454g Tub" in i or "Ketoconazole" in i for i in items)
|
|
190
|
+
# Bare form preserved, badge number gone
|
|
191
|
+
assert not any(i.endswith(" 4") for i in items)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_size_number_not_stripped() -> None:
|
|
195
|
+
"""Trailing size numbers (2+ digits) are NOT stripped — they are part of the name."""
|
|
196
|
+
parser = AmazonParser()
|
|
197
|
+
|
|
198
|
+
order_content = """
|
|
199
|
+
Corset Femme Tops Floral Renaissance Pirate Overbust Boned Bustier Tops Green Size 10
|
|
200
|
+
Corset Femme Tops Floral Renaissance Pirate Overbust Boned Bustier Tops Green Size 10
|
|
201
|
+
Return items: Eligible through May 17, 2026
|
|
202
|
+
Buy it again
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
items = parser.extract_items_from_content(order_content)
|
|
206
|
+
assert len(items) == 1
|
|
207
|
+
assert items[0].endswith("Size 10")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_now_arriving_status_line_skipped() -> None:
|
|
211
|
+
"""'Now arriving today X:XX p.m.' delivery status must not be extracted as an item."""
|
|
212
|
+
parser = AmazonParser()
|
|
213
|
+
|
|
214
|
+
order_content = """
|
|
215
|
+
Now arriving today 5:15 p.m. - 8:15 p.m.
|
|
216
|
+
|
|
217
|
+
Yogti Psyllium Husk Capsules, 300 Count
|
|
218
|
+
Yogti Psyllium Husk Capsules, 300 Count
|
|
219
|
+
Buy it again
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
items = parser.extract_items_from_content(order_content)
|
|
223
|
+
assert all("arriving" not in i.lower() for i in items)
|
|
224
|
+
assert any("Psyllium" in i for i in items)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools-scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"__version__",
|
|
6
|
-
"__version_tuple__",
|
|
7
|
-
"version",
|
|
8
|
-
"version_tuple",
|
|
9
|
-
"__commit_id__",
|
|
10
|
-
"commit_id",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
|
-
version: str
|
|
25
|
-
__version__: str
|
|
26
|
-
__version_tuple__: VERSION_TUPLE
|
|
27
|
-
version_tuple: VERSION_TUPLE
|
|
28
|
-
commit_id: COMMIT_ID
|
|
29
|
-
__commit_id__: COMMIT_ID
|
|
30
|
-
|
|
31
|
-
__version__ = version = '2.2.0'
|
|
32
|
-
__version_tuple__ = version_tuple = (2, 2, 0)
|
|
33
|
-
|
|
34
|
-
__commit_id__ = commit_id = 'g431256193'
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"""Tests for Amazon order parsing functionality."""
|
|
2
|
-
|
|
3
|
-
from ynab_amazon_categorizer.amazon_parser import AmazonParser
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_parse_simple_order() -> None:
|
|
7
|
-
"""Test parsing a simple Amazon order."""
|
|
8
|
-
order_text = """
|
|
9
|
-
Order placed July 31, 2024
|
|
10
|
-
Total $57.57
|
|
11
|
-
Order # 702-8237239-1234567
|
|
12
|
-
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
parser = AmazonParser()
|
|
16
|
-
orders = parser.parse_orders_page(order_text)
|
|
17
|
-
|
|
18
|
-
assert len(orders) == 1
|
|
19
|
-
order = orders[0]
|
|
20
|
-
assert order.order_id == "702-8237239-1234567"
|
|
21
|
-
assert order.total == 57.57
|
|
22
|
-
assert order.date_str == "July 31, 2024"
|
|
23
|
-
assert len(order.items) == 1
|
|
24
|
-
assert "Fancy Feast" in order.items[0]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_parse_empty_order_text() -> None:
|
|
28
|
-
"""Test parsing empty order text returns empty list."""
|
|
29
|
-
parser = AmazonParser()
|
|
30
|
-
orders = parser.parse_orders_page("")
|
|
31
|
-
assert len(orders) == 0
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_extract_items_from_order_content() -> None:
|
|
35
|
-
"""Test extracting items from Amazon order content."""
|
|
36
|
-
parser = AmazonParser()
|
|
37
|
-
|
|
38
|
-
order_content = """
|
|
39
|
-
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
40
|
-
ACME Brand Premium Dog Treats - Large Size 2 lb bag
|
|
41
|
-
Skip this line
|
|
42
|
-
Buy it again - should be skipped
|
|
43
|
-
Another product: Organic Cat Litter - 20 lbs Natural Clay
|
|
44
|
-
Track package - should be skipped
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
items = parser.extract_items_from_content(order_content)
|
|
48
|
-
|
|
49
|
-
assert len(items) >= 1
|
|
50
|
-
assert "Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)" in items
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_parse_order_without_items_kept() -> None:
|
|
54
|
-
"""Orders without parseable items are still kept (partial orders)."""
|
|
55
|
-
order_text = """
|
|
56
|
-
Order placed January 5, 2025
|
|
57
|
-
Total $15.00
|
|
58
|
-
Order # 702-1111111-2222222
|
|
59
|
-
short
|
|
60
|
-
"""
|
|
61
|
-
parser = AmazonParser()
|
|
62
|
-
orders = parser.parse_orders_page(order_text)
|
|
63
|
-
|
|
64
|
-
# Order is kept even without items
|
|
65
|
-
assert len(orders) == 1
|
|
66
|
-
assert orders[0].order_id == "702-1111111-2222222"
|
|
67
|
-
assert orders[0].total == 15.00
|
|
68
|
-
assert orders[0].items == []
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_parse_multiple_orders() -> None:
|
|
72
|
-
"""Multiple orders in same text are parsed correctly."""
|
|
73
|
-
order_text = """
|
|
74
|
-
Order placed July 31, 2024
|
|
75
|
-
Total $57.57
|
|
76
|
-
Order # 702-1111111-1111111
|
|
77
|
-
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
78
|
-
|
|
79
|
-
Order placed August 15, 2024
|
|
80
|
-
Total $89.99
|
|
81
|
-
Order # 702-2222222-2222222
|
|
82
|
-
Wireless Bluetooth Headphones - Over-Ear Noise Cancelling
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
parser = AmazonParser()
|
|
86
|
-
orders = parser.parse_orders_page(order_text)
|
|
87
|
-
|
|
88
|
-
assert len(orders) == 2
|
|
89
|
-
assert orders[0].order_id == "702-1111111-1111111"
|
|
90
|
-
assert orders[0].total == 57.57
|
|
91
|
-
assert orders[1].order_id == "702-2222222-2222222"
|
|
92
|
-
assert orders[1].total == 89.99
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def test_extract_more_than_three_items() -> None:
|
|
96
|
-
"""More than 3 items can be extracted (old 3-item limit removed)."""
|
|
97
|
-
parser = AmazonParser()
|
|
98
|
-
|
|
99
|
-
order_content = """
|
|
100
|
-
Fancy Feast Grilled Wet Cat Food, Tuna Feast - 85 g Can (24 Pack)
|
|
101
|
-
ACME Brand Premium Dog Treats - Large Size 2 lb bag
|
|
102
|
-
Another product: Organic Cat Litter - 20 lbs Natural Clay
|
|
103
|
-
Super Premium Fancy Dog Collar with LED - Size Medium
|
|
104
|
-
Deluxe Pet Bed Extra Large Orthopedic Memory Foam 36 inch
|
|
105
|
-
"""
|
|
106
|
-
|
|
107
|
-
items = parser.extract_items_from_content(order_content)
|
|
108
|
-
assert len(items) >= 4 # Should extract more than 3 now
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/.github/workflows/release.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/src/ynab_amazon_categorizer/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_amazon_parser_real.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_memo_generator.py
RENAMED
|
File without changes
|
{ynab_amazon_categorizer-2.2.0 → ynab_amazon_categorizer-2.2.2}/tests/test_transaction_matcher.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|