James Carl Sitsit

OCR can read the right numbers and still choose the wrong one

GCash ads put valid peso amounts beside the receipt total. FloatWatch uses the relationship between amount, fee, and total to reject that noise.

By James Carl Sitsit

I expected blurry text to be the hard part of receipt OCR. The worse failure was clean text in the wrong context.

GCash receipt screenshots can include ad banners with peso amounts. ML Kit may read the ad perfectly, then hand that number to the parser before the transaction total. A parser that grabs the first currency value can be wrong even though the recognizer did its job.

That changed what I thought I was debugging. OCR accuracy still mattered, but the harder problem here was selection.

Position and labels are weak evidence

Position looks useful until an ad moves the content around. Labels look better, but the FloatWatch parser still ran into receipts where label-based extraction chose the wrong number.

Both approaches depend on layout. The ad is part of that layout, so neither one can tell whether a valid peso amount belongs to the transaction or to the banner around it.

The parser needed evidence from the numbers themselves.

Use the relationship on the receipt

A load receipt contains an amount, a convenience fee, and a total. Those values have a relationship: the amount plus the fee equals the total.

FloatWatch uses that arithmetic consistency instead of trusting whichever peso value appears first. The transaction figures fit together. A promo value such as 99.00 usually does not fit the same relationship.

This does not make OCR smarter. It gives the parser a way to reject a number that looks valid on its own but does not belong to a valid set.

That distinction is the useful part for me. Recognition answers, "What characters are in this image?" Parsing has to answer, "Which of those characters describe the transaction?" The second question needs domain rules, not a better regular expression around the first number.

Where this stops working

The rest of the parser is still a wall of regular expressions built from receipts I have actually seen. A new receipt layout can still need a new pattern. Arithmetic consistency only helps when the receipt exposes values with a relationship the parser knows how to check.

FloatWatch does not treat that check as permission to save blindly. Parsed receipts still go through the confidence gate and manual review before confirmation. The arithmetic rule cuts one class of false match. It does not certify the whole receipt.

That is the narrower lesson I would keep from this parser: an OCR result can be textually correct and semantically wrong. When the real values obey an equation, that equation is better evidence than where a number happened to land on the screen.


This is from FloatWatch, a Flutter app that reads GCash receipts on-device to reconcile cash and e-float. The full build is in the FloatWatch deep dive.