tabscanner 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 94b707ecbc035054b09ece9560c43ab50cc807742dc0c3b822934d1b8a5e8c00
4
+ data.tar.gz: 88adeaa46a2717c71cd4843466b6addb01da8c766c09a30013450c9f45c0fe34
5
+ SHA512:
6
+ metadata.gz: e03197b23de85a72d1d2417e66875fa047e92f106e8d343c3326158ca240bc8aebab15c389dd71257607f7b9a7c9656b55c63f8e53e27d41b69aa0e8fea88996
7
+ data.tar.gz: 186b1b74b5f5881422b575e12f78bc62c4cc1edd41e56fe4ea82691469fccf41cef103f19bbeb15e899cc58e03e3723d905ca4d75e2894a4a63ec13528b84be6
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/README.md ADDED
@@ -0,0 +1,582 @@
1
+ # Tabscanner
2
+
3
+ A Ruby gem for processing receipt images using the Tabscanner API. Extract structured data from receipt images with just a few lines of code.
4
+
5
+ ## Features
6
+
7
+ - 📸 **Image Processing**: Submit receipt images via file path or IO stream
8
+ - 🔄 **Automatic Polling**: Built-in polling for processing results with timeout handling
9
+ - 💳 **Credit Monitoring**: Check remaining API credits to stay within plan limits
10
+ - 🛡️ **Error Handling**: Comprehensive error handling with detailed debug information
11
+ - 🔧 **Configurable**: Environment variables and programmatic configuration
12
+ - 🐛 **Debug Support**: Optional debug logging for troubleshooting
13
+ - ⚡ **Simple API**: Complete workflow in under 10 lines of code
14
+
15
+ ## Installation
16
+
17
+ ### Using Bundler
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'tabscanner'
23
+ ```
24
+
25
+ Then execute:
26
+
27
+ ```bash
28
+ bundle install
29
+ ```
30
+
31
+ ### Manual Installation
32
+
33
+ Install the gem directly:
34
+
35
+ ```bash
36
+ gem install tabscanner
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ Here's a complete example that processes a receipt in under 10 lines:
42
+
43
+ ```ruby
44
+ require 'tabscanner'
45
+
46
+ # Configure
47
+ Tabscanner.configure do |config|
48
+ config.api_key = 'your_api_key_here'
49
+ end
50
+
51
+ # Process receipt
52
+ token = Tabscanner.submit_receipt('receipt.jpg')
53
+ result = Tabscanner.get_result(token)
54
+
55
+ puts "Merchant: #{result['merchant']}"
56
+ puts "Total: $#{result['total']}"
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ ### Environment Variables
62
+
63
+ The simplest way to configure the gem is using environment variables:
64
+
65
+ ```bash
66
+ export TABSCANNER_API_KEY=your_api_key_here
67
+ export TABSCANNER_REGION=us # Optional, defaults to 'us'
68
+ export TABSCANNER_BASE_URL=https://custom.api.url # Optional, for staging/testing
69
+ export TABSCANNER_DEBUG=true # Optional, enables debug logging
70
+ ```
71
+
72
+ ### Programmatic Configuration
73
+
74
+ ```ruby
75
+ Tabscanner.configure do |config|
76
+ config.api_key = 'your_api_key_here'
77
+ config.region = 'us' # Optional
78
+ config.base_url = 'https://api.tabscanner.com' # Optional
79
+ config.debug = false # Optional, enables debug logging
80
+ config.logger = Logger.new(STDOUT) # Optional, custom logger
81
+ end
82
+ ```
83
+
84
+ ### Configuration Examples
85
+
86
+ #### Production Configuration
87
+ ```ruby
88
+ Tabscanner.configure do |config|
89
+ config.api_key = ENV['TABSCANNER_API_KEY']
90
+ config.region = 'us'
91
+ config.debug = false
92
+ end
93
+ ```
94
+
95
+ #### Development Configuration
96
+ ```ruby
97
+ Tabscanner.configure do |config|
98
+ config.api_key = ENV['TABSCANNER_API_KEY']
99
+ config.region = 'us'
100
+ config.debug = true
101
+ config.logger = Logger.new(STDOUT)
102
+ end
103
+ ```
104
+
105
+ #### Staging Configuration
106
+ ```ruby
107
+ Tabscanner.configure do |config|
108
+ config.api_key = ENV['TABSCANNER_STAGING_API_KEY']
109
+ config.region = 'us'
110
+ config.base_url = 'https://staging.tabscanner.com'
111
+ config.debug = true
112
+ end
113
+ ```
114
+
115
+ ## Usage
116
+
117
+ ### Basic Usage
118
+
119
+ #### Submit a Receipt Image
120
+
121
+ ```ruby
122
+ # From file path
123
+ token = Tabscanner.submit_receipt('/path/to/receipt.jpg')
124
+
125
+ # From IO stream
126
+ File.open('/path/to/receipt.jpg', 'rb') do |file|
127
+ token = Tabscanner.submit_receipt(file)
128
+ end
129
+
130
+ # From uploaded file (Rails example)
131
+ token = Tabscanner.submit_receipt(params[:receipt_file])
132
+ ```
133
+
134
+ #### Get Processing Results
135
+
136
+ ```ruby
137
+ # With default timeout (15 seconds)
138
+ result = Tabscanner.get_result(token)
139
+
140
+ # With custom timeout
141
+ result = Tabscanner.get_result(token, timeout: 30)
142
+ ```
143
+
144
+ ### Complete Workflow Example
145
+
146
+ ```ruby
147
+ require 'tabscanner'
148
+
149
+ # Configure once
150
+ Tabscanner.configure do |config|
151
+ config.api_key = ENV['TABSCANNER_API_KEY']
152
+ config.debug = Rails.env.development?
153
+ end
154
+
155
+ # Process multiple receipts
156
+ receipt_files = Dir['receipts/*.jpg']
157
+
158
+ receipt_files.each do |file_path|
159
+ begin
160
+ # Submit for processing
161
+ puts "Processing #{file_path}..."
162
+ token = Tabscanner.submit_receipt(file_path)
163
+
164
+ # Get results
165
+ result = Tabscanner.get_result(token, timeout: 30)
166
+
167
+ # Use the data
168
+ puts "✅ #{File.basename(file_path)}"
169
+ puts " Merchant: #{result['merchant']}"
170
+ puts " Total: $#{result['total']}"
171
+ puts " Date: #{result['date']}"
172
+ puts " Items: #{result['items']&.count || 0}"
173
+ puts
174
+
175
+ rescue => e
176
+ puts "❌ Error processing #{file_path}: #{e.message}"
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Credit Monitoring
182
+
183
+ Check your remaining API credits to stay within your plan limits:
184
+
185
+ ```ruby
186
+ # Check remaining credits
187
+ credits = Tabscanner.get_credits
188
+ puts "Remaining credits: #{credits}"
189
+
190
+ # Basic usage monitoring
191
+ if credits < 10
192
+ puts "Warning: Low credit balance!"
193
+ end
194
+ ```
195
+
196
+ #### Credits with Error Handling
197
+
198
+ ```ruby
199
+ begin
200
+ credits = Tabscanner.get_credits
201
+
202
+ case credits
203
+ when 0
204
+ puts "⚠️ No credits remaining - please upgrade your plan"
205
+ when 1..9
206
+ puts "⚠️ Low credits (#{credits}) - consider upgrading soon"
207
+ when 10..49
208
+ puts "ℹ️ Credits getting low (#{credits})"
209
+ else
210
+ puts "✅ #{credits} credits available"
211
+ end
212
+
213
+ rescue Tabscanner::UnauthorizedError => e
214
+ puts "❌ Invalid API key: #{e.message}"
215
+ rescue Tabscanner::ServerError => e
216
+ puts "❌ Service error: #{e.message}"
217
+ rescue Tabscanner::Error => e
218
+ puts "❌ Error checking credits: #{e.message}"
219
+ end
220
+ ```
221
+
222
+ ### Ruby on Rails Integration
223
+
224
+ ```ruby
225
+ class ReceiptsController < ApplicationController
226
+ def create
227
+ receipt_file = params[:receipt_file]
228
+
229
+ begin
230
+ # Submit receipt for processing
231
+ token = Tabscanner.submit_receipt(receipt_file)
232
+
233
+ # Store token for later retrieval
234
+ receipt = Receipt.create!(
235
+ user: current_user,
236
+ token: token,
237
+ status: 'processing',
238
+ filename: receipt_file.original_filename
239
+ )
240
+
241
+ # Start background job to poll for results
242
+ ProcessReceiptJob.perform_later(receipt.id)
243
+
244
+ render json: { status: 'submitted', receipt_id: receipt.id }
245
+
246
+ rescue Tabscanner::ValidationError => e
247
+ render json: { error: "Invalid receipt: #{e.message}" }, status: 422
248
+ rescue Tabscanner::UnauthorizedError => e
249
+ render json: { error: "API authentication failed" }, status: 401
250
+ rescue => e
251
+ render json: { error: "Processing failed: #{e.message}" }, status: 500
252
+ end
253
+ end
254
+ end
255
+
256
+ # Background job
257
+ class ProcessReceiptJob < ApplicationJob
258
+ def perform(receipt_id)
259
+ receipt = Receipt.find(receipt_id)
260
+
261
+ begin
262
+ result = Tabscanner.get_result(receipt.token, timeout: 60)
263
+
264
+ receipt.update!(
265
+ status: 'completed',
266
+ merchant: result['merchant'],
267
+ total: result['total'],
268
+ date: result['date'],
269
+ raw_data: result
270
+ )
271
+
272
+ rescue Tabscanner::Error => e
273
+ receipt.update!(status: 'failed', error_message: e.message)
274
+ end
275
+ end
276
+ end
277
+ ```
278
+
279
+ ## Error Handling
280
+
281
+ The gem provides specific error classes for different types of failures:
282
+
283
+ ### Error Types
284
+
285
+ - `Tabscanner::ConfigurationError` - Invalid or missing configuration
286
+ - `Tabscanner::UnauthorizedError` - Authentication failures (401)
287
+ - `Tabscanner::ValidationError` - Request validation failures (422)
288
+ - `Tabscanner::ServerError` - Server errors (500+)
289
+ - `Tabscanner::Error` - Base error class for other failures
290
+
291
+ ### Error Handling Examples
292
+
293
+ #### Basic Error Handling
294
+
295
+ ```ruby
296
+ begin
297
+ token = Tabscanner.submit_receipt('receipt.jpg')
298
+ result = Tabscanner.get_result(token)
299
+ rescue Tabscanner::ConfigurationError => e
300
+ puts "Configuration problem: #{e.message}"
301
+ rescue Tabscanner::UnauthorizedError => e
302
+ puts "Authentication failed: #{e.message}"
303
+ rescue Tabscanner::ValidationError => e
304
+ puts "Invalid request: #{e.message}"
305
+ rescue Tabscanner::ServerError => e
306
+ puts "Server error: #{e.message}"
307
+ rescue Tabscanner::Error => e
308
+ puts "General error: #{e.message}"
309
+ end
310
+ ```
311
+
312
+ #### Specific Error Scenarios
313
+
314
+ ```ruby
315
+ # Handle configuration errors
316
+ begin
317
+ Tabscanner.submit_receipt('receipt.jpg')
318
+ rescue Tabscanner::ConfigurationError => e
319
+ # API key not set or invalid
320
+ puts "Please set TABSCANNER_API_KEY environment variable"
321
+ return
322
+ end
323
+
324
+ # Handle authentication errors
325
+ begin
326
+ token = Tabscanner.submit_receipt('receipt.jpg')
327
+ rescue Tabscanner::UnauthorizedError => e
328
+ # Invalid API key or expired
329
+ puts "Please check your API key: #{e.message}"
330
+ return
331
+ end
332
+
333
+ # Handle validation errors
334
+ begin
335
+ token = Tabscanner.submit_receipt('invalid-file.txt')
336
+ rescue Tabscanner::ValidationError => e
337
+ # Invalid file format or corrupted image
338
+ puts "Invalid image file: #{e.message}"
339
+ return
340
+ end
341
+
342
+ # Handle timeout errors
343
+ begin
344
+ result = Tabscanner.get_result(token, timeout: 5)
345
+ rescue Tabscanner::Error => e
346
+ if e.message.include?('Timeout')
347
+ puts "Processing is taking longer than expected, try again later"
348
+ else
349
+ puts "Unexpected error: #{e.message}"
350
+ end
351
+ end
352
+ ```
353
+
354
+ #### Advanced Error Handling with Debug Information
355
+
356
+ When debug mode is enabled, errors include additional debugging information:
357
+
358
+ ```ruby
359
+ Tabscanner.configure do |config|
360
+ config.api_key = 'your_key'
361
+ config.debug = true # Enable debug mode
362
+ end
363
+
364
+ begin
365
+ result = Tabscanner.get_result('invalid_token')
366
+ rescue Tabscanner::ValidationError => e
367
+ puts "Error: #{e.message}"
368
+
369
+ # Access raw response data for debugging
370
+ if e.raw_response
371
+ puts "HTTP Status: #{e.raw_response[:status]}"
372
+ puts "Response Body: #{e.raw_response[:body]}"
373
+ puts "Response Headers: #{e.raw_response[:headers]}"
374
+ end
375
+ end
376
+ ```
377
+
378
+ #### Retry Logic Example
379
+
380
+ ```ruby
381
+ def process_receipt_with_retry(file_path, max_retries: 3)
382
+ retries = 0
383
+
384
+ begin
385
+ token = Tabscanner.submit_receipt(file_path)
386
+ result = Tabscanner.get_result(token)
387
+ return result
388
+
389
+ rescue Tabscanner::ServerError => e
390
+ retries += 1
391
+ if retries <= max_retries
392
+ puts "Server error, retrying (#{retries}/#{max_retries})..."
393
+ sleep(2 ** retries) # Exponential backoff
394
+ retry
395
+ else
396
+ raise e
397
+ end
398
+
399
+ rescue Tabscanner::UnauthorizedError, Tabscanner::ValidationError => e
400
+ # Don't retry these errors
401
+ raise e
402
+ end
403
+ end
404
+ ```
405
+
406
+ ## Debug Mode
407
+
408
+ Enable debug mode to get detailed logging of HTTP requests and responses:
409
+
410
+ ### Environment Variable
411
+
412
+ ```bash
413
+ export TABSCANNER_DEBUG=true
414
+ ```
415
+
416
+ ### Programmatic Configuration
417
+
418
+ ```ruby
419
+ Tabscanner.configure do |config|
420
+ config.api_key = 'your_key'
421
+ config.debug = true
422
+ config.logger = Logger.new(STDOUT) # Optional: custom logger
423
+ end
424
+ ```
425
+
426
+ ### Debug Output Example
427
+
428
+ When debug mode is enabled, you'll see detailed logs:
429
+
430
+ ```
431
+ [2023-07-28 10:30:15] DEBUG -- Tabscanner: Starting result polling for token: abc123 (timeout: 15s)
432
+ [2023-07-28 10:30:15] DEBUG -- Tabscanner: HTTP Request: GET result/abc123
433
+ [2023-07-28 10:30:15] DEBUG -- Tabscanner: Request Headers: Authorization=Bearer [REDACTED], User-Agent=Tabscanner Ruby Gem 1.0.0
434
+ [2023-07-28 10:30:16] DEBUG -- Tabscanner: HTTP Response: 200
435
+ [2023-07-28 10:30:16] DEBUG -- Tabscanner: Response Headers: {"content-type"=>["application/json"]}
436
+ [2023-07-28 10:30:16] DEBUG -- Tabscanner: Response Body: {"status":"processing"}
437
+ [2023-07-28 10:30:16] DEBUG -- Tabscanner: Result still processing for token: abc123, waiting 1s...
438
+ ```
439
+
440
+ ## API Reference
441
+
442
+ ### Configuration Methods
443
+
444
+ ```ruby
445
+ # Configure the gem
446
+ Tabscanner.configure do |config|
447
+ config.api_key = 'string' # Required: Your API key
448
+ config.region = 'string' # Optional: API region (default: 'us')
449
+ config.base_url = 'string' # Optional: Custom API base URL
450
+ config.debug = boolean # Optional: Enable debug logging (default: false)
451
+ config.logger = Logger # Optional: Custom logger instance
452
+ end
453
+
454
+ # Access current configuration
455
+ Tabscanner.config
456
+
457
+ # Validate configuration
458
+ Tabscanner.config.validate!
459
+ ```
460
+
461
+ ### Core Methods
462
+
463
+ ```ruby
464
+ # Submit a receipt for processing
465
+ # @param file_path_or_io [String, IO] File path or IO stream
466
+ # @return [String] Token for result retrieval
467
+ token = Tabscanner.submit_receipt(file_path_or_io)
468
+
469
+ # Get processing results
470
+ # @param token [String] Token from submit_receipt
471
+ # @param timeout [Integer] Maximum wait time in seconds (default: 15)
472
+ # @return [Hash] Parsed receipt data
473
+ result = Tabscanner.get_result(token, timeout: 30)
474
+ ```
475
+
476
+ ### Response Format
477
+
478
+ The `get_result` method returns a hash with the parsed receipt data:
479
+
480
+ ```ruby
481
+ {
482
+ "merchant" => "Coffee Shop",
483
+ "total" => 15.99,
484
+ "subtotal" => 14.50,
485
+ "tax" => 1.49,
486
+ "date" => "2023-07-28",
487
+ "time" => "14:30:00",
488
+ "items" => [
489
+ {
490
+ "name" => "Latte",
491
+ "price" => 4.50,
492
+ "quantity" => 1
493
+ },
494
+ {
495
+ "name" => "Sandwich",
496
+ "price" => 10.00,
497
+ "quantity" => 1
498
+ }
499
+ ],
500
+ "payment_method" => "Credit Card",
501
+ "currency" => "USD"
502
+ }
503
+ ```
504
+
505
+ ## Examples
506
+
507
+ ### Simple Script Example (Under 10 Lines)
508
+
509
+ Create a file `process_receipt.rb`:
510
+
511
+ ```ruby
512
+ #!/usr/bin/env ruby
513
+ require 'tabscanner'
514
+
515
+ Tabscanner.configure { |c| c.api_key = ENV['TABSCANNER_API_KEY'] }
516
+
517
+ token = Tabscanner.submit_receipt(ARGV[0])
518
+ result = Tabscanner.get_result(token)
519
+
520
+ puts "Merchant: #{result['merchant']}"
521
+ puts "Total: $#{result['total']}"
522
+ puts "Items: #{result['items']&.count || 0}"
523
+ ```
524
+
525
+ Usage:
526
+ ```bash
527
+ ruby process_receipt.rb receipt.jpg
528
+ ```
529
+
530
+ ### Batch Processing Example
531
+
532
+ ```ruby
533
+ require 'tabscanner'
534
+
535
+ Tabscanner.configure do |config|
536
+ config.api_key = ENV['TABSCANNER_API_KEY']
537
+ config.debug = true
538
+ end
539
+
540
+ receipts_dir = 'receipts'
541
+ results = []
542
+
543
+ Dir[File.join(receipts_dir, '*.{jpg,jpeg,png}')].each do |file_path|
544
+ puts "Processing #{File.basename(file_path)}..."
545
+
546
+ begin
547
+ token = Tabscanner.submit_receipt(file_path)
548
+ result = Tabscanner.get_result(token, timeout: 30)
549
+
550
+ results << {
551
+ file: File.basename(file_path),
552
+ merchant: result['merchant'],
553
+ total: result['total'],
554
+ date: result['date']
555
+ }
556
+
557
+ puts "✅ Success: #{result['merchant']} - $#{result['total']}"
558
+
559
+ rescue => e
560
+ puts "❌ Error: #{e.message}"
561
+ end
562
+ end
563
+
564
+ # Save results to CSV
565
+ require 'csv'
566
+ CSV.open('receipt_results.csv', 'w') do |csv|
567
+ csv << ['File', 'Merchant', 'Total', 'Date']
568
+ results.each { |r| csv << [r[:file], r[:merchant], r[:total], r[:date]] }
569
+ end
570
+
571
+ puts "\nProcessed #{results.count} receipts successfully"
572
+ ```
573
+
574
+ ## Development
575
+
576
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
577
+
578
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
579
+
580
+ ## Contributing
581
+
582
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fkchang/tabscanner.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec