loyverse-sdk 0.1.0__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 (76) hide show
  1. loyverse_sdk-0.1.0/.gitignore +28 -0
  2. loyverse_sdk-0.1.0/.python-version +1 -0
  3. loyverse_sdk-0.1.0/LICENSE +21 -0
  4. loyverse_sdk-0.1.0/PKG-INFO +518 -0
  5. loyverse_sdk-0.1.0/README.md +489 -0
  6. loyverse_sdk-0.1.0/examples/duckdb_export.py +315 -0
  7. loyverse_sdk-0.1.0/pyproject.toml +66 -0
  8. loyverse_sdk-0.1.0/src/loyverse_sdk/__init__.py +4 -0
  9. loyverse_sdk-0.1.0/src/loyverse_sdk/auth.py +23 -0
  10. loyverse_sdk-0.1.0/src/loyverse_sdk/client.py +340 -0
  11. loyverse_sdk-0.1.0/src/loyverse_sdk/core/config.py +15 -0
  12. loyverse_sdk-0.1.0/src/loyverse_sdk/core/console.py +7 -0
  13. loyverse_sdk-0.1.0/src/loyverse_sdk/db/__init__.py +47 -0
  14. loyverse_sdk-0.1.0/src/loyverse_sdk/db/connection.py +217 -0
  15. loyverse_sdk-0.1.0/src/loyverse_sdk/db/converters.py +630 -0
  16. loyverse_sdk-0.1.0/src/loyverse_sdk/db/exporter.py +537 -0
  17. loyverse_sdk-0.1.0/src/loyverse_sdk/db/schema_builder.py +1017 -0
  18. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/__init__.py +16 -0
  19. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/base.py +29 -0
  20. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/categories.py +31 -0
  21. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/customers.py +30 -0
  22. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/devices.py +30 -0
  23. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/discounts.py +31 -0
  24. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/employees.py +24 -0
  25. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/inventory.py +33 -0
  26. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/items.py +34 -0
  27. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/merchant.py +13 -0
  28. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/mixins.py +204 -0
  29. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/modifiers.py +34 -0
  30. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/receipts.py +38 -0
  31. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/shifts.py +22 -0
  32. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/stores.py +22 -0
  33. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/suppliers.py +30 -0
  34. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/taxes.py +28 -0
  35. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/variants.py +28 -0
  36. loyverse_sdk-0.1.0/src/loyverse_sdk/endpoints/webhooks.py +21 -0
  37. loyverse_sdk-0.1.0/src/loyverse_sdk/exceptions.py +312 -0
  38. loyverse_sdk-0.1.0/src/loyverse_sdk/helpers.py +148 -0
  39. loyverse_sdk-0.1.0/src/loyverse_sdk/models/__init__.py +21 -0
  40. loyverse_sdk-0.1.0/src/loyverse_sdk/models/category.py +54 -0
  41. loyverse_sdk-0.1.0/src/loyverse_sdk/models/common.py +30 -0
  42. loyverse_sdk-0.1.0/src/loyverse_sdk/models/device.py +22 -0
  43. loyverse_sdk-0.1.0/src/loyverse_sdk/models/discount.py +54 -0
  44. loyverse_sdk-0.1.0/src/loyverse_sdk/models/inventory.py +28 -0
  45. loyverse_sdk-0.1.0/src/loyverse_sdk/models/item.py +48 -0
  46. loyverse_sdk-0.1.0/src/loyverse_sdk/models/merchant.py +29 -0
  47. loyverse_sdk-0.1.0/src/loyverse_sdk/models/modifier.py +21 -0
  48. loyverse_sdk-0.1.0/src/loyverse_sdk/models/receipt.py +124 -0
  49. loyverse_sdk-0.1.0/src/loyverse_sdk/models/shift.py +47 -0
  50. loyverse_sdk-0.1.0/src/loyverse_sdk/models/store.py +17 -0
  51. loyverse_sdk-0.1.0/src/loyverse_sdk/models/supplier.py +21 -0
  52. loyverse_sdk-0.1.0/src/loyverse_sdk/models/tax.py +15 -0
  53. loyverse_sdk-0.1.0/src/loyverse_sdk/models/user.py +62 -0
  54. loyverse_sdk-0.1.0/src/loyverse_sdk/models/variant.py +29 -0
  55. loyverse_sdk-0.1.0/src/loyverse_sdk/models/webhook.py +32 -0
  56. loyverse_sdk-0.1.0/src/loyverse_sdk/utils.py +7 -0
  57. loyverse_sdk-0.1.0/tests/integration/conftest.py +27 -0
  58. loyverse_sdk-0.1.0/tests/integration/db/test_full_export.py +306 -0
  59. loyverse_sdk-0.1.0/tests/integration/test_categories_endpoint.py +5 -0
  60. loyverse_sdk-0.1.0/tests/unit/conftest.py +7 -0
  61. loyverse_sdk-0.1.0/tests/unit/db/test_converters.py +635 -0
  62. loyverse_sdk-0.1.0/tests/unit/db/test_exporter.py +773 -0
  63. loyverse_sdk-0.1.0/tests/unit/db/test_schema_builder.py +439 -0
  64. loyverse_sdk-0.1.0/tests/unit/endpoints/__init__.py +0 -0
  65. loyverse_sdk-0.1.0/tests/unit/endpoints/test_inventory_endpoint.py +155 -0
  66. loyverse_sdk-0.1.0/tests/unit/models/test_category_model.py +37 -0
  67. loyverse_sdk-0.1.0/tests/unit/models/test_customer_model.py +62 -0
  68. loyverse_sdk-0.1.0/tests/unit/models/test_discount_model.py +88 -0
  69. loyverse_sdk-0.1.0/tests/unit/models/test_employee_model.py +48 -0
  70. loyverse_sdk-0.1.0/tests/unit/models/test_inventory_model.py +112 -0
  71. loyverse_sdk-0.1.0/tests/unit/models/test_item_model.py +67 -0
  72. loyverse_sdk-0.1.0/tests/unit/models/test_payment_type_model.py +52 -0
  73. loyverse_sdk-0.1.0/tests/unit/models/test_pos_device_model.py +56 -0
  74. loyverse_sdk-0.1.0/tests/unit/models/test_receipt_model.py +163 -0
  75. loyverse_sdk-0.1.0/tests/unit/models/test_shift_model.py +352 -0
  76. loyverse_sdk-0.1.0/uv.lock +634 -0
@@ -0,0 +1,28 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+
10
+ # Command runners
11
+ justfile
12
+ Makefile
13
+
14
+ # Virtual environments
15
+ .venv
16
+ .env
17
+
18
+ # Databases
19
+ *.sqlite*
20
+ *.db
21
+ *.duckdb
22
+
23
+ # Markdown files
24
+ TODO.md
25
+ CLAUDE.md
26
+ AGENTS.md
27
+
28
+ .planning/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 dagsdags212 <jegsamson.dev@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,518 @@
1
+ Metadata-Version: 2.4
2
+ Name: loyverse-sdk
3
+ Version: 0.1.0
4
+ Summary: Asynchronous Python SDK for the Loyverse API
5
+ Project-URL: Homepage, https://github.com/dagsdags212/loyverse_sdk
6
+ Project-URL: Repository, https://github.com/dagsdags212/loyverse_sdk
7
+ Project-URL: Issues, https://github.com/dagsdags212/loyverse_sdk/issues
8
+ Author-email: dagsdags212 <jegsamson.dev@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: api,async,loyverse,pos,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: duckdb>=1.1.0
20
+ Requires-Dist: httpx>=0.28.1
21
+ Requires-Dist: polars>=1.0.0
22
+ Requires-Dist: pyarrow>=15.0.0
23
+ Requires-Dist: pydantic-settings>=2.12.0
24
+ Requires-Dist: pydantic>=2.12.4
25
+ Requires-Dist: pytz>=2025.2
26
+ Requires-Dist: rich>=14.2.0
27
+ Requires-Dist: sqlmodel>=0.0.27
28
+ Description-Content-Type: text/markdown
29
+
30
+ # Loyverse SDK
31
+
32
+ Asynchronous Python SDK for the [Loyverse API](https://developer.loyverse.com/docs/), a point-of-sale (POS) system for managing business transactions, inventory, and customer data.
33
+
34
+ ## Overview
35
+
36
+ The SDK provides:
37
+ - **Async/await** interface using `httpx` for non-blocking API calls
38
+ - **Type-safe** request/response models using Pydantic
39
+ - **Automatic pagination** with cursor-based iteration via `iter_all()`
40
+ - **Full CRUD operations** for supported endpoints
41
+ - **16 endpoints**: categories, customers, discounts, devices, employees, inventory, items, merchant, modifiers, receipts, shifts, stores, suppliers, taxes, webhooks, variants
42
+
43
+ ### Codebase Structure
44
+
45
+ **`src/loyverse_sdk/`** contains:
46
+ - `client.py` - Main `LoyverseClient` class with endpoint access
47
+ - `endpoints/` - Endpoint classes using mixin pattern for CRUD operations
48
+ - `models/` - Pydantic models for request/response validation
49
+ - `auth.py` - Token-based authentication
50
+ - `core/` - Configuration, logging, and utilities
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ uv pip install git+https://github.com/dagsdags212/loyverse_sdk.git
56
+ ```
57
+
58
+ ## Setup
59
+
60
+ Set your API token as an environment variable:
61
+
62
+ ```bash
63
+ export LOYVERSE_API_TOKEN=your_api_token
64
+ ```
65
+
66
+ Or create a `.env` file in your project root:
67
+
68
+ ```env
69
+ LOYVERSE_API_TOKEN=your_api_token
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```python
75
+ import asyncio
76
+ from loyverse_sdk import LoyverseClient
77
+
78
+ async def main():
79
+ # Create client (automatically loads token from environment)
80
+ client = LoyverseClient()
81
+
82
+ # List customers
83
+ response = await client.customers.list(limit=10)
84
+ print(f"Found {len(response.items)} customers")
85
+
86
+ # Close connection
87
+ await client.close()
88
+
89
+ asyncio.run(main())
90
+ ```
91
+
92
+ ## Usage Examples
93
+
94
+ ### Customers Endpoint
95
+
96
+ The customers endpoint manages customer data from your POS system.
97
+
98
+ **List customers with pagination:**
99
+
100
+ ```python
101
+ # Get first page of customers
102
+ response = await client.customers.list(limit=50)
103
+
104
+ for customer in response.items:
105
+ print(f"{customer.name} - {customer.email}")
106
+ print(f" Total visits: {customer.total_visits}")
107
+ print(f" Total spent: ${customer.total_spent}")
108
+
109
+ # Get next page using cursor
110
+ if response.cursor:
111
+ next_page = await client.customers.list(limit=50, cursor=response.cursor)
112
+ ```
113
+
114
+ **Retrieve a single customer:**
115
+
116
+ ```python
117
+ customer = await client.customers.retrieve(id="customer-uuid-here")
118
+ print(customer.name)
119
+ print(customer.phone_number)
120
+ print(customer.address)
121
+ ```
122
+
123
+ **Create a new customer:**
124
+
125
+ ```python
126
+ new_customer = await client.customers.create({
127
+ "name": "Jane Smith",
128
+ "email": "jane@example.com",
129
+ "phone_number": "+1234567890",
130
+ "address": "123 Main St",
131
+ "city": "San Francisco",
132
+ "postal_code": "94102",
133
+ "customer_code": "CUST001"
134
+ })
135
+
136
+ print(f"Created customer: {new_customer.id}")
137
+ ```
138
+
139
+ **Update an existing customer:**
140
+
141
+ ```python
142
+ updated = await client.customers.update(
143
+ id=customer.id,
144
+ payload={"email": "newemail@example.com", "note": "VIP customer"}
145
+ )
146
+
147
+ print(f"Updated {updated.name}")
148
+ ```
149
+
150
+ **Delete a customer:**
151
+
152
+ ```python
153
+ result = await client.customers.delete(id=customer.id)
154
+ print(result) # {'deleted_object_ids': ['customer-uuid']}
155
+ ```
156
+
157
+ **Iterate through all customers:**
158
+
159
+ ```python
160
+ # Automatically handles pagination across all pages
161
+ async for customer in client.customers.iter_all():
162
+ print(f"{customer.name} - Last visit: {customer.last_visit}")
163
+ ```
164
+
165
+ **Filter customers by date:**
166
+
167
+ ```python
168
+ from datetime import datetime
169
+
170
+ # Get customers created in the last 30 days
171
+ start_date = datetime.now() - timedelta(days=30)
172
+
173
+ async for customer in client.customers.iter_all(created_at_min=start_date):
174
+ tenure = customer.tenure() # timedelta between first and last visit
175
+ print(f"{customer.name} - Customer for {tenure.days} days")
176
+ ```
177
+
178
+ ### Other Endpoints
179
+
180
+ All endpoints follow the same pattern. Available endpoints:
181
+
182
+ ```python
183
+ client.categories # Item categories
184
+ client.customers # Customer records
185
+ client.discounts # Discount rules
186
+ client.devices # POS devices
187
+ client.employees # Staff members
188
+ client.inventory # Stock levels
189
+ client.items # Inventory items
190
+ client.merchant # Merchant info
191
+ client.modifiers # Item modifiers
192
+ client.receipts # Transaction receipts
193
+ client.shifts # Employee shifts
194
+ client.stores # Store locations
195
+ client.suppliers # Supplier records
196
+ client.taxes # Tax configurations
197
+ client.variants # Item variants
198
+ client.webhooks # Webhook subscriptions
199
+ ```
200
+
201
+ Each endpoint supports operations based on the [Loyverse API capabilities](https://developer.loyverse.com/docs/).
202
+
203
+ ## DuckDB Export
204
+
205
+ The SDK includes powerful export functionality to save all your Loyverse data to a local DuckDB database for analytics, reporting, and data warehousing.
206
+
207
+ ### Why DuckDB?
208
+
209
+ DuckDB is an analytics-focused database perfect for:
210
+ - **Fast analytical queries** on large datasets
211
+ - **Local data warehousing** without server infrastructure
212
+ - **SQL analytics** with familiar syntax
213
+ - **Integration** with Python, R, and BI tools
214
+ - **Efficient storage** with columnar compression
215
+
216
+ ### Features
217
+
218
+ - ✅ **15 main resource tables** (categories, items, receipts, etc.)
219
+ - ✅ **Relational schema** with foreign keys and indexes
220
+ - ✅ **Junction tables** for many-to-many relationships
221
+ - ✅ **Child tables** for nested data (line items, modifier options)
222
+ - ✅ **Full and incremental exports** with date range filtering
223
+ - ✅ **Streaming export** for memory efficiency
224
+ - ✅ **UPSERT support** (INSERT OR REPLACE) to prevent duplicates
225
+ - ✅ **Progress tracking** with callback support
226
+
227
+ ### Quick Start
228
+
229
+ **Full export:**
230
+
231
+ ```python
232
+ import asyncio
233
+ from loyverse_sdk import LoyverseClient
234
+
235
+ async def main():
236
+ client = LoyverseClient()
237
+
238
+ # Export all data to DuckDB
239
+ counts = await client.export_to_duckdb("loyverse.duckdb")
240
+
241
+ print(f"Exported {sum(counts.values())} total records")
242
+ # Output: {'categories': 15, 'customers': 1250, 'receipts': 45000, ...}
243
+
244
+ await client.close()
245
+
246
+ asyncio.run(main())
247
+ ```
248
+
249
+ **Query exported data:**
250
+
251
+ ```python
252
+ import duckdb
253
+
254
+ conn = duckdb.connect("loyverse.duckdb")
255
+
256
+ # Top 10 customers by total spent
257
+ result = conn.execute("""
258
+ SELECT
259
+ c.name,
260
+ COUNT(DISTINCT r.id) as receipt_count,
261
+ SUM(r.total_amount) as total_spent
262
+ FROM customers c
263
+ JOIN receipts r ON c.id = r.customer_id
264
+ WHERE r.receipt_type = 'SALE'
265
+ GROUP BY c.id, c.name
266
+ ORDER BY total_spent DESC
267
+ LIMIT 10
268
+ """).fetchall()
269
+
270
+ conn.close()
271
+ ```
272
+
273
+ ### Export Methods
274
+
275
+ #### 1. Full Export with Options
276
+
277
+ Export all or selected resources with comprehensive filtering:
278
+
279
+ ```python
280
+ from datetime import datetime, timedelta
281
+
282
+ client = LoyverseClient()
283
+
284
+ # Export with all options
285
+ counts = await client.export_to_duckdb(
286
+ db_path="loyverse.duckdb",
287
+ resources=["receipts", "customers", "items"], # Optional: specific resources
288
+ created_at_min=datetime(2024, 1, 1), # Optional: start date
289
+ created_at_max=datetime(2024, 12, 31), # Optional: end date
290
+ updated_at_min=datetime.now() - timedelta(days=7), # Optional: updated after
291
+ batch_size=1000, # Optional: records per batch
292
+ progress_callback=lambda res, curr, total: print(f"{res}: {curr}"), # Optional
293
+ create_indexes=True # Optional: create indexes after
294
+ )
295
+
296
+ print(f"Exported: {counts}")
297
+ # Returns: {'receipts': 5000, 'customers': 1200, 'items': 350}
298
+
299
+ await client.close()
300
+ ```
301
+
302
+ #### 2. Single Resource Export
303
+
304
+ Export one resource with fine-grained control:
305
+
306
+ ```python
307
+ client = LoyverseClient()
308
+
309
+ # Export only receipts from last 30 days
310
+ count = await client.export_resource_to_duckdb(
311
+ resource_name="receipts",
312
+ db_path="loyverse.duckdb",
313
+ created_at_min=datetime.now() - timedelta(days=30)
314
+ )
315
+
316
+ print(f"Exported {count} receipts")
317
+
318
+ await client.close()
319
+ ```
320
+
321
+ #### 3. Schema Initialization
322
+
323
+ Create database schema without exporting data:
324
+
325
+ ```python
326
+ client = LoyverseClient()
327
+
328
+ # Initialize empty database with schema
329
+ client.init_duckdb_schema("loyverse.duckdb")
330
+
331
+ # Or reset existing database
332
+ client.init_duckdb_schema("loyverse.duckdb", drop_existing=True)
333
+ ```
334
+
335
+ ### Advanced Usage
336
+
337
+ **Progress tracking:**
338
+
339
+ ```python
340
+ def progress_callback(resource_name: str, current: int, total: int):
341
+ """Called for each batch of records."""
342
+ print(f"Exporting {resource_name}: {current:,} records processed...")
343
+
344
+ counts = await client.export_to_duckdb(
345
+ "loyverse.duckdb",
346
+ progress_callback=progress_callback
347
+ )
348
+ ```
349
+
350
+ **Incremental updates:**
351
+
352
+ ```python
353
+ # Export only records updated in last 24 hours
354
+ yesterday = datetime.now() - timedelta(days=1)
355
+
356
+ counts = await client.export_to_duckdb(
357
+ "loyverse.duckdb",
358
+ updated_at_min=yesterday
359
+ )
360
+
361
+ # UPSERT semantics: existing records are updated, new ones inserted
362
+ ```
363
+
364
+ **Selective export:**
365
+
366
+ ```python
367
+ # Export only what you need
368
+ counts = await client.export_to_duckdb(
369
+ "loyverse.duckdb",
370
+ resources=[
371
+ "receipts", # Transaction data
372
+ "customers", # Customer profiles
373
+ "items", # Product catalog
374
+ "categories" # Item categories
375
+ ]
376
+ )
377
+ ```
378
+
379
+ ### Database Schema
380
+
381
+ The exported database includes:
382
+
383
+ **Main Tables (15):**
384
+ - `categories` - Item categories
385
+ - `stores` - Store locations
386
+ - `suppliers` - Supplier records
387
+ - `taxes` - Tax configurations
388
+ - `modifiers` - Item modifiers
389
+ - `discounts` - Discount rules
390
+ - `employees` - Staff members
391
+ - `customers` - Customer records
392
+ - `pos_devices` - POS devices
393
+ - `payment_types` - Payment methods
394
+ - `items` - Inventory items
395
+ - `variants` - Item variants
396
+ - `receipts` - Transaction receipts
397
+ - `inventory` - Stock levels
398
+ - `merchant` - Merchant info
399
+
400
+ **Junction Tables (8):**
401
+ - `employee_store` - Employee-to-store assignments
402
+ - `item_tax` - Item-to-tax relationships
403
+ - `item_modifier` - Item-to-modifier relationships
404
+ - `modifier_store` - Modifier-to-store assignments
405
+ - `tax_store` - Tax-to-store assignments
406
+ - `discount_store` - Discount-to-store assignments
407
+ - `payment_type_store` - Payment type availability by store
408
+ - `variant_store` - Variant inventory by store
409
+
410
+ **Child Tables (2):**
411
+ - `receipt_line_items` - Individual line items per receipt
412
+ - `modifier_options` - Options within modifiers
413
+
414
+ **Metadata:**
415
+ - `sync_metadata` - Tracks export history and record counts
416
+
417
+ ### Example Queries
418
+
419
+ **Daily revenue:**
420
+
421
+ ```sql
422
+ SELECT
423
+ DATE(receipt_date) as date,
424
+ COUNT(*) as receipt_count,
425
+ SUM(total_amount) as revenue
426
+ FROM receipts
427
+ WHERE receipt_type = 'SALE'
428
+ AND receipt_date >= CURRENT_DATE - INTERVAL '30 days'
429
+ GROUP BY DATE(receipt_date)
430
+ ORDER BY date DESC;
431
+ ```
432
+
433
+ **Best-selling items:**
434
+
435
+ ```sql
436
+ SELECT
437
+ i.name,
438
+ SUM(l.quantity) as units_sold,
439
+ SUM(l.quantity * l.price) as revenue
440
+ FROM items i
441
+ JOIN receipt_line_items l ON i.id = l.item_id
442
+ JOIN receipts r ON l.receipt_id = r.id
443
+ WHERE r.receipt_type = 'SALE'
444
+ GROUP BY i.id, i.name
445
+ ORDER BY units_sold DESC
446
+ LIMIT 10;
447
+ ```
448
+
449
+ **Customer lifetime value:**
450
+
451
+ ```sql
452
+ SELECT
453
+ c.name,
454
+ c.total_visits,
455
+ c.total_spent,
456
+ c.total_spent / NULLIF(c.total_visits, 0) as avg_per_visit
457
+ FROM customers c
458
+ WHERE c.total_visits > 0
459
+ ORDER BY c.total_spent DESC
460
+ LIMIT 20;
461
+ ```
462
+
463
+ **Inventory by category:**
464
+
465
+ ```sql
466
+ SELECT
467
+ cat.name as category,
468
+ COUNT(DISTINCT i.id) as item_count,
469
+ COUNT(DISTINCT v.id) as variant_count
470
+ FROM categories cat
471
+ LEFT JOIN items i ON cat.id = i.category_id
472
+ LEFT JOIN variants v ON i.id = v.item_id
473
+ GROUP BY cat.id, cat.name
474
+ ORDER BY item_count DESC;
475
+ ```
476
+
477
+ ### Performance Tips
478
+
479
+ 1. **Batch size**: Default is 1000 records per transaction. Increase for faster exports on powerful machines:
480
+ ```python
481
+ counts = await client.export_to_duckdb("loyverse.duckdb", batch_size=5000)
482
+ ```
483
+
484
+ 2. **Indexes**: Created automatically after export. Disable for faster initial load:
485
+ ```python
486
+ counts = await client.export_to_duckdb("loyverse.duckdb", create_indexes=False)
487
+ ```
488
+
489
+ 3. **Memory**: DuckDB is configured with 4GB memory limit by default. Efficient for datasets with millions of records.
490
+
491
+ 4. **Incremental updates**: Export only changed records to minimize transfer time:
492
+ ```python
493
+ # Daily sync: export only yesterday's data
494
+ yesterday = datetime.now() - timedelta(days=1)
495
+ counts = await client.export_to_duckdb("loyverse.duckdb", created_at_min=yesterday)
496
+ ```
497
+
498
+ ### Use Cases
499
+
500
+ - **Business Intelligence**: Connect DuckDB to Metabase, Superset, or Tableau
501
+ - **Custom Reports**: Write SQL queries for specific business questions
502
+ - **Data Science**: Analyze sales patterns, customer behavior, inventory trends
503
+ - **Backup**: Maintain local copy of all POS data
504
+ - **Data Warehouse**: Centralize data for cross-system analytics
505
+ - **Migration**: Export data for migration to other systems
506
+
507
+ ### Complete Example
508
+
509
+ See `examples/duckdb_export.py` for comprehensive examples including:
510
+ - Full and selective exports
511
+ - Date range filtering
512
+ - Progress tracking
513
+ - Querying exported data
514
+ - Incremental updates
515
+
516
+ ```bash
517
+ python examples/duckdb_export.py
518
+ ```