A React Native library for scanning handwritten Devanagari grocery lists using AI vision. Supports multiple AI providers (Claude, Gemini, Groq) and outputs structured item data with optional English translations.
- Reads handwritten grocery lists in Devanagari script from photos or PDFs
- Extracts item name, quantity, unit, category, and confidence score per item
- Supports day-of-week section headers (e.g. सोमवार / Monday)
- Skips crossed-out items automatically
- Output in Devanagari, English, or both
- Configurable confidence threshold — throws on low-quality scans
- Pluggable provider interface: ship your own AI backend
- Chained provider: automatically refines low-confidence or transliterated items using a second model
npm install react-native-grocery-scannerPeer dependencies:
npm install react-native react-native-fsInstall your chosen AI provider SDK:
| Provider | Package |
|---|---|
| Claude (default) | npm install @anthropic-ai/sdk |
| Gemini | npm install @google/generative-ai |
| Groq | none (uses fetch) |
import { GroceryScanner } from 'react-native-grocery-scanner';
const scanner = new GroceryScanner({
provider: 'claude',
apiKey: process.env.ANTHROPIC_API_KEY,
outputLanguage: 'both',
confidenceThreshold: 0.95,
categories: ['dairy', 'grains', 'spices', 'oil', 'pulses', 'snacks', 'other'],
});
const list = await scanner.scan(imageUri);
// list.items → GroceryItem[]
// list.rawText → raw OCR text
// list.scanQuality → 'good' | 'degraded'Supports JPEG, PNG, WebP, and PDF inputs. Use scanner.scanPdf(uri) to assert the file is a PDF before scanning.
interface ScannerConfig {
provider: 'claude' | GroceryProvider; // built-in or custom provider
apiKey?: string; // required for built-in providers
outputLanguage: 'devanagari' | 'english' | 'both';
confidenceThreshold: number; // 0.0–1.0; throws ScanError on low confidence
categories: string[]; // category labels for classification
}Each item in GroceryList.items has the following shape:
interface GroceryItem {
nameDevanagari?: string; // standard Hindi name (present when outputLanguage is 'devanagari' or 'both')
nameEnglish?: string; // English translation (present when outputLanguage is 'english' or 'both')
quantity: number;
unit: string; // kg | g | litre | ml | packet | piece | dozen | bunch | box | bottle | can | other
category: string; // one of your configured categories, or 'other'
confidence: number; // 0.0–1.0
day?: string; // e.g. "Monday" — set when item appears under a day-of-week heading
}When using ChainedProvider, ProviderResult.chainLog contains a full audit trail:
interface ChainLog {
timestamp: string;
primaryProvider: string;
refinerProvider: string;
items: ItemAudit[];
}
interface ItemAudit {
nameEnglish: string;
primary: { nameDevanagari: string; confidence: number };
refinementTrigger: 'low_confidence' | 'transliteration' | null;
refined: { nameDevanagari: string; confidence: number } | null;
final: { nameDevanagari: string; confidence: number };
}import { GroceryScanner, ScanError } from 'react-native-grocery-scanner';
try {
const list = await scanner.scan(uri);
} catch (e) {
if (e instanceof ScanError) {
switch (e.code) {
case 'LOW_CONFIDENCE':
console.log('Confidence too low:', e.confidence, e.rawText);
break;
case 'PROVIDER_ERROR':
console.log('AI provider failed:', e.message);
break;
case 'INVALID_INPUT':
console.log('Blank or unreadable image');
break;
case 'UNSUPPORTED_FORMAT':
console.log('File type not supported');
break;
}
}
}Uses claude-sonnet-4-6 with vision. Supports images and PDFs.
{ provider: 'claude', apiKey: 'sk-ant-...' }Uses gemini-2.0-flash. Images only.
import { GeminiProvider } from 'react-native-grocery-scanner/providers';
{ provider: new GeminiProvider('AIza...') }Uses llama-3.2-11b-vision-preview via the Groq API. Images only.
import { GroqProvider } from 'react-native-grocery-scanner/providers';
{ provider: new GroqProvider('gsk_...') }Runs a primary provider for OCR, then automatically sends low-confidence or transliterated items to a refiner (a second RefinementProvider) for correction. Each scan result includes a chainLog with a per-item audit trail.
import { ChainedProvider } from 'react-native-grocery-scanner/providers';
import { ClaudeProvider } from 'react-native-grocery-scanner/providers';
const provider = new ChainedProvider({
primary: new ClaudeProvider(process.env.ANTHROPIC_API_KEY),
refiner: new ClaudeProvider(process.env.ANTHROPIC_API_KEY), // or any RefinementProvider
refinementThreshold: 0.8, // items below this confidence are sent to the refiner
});
const scanner = new GroceryScanner({ provider, outputLanguage: 'both', confidenceThreshold: 0.5, categories: [...] });
const list = await scanner.scan(imageUri);
// list.items → refined GroceryItem[]
// result.chainLog → ChainLog with per-item audit (primary, trigger, refined, final)Refinement is also triggered when a Devanagari name appears to be a phonetic transliteration of the English name (e.g. केपसीकम for "capsicum") rather than the correct Hindi vocabulary term.
Implement the GroceryProvider interface to use any vision model:
import type { GroceryProvider, ProviderResult, ScanConfig } from 'react-native-grocery-scanner';
class MyProvider implements GroceryProvider {
name = 'my-provider';
async scan(base64: string, mimeType: string, config: ScanConfig): Promise<ProviderResult> {
// call your model, return structured result
}
}
const scanner = new GroceryScanner({ provider: new MyProvider(), ... });Implement RefinementProvider to supply a second-pass corrector for ChainedProvider:
import type { RefinementProvider, RawItem, ScanConfig } from 'react-native-grocery-scanner';
class MyRefiner implements RefinementProvider {
async refine(rawText: string, items: RawItem[], config: ScanConfig): Promise<RawItem[]> {
// re-interpret flagged items using rawText context; return corrected items in the same order
}
}See example/App.tsx for a complete React Native app that picks a photo from the gallery and displays the parsed grocery list.
npm test # run jest tests
npm run typecheck # TypeScript checkMIT
