continentept-mcp 0.4.0 → 0.6.0

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,39 @@ All notable changes to this project are documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.0] - 2026-10-15
10
+
11
+ ### Changed
12
+
13
+ - **Breaking:** `get_cart` returns `totals` instead of `total`: `products`, `bags`, `deposit`, `delivery`, `discounts` and `grandTotal`, the amount to pay. The old `total` left out the bags, so it was less than the amount to pay.
14
+ - **Breaking:** cart line prices are `{ amount, per }` price tags, like catalogue prices, plus a `lineTotal`. Products sold by the piece but priced by weight were shown as a price "each" when it was the price per kg.
15
+
16
+ ### Added
17
+
18
+ - Cart lines have a `kind`, `bag` for the shopping bags the website adds by itself and `product` for everything else.
19
+ - `search_products` and `get_product` return `recommendedPrice`, the recommended retail price (PVPR) the website shows beside the price on many promotions, so assistants can compare it with the selling price. ([#4](https://github.com/andredelgado/continente-mcp/pull/4), thanks [@Lozano-Miguel](https://github.com/Lozano-Miguel))
20
+
21
+ ### Fixed
22
+
23
+ - Products sold by the piece but priced per kilogram (fruit, vegetables, meat, fish, counter cheese and ham) had no sale rules, so they could not be added to the cart. Their maximum is now read in kilograms, like the minimum and step, and converted to whole pieces.
24
+ - Cart quantities now have a unit. They were all reported as `unknown`; each line's unit now comes from its own sale rules, so a weighted line reads as pieces. A cart change is no longer confirmed by a line that reports another unit.
25
+
26
+ ## [0.5.0] - 2026-10-15
27
+
28
+ ### Fixed
29
+
30
+ - Searching for produce, meat and other products priced per kilogram failed with "Invalid money amount". Prices now record what they are for: per item, per piece, per kg or per litre.
31
+ - One unreadable search result no longer fails the whole search; the other results are returned as a partial result, and the unreadable one is described.
32
+ - Search results keep the website's relevance order. Products whose names merely resembled a favorite no longer jump ahead of better matches; exact favorites still come first.
33
+ - Favorites priced per kilogram are read correctly, and an unrecognised favorite price is reported as unknown instead of failing the refresh.
34
+
35
+ ### Changed
36
+
37
+ - **Breaking:** catalogue prices (`price` in search, favorites and `get_product`) are now `{ amount, per }`. `unitPriceText` is replaced by `secondaryPrice`, and the search field `similarity` is removed. Saved favorites are fetched again once after updating.
38
+ - Search results include `packageText`, the pack size as shown on the website.
39
+ - Sign-in errors say whether no credentials are configured or Continente did not accept them, with the next step for each case. The instructions present the `basket-planner` prompt as optional, for clients that support prompts.
40
+ - Browser engine updated to Playwright 1.64 (Chrome 156). After updating, the new Chromium downloads automatically on first use (about 150 MB, once).
41
+
9
42
  ## [0.4.0] - 2026-10-15
10
43
 
11
44
  ### Added
@@ -55,7 +88,9 @@ All notable changes to this project are documented here. The format follows
55
88
  - Automatic sign-in from environment variables or a private credentials file, and a `login` command.
56
89
  - The `basket-planner` skill for planning a shopping list and updating the basket.
57
90
 
58
- [Unreleased]: https://github.com/andredelgado/continente-mcp/compare/v0.4.0...HEAD
91
+ [Unreleased]: https://github.com/andredelgado/continente-mcp/compare/v0.6.0...HEAD
92
+ [0.6.0]: https://github.com/andredelgado/continente-mcp/compare/v0.5.0...v0.6.0
93
+ [0.5.0]: https://github.com/andredelgado/continente-mcp/compare/v0.4.0...v0.5.0
59
94
  [0.4.0]: https://github.com/andredelgado/continente-mcp/compare/v0.3.1...v0.4.0
60
95
  [0.3.1]: https://github.com/andredelgado/continente-mcp/compare/v0.3.0...v0.3.1
61
96
  [0.3.0]: https://github.com/andredelgado/continente-mcp/compare/v0.2.0...v0.3.0
package/README.md CHANGED
@@ -119,7 +119,7 @@ Continente cart"* or *"What do I usually buy? Build me a list for this week."*
119
119
  | `search_products` | Search the catalogue; favorites rank first |
120
120
  | `get_product` | Price, availability and sale rules for one product |
121
121
  | `list_favorites` | Your saved favorites (`refresh: true` fetches the latest) |
122
- | `get_cart` | Cart lines, quantities and total |
122
+ | `get_cart` | Cart lines, quantities, bags and totals |
123
123
  | `add_cart_item` | Add an amount on top of what is in the cart |
124
124
  | `set_cart_item_quantity` | Make the cart hold exactly a quantity; zero removes |
125
125
  | `remove_cart_item` | Remove a line from the cart |
@@ -93,7 +93,7 @@ export class CatalogClient {
93
93
  break;
94
94
  }
95
95
  const previousCount = ids.size;
96
- parsed.value.forEach(product => ids.add(product.productId));
96
+ parsed.value.products.forEach(product => ids.add(product.productId));
97
97
  pages.push(html);
98
98
  reportProgress(pages.length, 5, 'Reading catalogue pages');
99
99
  const continuation = parseSearchPagination(html);
@@ -4,8 +4,16 @@ export declare const FavoriteProductSchema: z.ZodObject<{
4
4
  productSlug: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ProductSlug", "out">>;
5
5
  name: z.ZodString;
6
6
  price: z.ZodNullable<z.ZodObject<{
7
- currency: z.ZodLiteral<"EUR">;
8
- minorUnits: z.ZodNumber;
7
+ amount: z.ZodObject<{
8
+ currency: z.ZodLiteral<"EUR">;
9
+ minorUnits: z.ZodNumber;
10
+ }, z.core.$strict>;
11
+ per: z.ZodEnum<{
12
+ item: "item";
13
+ kg: "kg";
14
+ l: "l";
15
+ piece: "piece";
16
+ }>;
9
17
  }, z.core.$strict>>;
10
18
  url: z.ZodURL;
11
19
  }, z.core.$strict>;
@@ -15,11 +23,43 @@ export declare const ProductSchema: z.ZodObject<{
15
23
  productSlug: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ProductSlug", "out">>;
16
24
  name: z.ZodString;
17
25
  price: z.ZodNullable<z.ZodObject<{
18
- currency: z.ZodLiteral<"EUR">;
19
- minorUnits: z.ZodNumber;
26
+ amount: z.ZodObject<{
27
+ currency: z.ZodLiteral<"EUR">;
28
+ minorUnits: z.ZodNumber;
29
+ }, z.core.$strict>;
30
+ per: z.ZodEnum<{
31
+ item: "item";
32
+ kg: "kg";
33
+ l: "l";
34
+ piece: "piece";
35
+ }>;
20
36
  }, z.core.$strict>>;
21
37
  url: z.ZodURL;
22
- unitPriceText: z.ZodNullable<z.ZodString>;
38
+ secondaryPrice: z.ZodNullable<z.ZodObject<{
39
+ amount: z.ZodObject<{
40
+ currency: z.ZodLiteral<"EUR">;
41
+ minorUnits: z.ZodNumber;
42
+ }, z.core.$strict>;
43
+ per: z.ZodEnum<{
44
+ item: "item";
45
+ kg: "kg";
46
+ l: "l";
47
+ piece: "piece";
48
+ }>;
49
+ }, z.core.$strict>>;
50
+ packageText: z.ZodNullable<z.ZodString>;
51
+ recommendedPrice: z.ZodNullable<z.ZodObject<{
52
+ amount: z.ZodObject<{
53
+ currency: z.ZodLiteral<"EUR">;
54
+ minorUnits: z.ZodNumber;
55
+ }, z.core.$strict>;
56
+ per: z.ZodEnum<{
57
+ item: "item";
58
+ kg: "kg";
59
+ l: "l";
60
+ piece: "piece";
61
+ }>;
62
+ }, z.core.$strict>>;
23
63
  minimumText: z.ZodNullable<z.ZodString>;
24
64
  }, z.core.$strict>;
25
65
  export type Product = z.infer<typeof ProductSchema>;
@@ -27,6 +67,10 @@ export declare const CartItemSchema: z.ZodObject<{
27
67
  productId: z.core.$ZodBranded<z.ZodString, "ProductId", "out">;
28
68
  cartItemId: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "CartItemId", "out">>;
29
69
  name: z.ZodString;
70
+ kind: z.ZodEnum<{
71
+ bag: "bag";
72
+ product: "product";
73
+ }>;
30
74
  quantity: z.ZodDiscriminatedUnion<[z.ZodObject<{
31
75
  status: z.ZodLiteral<"known">;
32
76
  quantity: z.ZodObject<{
@@ -50,25 +94,62 @@ export declare const CartItemSchema: z.ZodObject<{
50
94
  unsupported_unit: "unsupported_unit";
51
95
  }>>;
52
96
  }, z.core.$strict>], "status">;
53
- price: z.ZodObject<{
54
- amount: z.ZodNullable<z.ZodObject<{
97
+ price: z.ZodNullable<z.ZodObject<{
98
+ amount: z.ZodObject<{
55
99
  currency: z.ZodLiteral<"EUR">;
56
100
  minorUnits: z.ZodNumber;
57
- }, z.core.$strict>>;
58
- basis: z.ZodEnum<{
59
- line: "line";
60
- unit: "unit";
61
- unknown: "unknown";
101
+ }, z.core.$strict>;
102
+ per: z.ZodEnum<{
103
+ item: "item";
104
+ kg: "kg";
105
+ l: "l";
106
+ piece: "piece";
62
107
  }>;
63
- }, z.core.$strict>;
108
+ }, z.core.$strict>>;
109
+ lineTotal: z.ZodNullable<z.ZodObject<{
110
+ currency: z.ZodLiteral<"EUR">;
111
+ minorUnits: z.ZodNumber;
112
+ }, z.core.$strict>>;
64
113
  }, z.core.$strict>;
65
114
  export type CartItem = z.infer<typeof CartItemSchema>;
115
+ /** Each amount as the website reports it, or null when it reports none. */
116
+ export declare const CartTotalsSchema: z.ZodObject<{
117
+ products: z.ZodNullable<z.ZodObject<{
118
+ currency: z.ZodLiteral<"EUR">;
119
+ minorUnits: z.ZodNumber;
120
+ }, z.core.$strict>>;
121
+ bags: z.ZodNullable<z.ZodObject<{
122
+ currency: z.ZodLiteral<"EUR">;
123
+ minorUnits: z.ZodNumber;
124
+ }, z.core.$strict>>;
125
+ deposit: z.ZodNullable<z.ZodObject<{
126
+ currency: z.ZodLiteral<"EUR">;
127
+ minorUnits: z.ZodNumber;
128
+ }, z.core.$strict>>;
129
+ delivery: z.ZodNullable<z.ZodObject<{
130
+ currency: z.ZodLiteral<"EUR">;
131
+ minorUnits: z.ZodNumber;
132
+ }, z.core.$strict>>;
133
+ discounts: z.ZodNullable<z.ZodObject<{
134
+ currency: z.ZodLiteral<"EUR">;
135
+ minorUnits: z.ZodNumber;
136
+ }, z.core.$strict>>;
137
+ grandTotal: z.ZodNullable<z.ZodObject<{
138
+ currency: z.ZodLiteral<"EUR">;
139
+ minorUnits: z.ZodNumber;
140
+ }, z.core.$strict>>;
141
+ }, z.core.$strict>;
142
+ export type CartTotals = z.infer<typeof CartTotalsSchema>;
66
143
  export declare const CartSchema: z.ZodObject<{
67
144
  customerAuthenticated: z.ZodBoolean;
68
145
  items: z.ZodArray<z.ZodObject<{
69
146
  productId: z.core.$ZodBranded<z.ZodString, "ProductId", "out">;
70
147
  cartItemId: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "CartItemId", "out">>;
71
148
  name: z.ZodString;
149
+ kind: z.ZodEnum<{
150
+ bag: "bag";
151
+ product: "product";
152
+ }>;
72
153
  quantity: z.ZodDiscriminatedUnion<[z.ZodObject<{
73
154
  status: z.ZodLiteral<"known">;
74
155
  quantity: z.ZodObject<{
@@ -92,22 +173,49 @@ export declare const CartSchema: z.ZodObject<{
92
173
  unsupported_unit: "unsupported_unit";
93
174
  }>>;
94
175
  }, z.core.$strict>], "status">;
95
- price: z.ZodObject<{
96
- amount: z.ZodNullable<z.ZodObject<{
176
+ price: z.ZodNullable<z.ZodObject<{
177
+ amount: z.ZodObject<{
97
178
  currency: z.ZodLiteral<"EUR">;
98
179
  minorUnits: z.ZodNumber;
99
- }, z.core.$strict>>;
100
- basis: z.ZodEnum<{
101
- line: "line";
102
- unit: "unit";
103
- unknown: "unknown";
180
+ }, z.core.$strict>;
181
+ per: z.ZodEnum<{
182
+ item: "item";
183
+ kg: "kg";
184
+ l: "l";
185
+ piece: "piece";
104
186
  }>;
105
- }, z.core.$strict>;
106
- }, z.core.$strict>>;
107
- total: z.ZodNullable<z.ZodObject<{
108
- currency: z.ZodLiteral<"EUR">;
109
- minorUnits: z.ZodNumber;
187
+ }, z.core.$strict>>;
188
+ lineTotal: z.ZodNullable<z.ZodObject<{
189
+ currency: z.ZodLiteral<"EUR">;
190
+ minorUnits: z.ZodNumber;
191
+ }, z.core.$strict>>;
110
192
  }, z.core.$strict>>;
193
+ totals: z.ZodObject<{
194
+ products: z.ZodNullable<z.ZodObject<{
195
+ currency: z.ZodLiteral<"EUR">;
196
+ minorUnits: z.ZodNumber;
197
+ }, z.core.$strict>>;
198
+ bags: z.ZodNullable<z.ZodObject<{
199
+ currency: z.ZodLiteral<"EUR">;
200
+ minorUnits: z.ZodNumber;
201
+ }, z.core.$strict>>;
202
+ deposit: z.ZodNullable<z.ZodObject<{
203
+ currency: z.ZodLiteral<"EUR">;
204
+ minorUnits: z.ZodNumber;
205
+ }, z.core.$strict>>;
206
+ delivery: z.ZodNullable<z.ZodObject<{
207
+ currency: z.ZodLiteral<"EUR">;
208
+ minorUnits: z.ZodNumber;
209
+ }, z.core.$strict>>;
210
+ discounts: z.ZodNullable<z.ZodObject<{
211
+ currency: z.ZodLiteral<"EUR">;
212
+ minorUnits: z.ZodNumber;
213
+ }, z.core.$strict>>;
214
+ grandTotal: z.ZodNullable<z.ZodObject<{
215
+ currency: z.ZodLiteral<"EUR">;
216
+ minorUnits: z.ZodNumber;
217
+ }, z.core.$strict>>;
218
+ }, z.core.$strict>;
111
219
  }, z.core.$strict>;
112
220
  export type Cart = z.infer<typeof CartSchema>;
113
221
  export declare const OrderLineSchema: z.ZodObject<{
@@ -227,7 +335,7 @@ export declare const PurchaseFrequencySchema: z.ZodObject<{
227
335
  }, z.core.$strict>;
228
336
  export type PurchaseFrequency = z.infer<typeof PurchaseFrequencySchema>;
229
337
  export declare const FavoritesCacheSchema: z.ZodObject<{
230
- schemaVersion: z.ZodLiteral<1>;
338
+ schemaVersion: z.ZodLiteral<2>;
231
339
  accountScope: z.ZodString;
232
340
  fetchedAt: z.ZodISODateTime;
233
341
  favorites: z.ZodArray<z.ZodObject<{
@@ -235,8 +343,16 @@ export declare const FavoritesCacheSchema: z.ZodObject<{
235
343
  productSlug: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ProductSlug", "out">>;
236
344
  name: z.ZodString;
237
345
  price: z.ZodNullable<z.ZodObject<{
238
- currency: z.ZodLiteral<"EUR">;
239
- minorUnits: z.ZodNumber;
346
+ amount: z.ZodObject<{
347
+ currency: z.ZodLiteral<"EUR">;
348
+ minorUnits: z.ZodNumber;
349
+ }, z.core.$strict>;
350
+ per: z.ZodEnum<{
351
+ item: "item";
352
+ kg: "kg";
353
+ l: "l";
354
+ piece: "piece";
355
+ }>;
240
356
  }, z.core.$strict>>;
241
357
  url: z.ZodURL;
242
358
  }, z.core.$strict>>;
@@ -248,18 +364,42 @@ export declare const ProductDetailsSchema: z.ZodObject<{
248
364
  productSlug: z.ZodNullable<z.core.$ZodBranded<z.ZodString, "ProductSlug", "out">>;
249
365
  name: z.ZodString;
250
366
  url: z.ZodURL;
251
- price: z.ZodObject<{
252
- amount: z.ZodNullable<z.ZodObject<{
367
+ price: z.ZodNullable<z.ZodObject<{
368
+ amount: z.ZodObject<{
253
369
  currency: z.ZodLiteral<"EUR">;
254
370
  minorUnits: z.ZodNumber;
255
- }, z.core.$strict>>;
256
- basis: z.ZodEnum<{
257
- line: "line";
258
- unit: "unit";
259
- unknown: "unknown";
371
+ }, z.core.$strict>;
372
+ per: z.ZodEnum<{
373
+ item: "item";
374
+ kg: "kg";
375
+ l: "l";
376
+ piece: "piece";
260
377
  }>;
261
- }, z.core.$strict>;
262
- unitPriceText: z.ZodNullable<z.ZodString>;
378
+ }, z.core.$strict>>;
379
+ secondaryPrice: z.ZodNullable<z.ZodObject<{
380
+ amount: z.ZodObject<{
381
+ currency: z.ZodLiteral<"EUR">;
382
+ minorUnits: z.ZodNumber;
383
+ }, z.core.$strict>;
384
+ per: z.ZodEnum<{
385
+ item: "item";
386
+ kg: "kg";
387
+ l: "l";
388
+ piece: "piece";
389
+ }>;
390
+ }, z.core.$strict>>;
391
+ recommendedPrice: z.ZodNullable<z.ZodObject<{
392
+ amount: z.ZodObject<{
393
+ currency: z.ZodLiteral<"EUR">;
394
+ minorUnits: z.ZodNumber;
395
+ }, z.core.$strict>;
396
+ per: z.ZodEnum<{
397
+ item: "item";
398
+ kg: "kg";
399
+ l: "l";
400
+ piece: "piece";
401
+ }>;
402
+ }, z.core.$strict>>;
263
403
  availability: z.ZodEnum<{
264
404
  available: "available";
265
405
  unavailable: "unavailable";
@@ -1,29 +1,54 @@
1
1
  import { z } from 'zod';
2
2
  import { ToolErrorSchema } from './errors.js';
3
3
  import { CartItemIdSchema, OrderIdSchema, OrderNumberSchema, ProductIdSchema, ProductReferenceSchema } from './identity.js';
4
- import { MoneySchema, QuotedMoneySchema } from './money.js';
4
+ import { MoneySchema, PriceTagSchema } from './money.js';
5
5
  import { ObservedQuantitySchema, QuantitySchema, ProductSaleRulesSchema } from './quantity.js';
6
6
  const productDetails = {
7
7
  name: z.string().trim().min(1).max(250),
8
- price: MoneySchema.nullable(),
8
+ /** The main price as displayed; null when absent or unrecognised. */
9
+ price: PriceTagSchema.nullable(),
9
10
  url: z.url(),
10
11
  };
11
12
  export const FavoriteProductSchema = ProductReferenceSchema.safeExtend(productDetails);
12
13
  export const ProductSchema = FavoriteProductSchema.safeExtend({
13
- unitPriceText: z.string().nullable(),
14
+ /** The comparison price shown next to the main one, such as per kg or per piece. */
15
+ secondaryPrice: PriceTagSchema.nullable(),
16
+ /** Pack size as displayed, such as `940 gr`, `2 x 52 gr` or `1,2 kg (aprox.)`. */
17
+ packageText: z.string().nullable(),
18
+ /** The recommended retail price (PVPR) shown beside a promotional price; null when the website shows none. */
19
+ recommendedPrice: PriceTagSchema.nullable(),
14
20
  minimumText: z.string().nullable(),
15
21
  });
16
22
  export const CartItemSchema = z.strictObject({
17
23
  productId: ProductIdSchema,
18
24
  cartItemId: CartItemIdSchema.nullable(),
19
25
  name: z.string().trim().min(1),
26
+ /** `bag` for the shopping bags the website adds to the cart on its own; `product` for everything else. */
27
+ kind: z.enum(['product', 'bag']),
20
28
  quantity: ObservedQuantitySchema,
21
- price: QuotedMoneySchema,
29
+ /** Unit price with what it is for: per item, or per kg for products sold by the piece but priced by weight. */
30
+ price: PriceTagSchema.nullable(),
31
+ /** The line's total as the website reports it. */
32
+ lineTotal: MoneySchema.nullable(),
33
+ });
34
+ /** Each amount as the website reports it, or null when it reports none. */
35
+ export const CartTotalsSchema = z.strictObject({
36
+ /** Product lines only, without bags or deposit. */
37
+ products: MoneySchema.nullable(),
38
+ /** The bag lines. */
39
+ bags: MoneySchema.nullable(),
40
+ /** Bottle and can deposit (SDR). */
41
+ deposit: MoneySchema.nullable(),
42
+ delivery: MoneySchema.nullable(),
43
+ /** Amount taken off by order and delivery discounts. */
44
+ discounts: MoneySchema.nullable(),
45
+ /** The amount to pay as the website shows it; it includes the bags. */
46
+ grandTotal: MoneySchema.nullable(),
22
47
  });
23
48
  export const CartSchema = z.strictObject({
24
49
  customerAuthenticated: z.boolean(),
25
50
  items: z.array(CartItemSchema),
26
- total: MoneySchema.nullable(),
51
+ totals: CartTotalsSchema,
27
52
  });
28
53
  export const OrderLineSchema = z.strictObject({
29
54
  productId: ProductIdSchema.nullable(),
@@ -51,15 +76,15 @@ export const PurchaseFrequencySchema = z.strictObject({
51
76
  }).refine(frequency => (frequency.identityBasis === 'product') === (frequency.productId !== null), { message: 'Identity basis must agree with the available product ID' })
52
77
  .refine(frequency => new Set(frequency.quantities.map(quantity => quantity.unit)).size === frequency.quantities.length, { message: 'Purchase totals must contain each unit only once' });
53
78
  export const FavoritesCacheSchema = z.strictObject({
54
- schemaVersion: z.literal(1),
79
+ schemaVersion: z.literal(2),
55
80
  accountScope: z.string().trim().min(1).max(128),
56
81
  fetchedAt: z.iso.datetime({ offset: true }),
57
82
  favorites: z.array(FavoriteProductSchema),
58
83
  completeness: z.literal('complete').optional(),
59
84
  });
60
85
  export const ProductDetailsSchema = ProductReferenceSchema.safeExtend({
61
- name: productDetails.name, url: productDetails.url, price: QuotedMoneySchema,
62
- unitPriceText: z.string().nullable(), availability: z.enum(['available', 'unavailable', 'unknown']),
86
+ name: productDetails.name, url: productDetails.url, price: PriceTagSchema.nullable(),
87
+ secondaryPrice: PriceTagSchema.nullable(), recommendedPrice: PriceTagSchema.nullable(), availability: z.enum(['available', 'unavailable', 'unknown']),
63
88
  saleRules: z.discriminatedUnion('status', [
64
89
  z.strictObject({ status: z.literal('available'), rules: ProductSaleRulesSchema }),
65
90
  z.strictObject({ status: z.literal('unavailable'), error: ToolErrorSchema }),
@@ -6,18 +6,24 @@ export declare const MoneySchema: z.ZodObject<{
6
6
  minorUnits: z.ZodNumber;
7
7
  }, z.core.$strict>;
8
8
  export type Money = z.infer<typeof MoneySchema>;
9
- /** The source of a price determines its basis; a line total is not a unit price. */
10
- export declare const QuotedMoneySchema: z.ZodObject<{
11
- amount: z.ZodNullable<z.ZodObject<{
9
+ /** Convert decimal text through integer arithmetic; never round an unrecognized precision. */
10
+ export declare function parseMoneyAmount(input: unknown): Result<Money | null, ToolError>;
11
+ /**
12
+ * What a displayed price is for. The storefront shows packaged goods per `item` (one pack or
13
+ * article), produce and meat per `kg`, drinks per `l`, and an approximate price per `piece`.
14
+ */
15
+ export declare const PriceTagSchema: z.ZodObject<{
16
+ amount: z.ZodObject<{
12
17
  currency: z.ZodLiteral<"EUR">;
13
18
  minorUnits: z.ZodNumber;
14
- }, z.core.$strict>>;
15
- basis: z.ZodEnum<{
16
- line: "line";
17
- unit: "unit";
18
- unknown: "unknown";
19
+ }, z.core.$strict>;
20
+ per: z.ZodEnum<{
21
+ item: "item";
22
+ kg: "kg";
23
+ l: "l";
24
+ piece: "piece";
19
25
  }>;
20
26
  }, z.core.$strict>;
21
- export type QuotedMoney = z.infer<typeof QuotedMoneySchema>;
22
- /** Convert decimal text through integer arithmetic; never round an unrecognized precision. */
23
- export declare function parseMoneyAmount(input: unknown): Result<Money | null, ToolError>;
27
+ export type PriceTag = z.infer<typeof PriceTagSchema>;
28
+ /** Parse labels such as `1,29€`, `1,19€/kg` or `0,24€/un`. An empty label is an unknown price. */
29
+ export declare function parsePriceTag(input: unknown): Result<PriceTag | null, ToolError>;
@@ -4,11 +4,6 @@ export const MoneySchema = z.strictObject({
4
4
  currency: z.literal('EUR'),
5
5
  minorUnits: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
6
6
  });
7
- /** The source of a price determines its basis; a line total is not a unit price. */
8
- export const QuotedMoneySchema = z.strictObject({
9
- amount: MoneySchema.nullable(),
10
- basis: z.enum(['unit', 'line', 'unknown']),
11
- });
12
7
  /** Convert decimal text through integer arithmetic; never round an unrecognized precision. */
13
8
  export function parseMoneyAmount(input) {
14
9
  if (input === null || input === undefined || input === '')
@@ -28,3 +23,28 @@ export function parseMoneyAmount(input) {
28
23
  }
29
24
  return { ok: true, value: { currency: 'EUR', minorUnits: Number(minorUnits) } };
30
25
  }
26
+ /**
27
+ * What a displayed price is for. The storefront shows packaged goods per `item` (one pack or
28
+ * article), produce and meat per `kg`, drinks per `l`, and an approximate price per `piece`.
29
+ */
30
+ export const PriceTagSchema = z.strictObject({
31
+ amount: MoneySchema,
32
+ per: z.enum(['item', 'piece', 'kg', 'l']),
33
+ });
34
+ const PRICE_UNITS = { kg: 'kg', l: 'l', lt: 'l', un: 'piece' };
35
+ /** Parse labels such as `1,29€`, `1,19€/kg` or `0,24€/un`. An empty label is an unknown price. */
36
+ export function parsePriceTag(input) {
37
+ if (input === null || input === undefined)
38
+ return { ok: true, value: null };
39
+ if (typeof input !== 'string')
40
+ return { ok: false, error: invalidData('price') };
41
+ const text = input.replace(/\s+/g, '');
42
+ if (!text)
43
+ return { ok: true, value: null };
44
+ const match = /^(\d+(?:[.,]\d{1,2})?)€(?:\/([a-z]+))?$/i.exec(text);
45
+ const per = match ? PRICE_UNITS[(match[2] ?? '').toLowerCase()] ?? (match[2] === undefined ? 'item' : undefined) : undefined;
46
+ const amount = match && per ? parseMoneyAmount(match[1]) : null;
47
+ if (!amount?.ok || !amount.value || !per)
48
+ return { ok: false, error: { ...invalidData('price'), message: 'Unrecognised price format' } };
49
+ return { ok: true, value: { amount: amount.value, per } };
50
+ }