Remote sync of webdav
This commit is contained in:
parent
f01186df79
commit
483fbeafb6
44 changed files with 4842 additions and 975 deletions
|
|
@ -21,6 +21,37 @@ void main() {
|
|||
expect(result.totalCost, 44.44);
|
||||
});
|
||||
|
||||
test('parses a tabular receipt where labels and values sit on separate lines', () {
|
||||
// Mirrors a real Casey's receipt: "Pump / Gallons / Price" is a
|
||||
// header row, with the actual values on the next line, and the
|
||||
// total is phrased "Total Sale" rather than a bare "Total".
|
||||
const receipt = '''
|
||||
Casey's
|
||||
Store #4021
|
||||
420 W. 6TH STREET
|
||||
KEARNEY, MO 64060
|
||||
Date 08/08/2026
|
||||
Time 02:21
|
||||
VI
|
||||
########8533
|
||||
Pump Gallons Price
|
||||
07 32.115 \$ 3.759
|
||||
Product Amount
|
||||
87E10 \$ 120.72
|
||||
Total Sale \$ 120.72
|
||||
Auth #
|
||||
Visa
|
||||
Seq # 008299
|
||||
98
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 32.115);
|
||||
expect(result.pricePerGallon, 3.759);
|
||||
expect(result.totalCost, 120.72);
|
||||
});
|
||||
|
||||
test('does not confuse SUBTOTAL with TOTAL', () {
|
||||
const receipt = '''
|
||||
SUBTOTAL 10.00
|
||||
|
|
@ -67,6 +98,386 @@ void main() {
|
|||
expect(result.gallons, isNull);
|
||||
expect(result.pricePerGallon, isNull);
|
||||
expect(result.totalCost, isNull);
|
||||
expect(result.date, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('ReceiptParser state parsing', () {
|
||||
test('detects a state abbreviation directly before a ZIP code', () {
|
||||
const receipt = '''
|
||||
Casey's
|
||||
420 W. 6TH STREET
|
||||
KEARNEY, MO 64060
|
||||
''';
|
||||
|
||||
expect(ReceiptParser.parse(receipt).state, 'MO');
|
||||
});
|
||||
|
||||
test('detects a non-Missouri state the same way', () {
|
||||
const receipt = '''
|
||||
Swift Stop #3
|
||||
1809 Hobbs Hwy
|
||||
Seminole TX 79360
|
||||
''';
|
||||
|
||||
expect(ReceiptParser.parse(receipt).state, 'TX');
|
||||
});
|
||||
|
||||
test('falls back to a state right after a comma when no ZIP is nearby', () {
|
||||
const receipt = '''
|
||||
Buc-ee's
|
||||
6988 Buc-ee's Blvd
|
||||
Leeds, AL
|
||||
''';
|
||||
|
||||
expect(ReceiptParser.parse(receipt).state, 'AL');
|
||||
});
|
||||
|
||||
test('does not mistake "VI" (Visa) for the Virgin Islands', () {
|
||||
const receipt = '''
|
||||
Casey's
|
||||
VI
|
||||
########8533
|
||||
Pump Gallons Price
|
||||
07 32.115 \$ 3.759
|
||||
''';
|
||||
|
||||
expect(ReceiptParser.parse(receipt).state, isNull);
|
||||
});
|
||||
|
||||
test('does not mistake the word "In" for Indiana', () {
|
||||
const receipt = '''
|
||||
Gallons 10.000
|
||||
Price/Gal \$3.399
|
||||
Total Fuel \$33.99
|
||||
All Taxes Included
|
||||
In Fuel Price.
|
||||
''';
|
||||
|
||||
expect(ReceiptParser.parse(receipt).state, isNull);
|
||||
});
|
||||
|
||||
test('returns null when no address-like state pattern is present', () {
|
||||
expect(ReceiptParser.parse('THANK YOU FOR YOUR PURCHASE').state, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('ReceiptParser date parsing', () {
|
||||
test('parses "Date: M/D/YYYY" + "Time: H:MM:SS AM/PM" on separate lines', () {
|
||||
const receipt = '''
|
||||
Date: 8/22/2024
|
||||
Time: 10:11:00 AM
|
||||
Gallons 10.000
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.date, DateTime(2024, 8, 22, 10, 11));
|
||||
});
|
||||
|
||||
test('parses "DATE M/D/YY H:MM" on the same line', () {
|
||||
const receipt = '''
|
||||
DATE 3/26/22 18:12
|
||||
PUMP# 06
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.date, DateTime(2022, 3, 26, 18, 12));
|
||||
});
|
||||
|
||||
test('parses a 2-digit year and a no-space AM/PM time', () {
|
||||
const receipt = '''
|
||||
Date 01/23/20
|
||||
Time 02:11PM
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.date, DateTime(2020, 1, 23, 14, 11));
|
||||
});
|
||||
|
||||
test('12 AM and 12 PM convert correctly (midnight/noon edge case)', () {
|
||||
expect(ReceiptParser.parse('Date 1/1/24 12:00 AM').date, DateTime(2024, 1, 1, 0, 0));
|
||||
expect(ReceiptParser.parse('Date 1/1/24 12:30 PM').date, DateTime(2024, 1, 1, 12, 30));
|
||||
});
|
||||
|
||||
test('falls back to midnight when a date is found but no time is nearby', () {
|
||||
// Comfortably more than the 40-character "nearby" search window
|
||||
// between the date and the unrelated text before a time appears
|
||||
// much later, regardless of how this string literal is indented.
|
||||
final filler = 'x' * 100;
|
||||
final receipt = 'Date 12/14/16\n$filler\nTime 5:11 PM';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
// Too far from the date to count as "nearby", so this should be
|
||||
// date-only at midnight — still better than no date at all, and the
|
||||
// confirm screen lets the user fix the time manually either way.
|
||||
expect(result.date, isNotNull);
|
||||
expect(result.date!.year, 2016);
|
||||
expect(result.date!.month, 12);
|
||||
expect(result.date!.day, 14);
|
||||
expect(result.date!.hour, 0);
|
||||
expect(result.date!.minute, 0);
|
||||
});
|
||||
|
||||
test('returns null when no date-like pattern is present', () {
|
||||
final result = ReceiptParser.parse('GALLONS 10.000\nPPG 3.500');
|
||||
|
||||
expect(result.date, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
// Transcribed from a batch of real photographed receipts across different
|
||||
// gas station chains, to catch label/layout variations the handful of
|
||||
// hand-written cases above don't happen to cover.
|
||||
group('ReceiptParser against real receipt layouts', () {
|
||||
test('Break Time — labels adjacent to values on the same line', () {
|
||||
const receipt = '''
|
||||
Date: 8/22/2024
|
||||
Time: 10:11:00 AM
|
||||
OXY87
|
||||
Pump Number 22
|
||||
Gallons 10.000
|
||||
Price/Gal \$3.399
|
||||
Total Fuel \$33.99
|
||||
Total Sale \$33.99
|
||||
Visa \$33.99
|
||||
All Taxes Included
|
||||
In Fuel Price.
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 10.000);
|
||||
expect(result.pricePerGallon, 3.399);
|
||||
expect(result.totalCost, 33.99);
|
||||
});
|
||||
|
||||
test('Break Time — tabular Pump/Gallons/Price header row', () {
|
||||
const receipt = '''
|
||||
Member# 111795637651
|
||||
Invoice# 11733
|
||||
Date: 03/23/22
|
||||
Time: 10:05
|
||||
Auth# 020413
|
||||
VI Acct #
|
||||
************0344
|
||||
Pump Gallons Price
|
||||
14 5.116 \$ 5.499
|
||||
Product Amount
|
||||
Regular \$ 28.13
|
||||
Total Sale \$ 28.13
|
||||
SALE- Contactless
|
||||
Approved
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 5.116);
|
||||
expect(result.pricePerGallon, 5.499);
|
||||
expect(result.totalCost, 28.13);
|
||||
});
|
||||
|
||||
test('Buc-ee\'s — abbreviated "PRICE/G" label', () {
|
||||
const receipt = '''
|
||||
BUC-EE'S
|
||||
6988 Buc-ee's Blvd
|
||||
Leeds AL
|
||||
PUMP No. 63
|
||||
GALLONS 9.999
|
||||
PRICE/G \$2.499
|
||||
TOTAL FUEL \$24.99
|
||||
Regular
|
||||
TOTAL SALE \$24.99
|
||||
Visa \$24.99
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 9.999);
|
||||
expect(result.pricePerGallon, 2.499);
|
||||
expect(result.totalCost, 24.99);
|
||||
});
|
||||
|
||||
test('Costco — tabular header row, "Total Sale" phrasing', () {
|
||||
const receipt = '''
|
||||
Costco #01448
|
||||
1524 Beasie RD
|
||||
Murfreesboro TN
|
||||
Date: 01/03/24
|
||||
Time: 19:31
|
||||
Pump Gallons Price
|
||||
11 12.598 \$ 2.399
|
||||
Product Amount
|
||||
Regular \$ 30.22
|
||||
Total Sale \$ 30.22
|
||||
SALE- Contactless
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 12.598);
|
||||
expect(result.pricePerGallon, 2.399);
|
||||
expect(result.totalCost, 30.22);
|
||||
});
|
||||
|
||||
test('Murphy USA — "QTY(GAL)" label instead of "GALLONS"', () {
|
||||
const receipt = '''
|
||||
PUMP: 8
|
||||
PROD: UNLEAD
|
||||
PRICE/GAL: \$1.799
|
||||
NET/GAL: \$1.799
|
||||
QTY(GAL): 13.164
|
||||
FUEL TOTAL: \$23.68
|
||||
NET TOTAL: \$23.68
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 13.164);
|
||||
expect(result.pricePerGallon, 1.799);
|
||||
expect(result.totalCost, 23.68);
|
||||
});
|
||||
|
||||
test('Phillips 66 — "PRICE/G" label and "FUEL SALE" as the total', () {
|
||||
const receipt = '''
|
||||
WELCOME
|
||||
LAWNSIDE PHILLIPS 66
|
||||
355 Warrington Ave
|
||||
Lawnside NJ 08045
|
||||
DATE 3/26/22 18:12
|
||||
TRAN# 060757
|
||||
PUMP# 06
|
||||
SERVICE LEVEL: FULL
|
||||
PRODUCT: REGULAR
|
||||
GALLONS 3.642
|
||||
PRICE/G \$4.119
|
||||
FUEL SALE \$15.00
|
||||
CREDIT \$15.00
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 3.642);
|
||||
expect(result.pricePerGallon, 4.119);
|
||||
expect(result.totalCost, 15.00);
|
||||
});
|
||||
|
||||
test('Phillips 66 — second "FUEL SALE" example', () {
|
||||
const receipt = '''
|
||||
WELCOME
|
||||
SWIFT STOP #3
|
||||
1809 HOBBS HWY
|
||||
SEMINOLE TX 79360
|
||||
DATE 5/9/23 11:16
|
||||
TRAN#9103742
|
||||
PUMP# 10
|
||||
SERVICE LEVEL: SELF
|
||||
PRODUCT: DIESEL 2
|
||||
GALLONS: 29.854
|
||||
PRICE/G: \$3.499
|
||||
FUEL SALE \$181.66
|
||||
CREDIT \$181.66
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 29.854);
|
||||
expect(result.pricePerGallon, 3.499);
|
||||
expect(result.totalCost, 181.66);
|
||||
});
|
||||
|
||||
test('Phillips 66 / Murphy-style — "QTY(GAL)" plus "FUEL TOTAL"', () {
|
||||
const receipt = '''
|
||||
Verified by PIN
|
||||
PIN USED
|
||||
PUMP: 5
|
||||
PROD: UNLEAD
|
||||
PRICE/GAL: \$3.959
|
||||
NET/GAL: \$3.959
|
||||
QTY(GAL): 11.068
|
||||
FUEL TOTAL: \$43.82
|
||||
NET TOTAL: \$43.82
|
||||
GET REWARDED!
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 11.068);
|
||||
expect(result.pricePerGallon, 3.959);
|
||||
expect(result.totalCost, 43.82);
|
||||
});
|
||||
|
||||
test('QuikTrip — tabular header row, total not printed on receipt (derived)', () {
|
||||
const receipt = '''
|
||||
QUIKTRIP #01012
|
||||
383 George Liles PWK
|
||||
Concord, NC
|
||||
Invoice # 0000000
|
||||
Date 01/23/20
|
||||
Time 02:11PM
|
||||
Auth # 03581I
|
||||
Acct #
|
||||
************1821
|
||||
Pump Gallons Price
|
||||
14 10.496 \$2.359
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 10.496);
|
||||
expect(result.pricePerGallon, 2.359);
|
||||
// Not printed within the visible receipt text — derived from
|
||||
// gallons * price rather than found via a label.
|
||||
expect(result.totalCost, closeTo(10.496 * 2.359, 0.01));
|
||||
});
|
||||
|
||||
test('Sinclair — unit letter attached with no space, and a negative '
|
||||
'per-gallon discount line that must not be mistaken for gallons', () {
|
||||
const receipt = '''
|
||||
PUMP# 3
|
||||
UNLEADED CR 17.364G
|
||||
PRICE/GAL \$2.649
|
||||
DISCOUNTS BEFORE
|
||||
FUELING
|
||||
Debit Di/GAL \$-0.100
|
||||
FUEL TOTAL \$46.00
|
||||
DEBIT \$46.00
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 17.364);
|
||||
expect(result.pricePerGallon, 2.649);
|
||||
expect(result.totalCost, 46.00);
|
||||
});
|
||||
|
||||
test('Sinclair — PPG and GALLONS/FUEL TOTAL both split across header/data rows', () {
|
||||
const receipt = '''
|
||||
SINCLAIR
|
||||
5905 NW 72ND ST
|
||||
KANSAS CITY MO
|
||||
Date 12/14/16
|
||||
Time 5:11 PM
|
||||
Auth 068113
|
||||
VISA
|
||||
Pump Product PPG
|
||||
05 UNLD \$1.999
|
||||
Gallons Fuel Total
|
||||
9.505 \$19.00
|
||||
Thank you
|
||||
''';
|
||||
|
||||
final result = ReceiptParser.parse(receipt);
|
||||
|
||||
expect(result.gallons, 9.505);
|
||||
expect(result.pricePerGallon, 1.999);
|
||||
// "Fuel Total" is split from its value by a newline in this layout,
|
||||
// so it isn't label-matched — derived from gallons * price instead,
|
||||
// landing within a cent of the printed $19.00.
|
||||
expect(result.totalCost, closeTo(19.00, 0.01));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue