react-native-nlp-expense 1.0.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.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # react-native-nlp-expense
2
+
3
+ Lightweight, offline, rule-based NLP input for React Native expense capture.
4
+
5
+ `react-native-nlp-expense` parses plain-English expense phrases like:
6
+
7
+ - `Spent ₹250 on coffee yesterday`
8
+ - `Paid 1200 for groceries last Sunday`
9
+ - `Lunch 180`
10
+
11
+ It returns a predictable, typed `ExpenseResult` object with amount, currency, category, date, note, and confidence.
12
+
13
+ No AI, no backend, no network calls.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install react-native-nlp-expense
19
+ ```
20
+
21
+ Peer dependencies:
22
+
23
+ - `react`
24
+ - `react-native`
25
+
26
+ ## Usage
27
+
28
+ ```tsx
29
+ import React from 'react';
30
+ import { View } from 'react-native';
31
+ import { NLPExpenseInput, ExpenseResult } from 'react-native-nlp-expense';
32
+
33
+ export const ExpenseScreen = () => {
34
+ const handleParsed = (result: ExpenseResult) => {
35
+ console.log('Parsed expense', result);
36
+ };
37
+
38
+ return (
39
+ <View style={{ padding: 16 }}>
40
+ <NLPExpenseInput
41
+ locale="en-IN"
42
+ currency="INR"
43
+ categories={['coffee', 'groceries', 'fuel', 'rent', 'food']}
44
+ onParsed={handleParsed}
45
+ />
46
+ </View>
47
+ );
48
+ };
49
+ ```
50
+
51
+ ## Public API
52
+
53
+ ### `NLPExpenseInput`
54
+
55
+ A ready-to-use React Native text input component that parses expense phrases as the user types.
56
+
57
+ Props:
58
+
59
+ | Prop | Type | Default | Description |
60
+ | --- | --- | --- | --- |
61
+ | `locale` | `string` | `"en-IN"` | Locale used for date parsing (e.g. `"en-US"`, `"en-IN"`) |
62
+ | `currency` | `string` | — | Fallback currency code when no symbol is detected (e.g. `"INR"`, `"USD"`) |
63
+ | `categories` | `string[]` | — | List of category names to match against (e.g. `["coffee", "groceries"]`) |
64
+ | `onParsed` | `(result: ExpenseResult) => void` | — | Callback invoked with the parsed result |
65
+ | `placeholder` | `string` | — | Placeholder text shown inside the input |
66
+ | `debounceMs` | `number` | `500` | Milliseconds to debounce parsing while the user is typing |
67
+ | `showHint` | `boolean` | `true` | Whether to show a subtle parse hint beneath the input |
68
+
69
+ ### `parseExpenseText`
70
+
71
+ Low-level function that parses a raw expense string without requiring the React component.
72
+
73
+ ```ts
74
+ import { parseExpenseText } from 'react-native-nlp-expense';
75
+
76
+ const result = parseExpenseText('Spent ₹250 on coffee yesterday', {
77
+ locale: 'en-IN',
78
+ fallbackCurrency: 'INR',
79
+ categories: ['coffee', 'groceries', 'fuel'],
80
+ });
81
+ ```
82
+
83
+ ### `ExpenseResult`
84
+
85
+ ```ts
86
+ type ExpenseResult = {
87
+ amount: number; // Parsed numeric amount (0 if not found)
88
+ currency: string; // ISO currency code, e.g. "INR" or "USD"
89
+ category?: string; // Matched category from your list (or via alias)
90
+ date: Date; // Parsed date; defaults to today when not found
91
+ note?: string; // Remaining meaningful text after all fields are extracted
92
+ confidence: number; // Parse confidence score from 0 (low) to 1 (high)
93
+ };
94
+ ```
95
+
96
+ ### `ParseContext`
97
+
98
+ ```ts
99
+ type ParseContext = {
100
+ locale: string; // e.g. "en-IN"
101
+ fallbackCurrency: string; // e.g. "INR"
102
+ categories: string[]; // e.g. ["coffee", "groceries"]
103
+ };
104
+ ```
105
+
106
+ ## Parsing Rules
107
+
108
+ - **Amount**: detects `250`, `₹250`, `1,200`, `1200.50`, and other comma-formatted or decimal values. Ordinal numbers like `1st` and `2nd` are correctly excluded.
109
+ - **Currency**: inferred from symbol (`₹` → `INR`, `$` → `USD`); otherwise falls back to the `currency` prop / `fallbackCurrency` context field.
110
+ - **Date**: parsed via [`chrono-node`](https://github.com/wanasit/chrono) for natural-language phrases like `yesterday`, `last Sunday`, `1st Feb`, `last month`; defaults to today when no date phrase is found.
111
+ - **Category**: exact match against the provided `categories` list (case-insensitive), with built-in lightweight aliases (e.g. `lunch` → `food` when `food` is in your list).
112
+ - **Note**: all remaining meaningful text after removing the matched amount, date, category, and common filler words (`spent`, `paid`, `on`, `for`, `rs`, `rupees`, etc.).
113
+ - **Confidence**: deterministic score from `0` to `1` based on how many fields were confidently parsed:
114
+ - Base score: `0.25`
115
+ - Amount found: `+0.45`
116
+ - Category found: `+0.15`
117
+ - Explicit date phrase found: `+0.10`
118
+ - Note found: `+0.05`
119
+
120
+ ### Built-in Category Aliases
121
+
122
+ | Alias | Resolves to |
123
+ | --- | --- |
124
+ | `lunch`, `dinner`, `breakfast`, `meal` | `food` |
125
+ | `grocery` | `groceries` |
126
+ | `cafe` | `coffee` |
127
+
128
+ ## Supported Input Patterns
129
+
130
+ ### Basic Patterns
131
+
132
+ | Pattern | Example |
133
+ | --- | --- |
134
+ | Amount + Category | `250 coffee` |
135
+ | Currency + Amount + Category | `₹250 coffee` |
136
+ | Action + Amount + Category | `Spent 250 coffee` |
137
+ | Amount + Category + Date | `250 coffee yesterday` |
138
+ | Action + Currency + Amount + Category | `Paid ₹1200 rent` |
139
+ | Category + Amount | `Lunch 180` |
140
+ | Amount + Category + Note | `250 coffee with friends` |
141
+ | Action + Amount + Category + Date | `Spent 400 fuel last Sunday` |
142
+ | Amount + Category + Specific Date | `250 groceries 1st Feb` |
143
+ | Category + Amount + Date | `Dinner 300 today` |
144
+ | Amount Only (defaults category) | `180` |
145
+ | Amount + Note | `300 taxi to office` |
146
+
147
+ ### Advanced Patterns
148
+
149
+ | Pattern | Example | Parsed As |
150
+ | --- | --- | --- |
151
+ | Action + Amount + Preposition + Category + Date | `Spent ₹250 on coffee yesterday` | amount=250, category=coffee |
152
+ | Amount + Category + Note + Date | `300 dinner with team last Friday` | amount=300, category=food, note=with team |
153
+ | Action + Amount + Category + Reason | `Paid 1200 rent for March` | amount=1200, category=rent, note=for March |
154
+ | Amount + Category + Location | `180 coffee at Starbucks` | amount=180, category=coffee, note=at Starbucks |
155
+ | Action + Amount + Category + Time Reference | `Spent 400 fuel in the morning` | amount=400, category=fuel |
156
+ | Category + Amount + Extra Words | `Lunch 250 office canteen` | amount=250, category=food |
157
+ | Amount + Currency Word + Category | `500 rupees groceries` | amount=500, category=groceries |
158
+ | Action + Approximate Amount | `Spent around 300 on snacks` | amount=300 (lower confidence) |
159
+ | Amount + Category + Possessive Note | `250 coffee with client` | amount=250, note=with client |
160
+ | Action + Amount + Category + Relative Date | `Paid 900 electricity bill last month` | amount=900, category=electricity |
161
+
162
+ ## Behavior
163
+
164
+ - Single text input optimized for mobile typing.
165
+ - Parsing runs on `onBlur` and on debounced `onChangeText` (default 500 ms).
166
+ - The input field is never blocked or reset while parsing.
167
+ - An optional subtle parse hint can be displayed below the input (`showHint` prop).
168
+
169
+ ## Typed Exports
170
+
171
+ ```ts
172
+ import {
173
+ NLPExpenseInput,
174
+ parseExpenseText,
175
+ parseAmount,
176
+ parseDate,
177
+ parseCategory,
178
+ ExpenseResult,
179
+ } from 'react-native-nlp-expense';
180
+ ```
181
+
182
+ | Export | Kind | Description |
183
+ | --- | --- | --- |
184
+ | `NLPExpenseInput` | Component | Drop-in React Native text input with NLP parsing |
185
+ | `parseExpenseText` | Function | Parse a raw string into an `ExpenseResult` |
186
+ | `parseAmount` | Function | Extract amount and currency from a string |
187
+ | `parseDate` | Function | Extract a date from a string using chrono-node |
188
+ | `parseCategory` | Function | Match a category from a string against a list |
189
+ | `ExpenseResult` | Type | The structured result returned by the parser |
190
+
191
+ ## Limitations
192
+
193
+ - Rule-based parser only (not AI): complex or ambiguous sentence structures may not parse perfectly.
194
+ - Currency inference currently supports `₹` (`INR`) and `$` (`USD`) symbols only.
195
+ - Category detection depends entirely on the list you provide plus the built-in aliases above.
196
+ - Locale handling is English-oriented; non-English date phrases are not supported in v1.
197
+ - Multiple expenses in a single phrase are not supported in v1.
198
+
199
+ ## Local Development
200
+
201
+ ```bash
202
+ npm install
203
+ npm run build
204
+ ```
205
+
206
+ Build output is generated in `dist/`.
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { NLPExpenseInputProps } from './types';
3
+ export declare const NLPExpenseInput: React.FC<NLPExpenseInputProps>;
4
+ //# sourceMappingURL=NLPExpenseInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NLPExpenseInput.d.ts","sourceRoot":"","sources":["../src/NLPExpenseInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAIjF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA8E1D,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { StyleSheet, Text, TextInput, View } from 'react-native';
4
+ import { parseExpenseText } from './parser';
5
+ export const NLPExpenseInput = ({ locale = 'en-IN', currency, categories, onParsed, placeholder = 'e.g. Spent ₹250 on coffee yesterday', debounceMs = 500, showHint = true }) => {
6
+ const [value, setValue] = useState('');
7
+ const [hint, setHint] = useState(null);
8
+ const debounceTimerRef = useRef(null);
9
+ const context = useMemo(() => ({
10
+ locale,
11
+ fallbackCurrency: currency,
12
+ categories
13
+ }), [categories, currency, locale]);
14
+ const runParse = useCallback((text) => {
15
+ const result = parseExpenseText(text, context);
16
+ onParsed(result);
17
+ if (showHint) {
18
+ if (result.amount > 0) {
19
+ setHint(`Parsed ${result.currency} ${result.amount.toFixed(0)}${result.category ? ` • ${result.category}` : ''}`);
20
+ }
21
+ else {
22
+ setHint('Amount not detected yet');
23
+ }
24
+ }
25
+ }, [context, onParsed, showHint]);
26
+ const scheduleParse = useCallback((text) => {
27
+ if (debounceTimerRef.current) {
28
+ clearTimeout(debounceTimerRef.current);
29
+ }
30
+ debounceTimerRef.current = setTimeout(() => {
31
+ runParse(text);
32
+ }, debounceMs);
33
+ }, [debounceMs, runParse]);
34
+ useEffect(() => {
35
+ return () => {
36
+ if (debounceTimerRef.current) {
37
+ clearTimeout(debounceTimerRef.current);
38
+ }
39
+ };
40
+ }, []);
41
+ return (_jsxs(View, { style: styles.container, children: [_jsx(TextInput, { value: value, placeholder: placeholder, onChangeText: (text) => {
42
+ setValue(text);
43
+ scheduleParse(text);
44
+ }, onBlur: () => runParse(value), style: styles.input, autoCorrect: false, autoCapitalize: "sentences" }), showHint && hint ? _jsx(Text, { style: styles.hint, children: hint }) : null] }));
45
+ };
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ width: '100%'
49
+ },
50
+ input: {
51
+ borderWidth: StyleSheet.hairlineWidth,
52
+ borderRadius: 10,
53
+ paddingHorizontal: 14,
54
+ paddingVertical: 10,
55
+ fontSize: 16
56
+ },
57
+ hint: {
58
+ marginTop: 6,
59
+ fontSize: 12
60
+ }
61
+ });
62
+ //# sourceMappingURL=NLPExpenseInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NLPExpenseInput.js","sourceRoot":"","sources":["../src/NLPExpenseInput.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { NLPExpenseInput } from './NLPExpenseInput';
2
+ export { parseAmount, parseCategory, parseDate, parseExpenseText } from './parser';
3
+ export type { ExpenseResult, NLPExpenseInputProps, ParseAmountResult, ParseCategoryResult, ParseDateResult, ParseContext } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACnF,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,YAAY,EACb,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { NLPExpenseInput } from './NLPExpenseInput';
2
+ export { parseAmount, parseCategory, parseDate, parseExpenseText } from './parser';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ExpenseResult, ParseContext } from '../types';
2
+ import { parseAmount } from './parseAmount';
3
+ import { parseCategory } from './parseCategory';
4
+ import { parseDate } from './parseDate';
5
+ export declare const parseExpenseText: (input: string, context: ParseContext) => ExpenseResult;
6
+ export { parseAmount, parseCategory, parseDate };
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE5D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA0ExC,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,EAAE,SAAS,YAAY,KAAG,aAwBvE,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { parseAmount } from './parseAmount';
2
+ import { parseCategory } from './parseCategory';
3
+ import { parseDate } from './parseDate';
4
+ const FILLER_WORDS = new Set([
5
+ 'spent',
6
+ 'pay',
7
+ 'paid',
8
+ 'for',
9
+ 'on',
10
+ 'rs',
11
+ 'rupees',
12
+ 'inr',
13
+ 'usd'
14
+ ]);
15
+ const normalizeSpaces = (value) => value.replace(/\s+/g, ' ').trim();
16
+ const removeMatchedSegment = (input, matchedText) => {
17
+ if (!matchedText) {
18
+ return input;
19
+ }
20
+ const escaped = matchedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21
+ return input.replace(new RegExp(escaped, 'i'), ' ');
22
+ };
23
+ const extractNote = (input, consumedSegments) => {
24
+ let noteCandidate = input;
25
+ consumedSegments.forEach((segment) => {
26
+ noteCandidate = removeMatchedSegment(noteCandidate, segment);
27
+ });
28
+ const words = normalizeSpaces(noteCandidate)
29
+ .split(' ')
30
+ .map((word) => word.trim())
31
+ .filter((word) => word.length > 0)
32
+ .filter((word) => !FILLER_WORDS.has(word.toLowerCase()));
33
+ if (words.length === 0) {
34
+ return undefined;
35
+ }
36
+ return words.join(' ');
37
+ };
38
+ const calculateConfidence = (params) => {
39
+ let score = 0.25;
40
+ if (params.hasAmount) {
41
+ score += 0.45;
42
+ }
43
+ if (params.hasCategory) {
44
+ score += 0.15;
45
+ }
46
+ if (params.hasDatePhrase) {
47
+ score += 0.1;
48
+ }
49
+ if (params.hasNote) {
50
+ score += 0.05;
51
+ }
52
+ return Math.max(0, Math.min(1, Number(score.toFixed(2))));
53
+ };
54
+ export const parseExpenseText = (input, context) => {
55
+ var _a, _b;
56
+ const amountResult = parseAmount(input);
57
+ const categoryResult = parseCategory(input, context.categories);
58
+ const dateResult = parseDate(input, context.locale);
59
+ const note = extractNote(input, [
60
+ amountResult.matchedText,
61
+ categoryResult.matchedText,
62
+ dateResult.matchedText
63
+ ]);
64
+ return {
65
+ amount: (_a = amountResult.amount) !== null && _a !== void 0 ? _a : 0,
66
+ currency: (_b = amountResult.currency) !== null && _b !== void 0 ? _b : context.fallbackCurrency,
67
+ category: categoryResult.category,
68
+ date: dateResult.date,
69
+ note,
70
+ confidence: calculateConfidence({
71
+ hasAmount: typeof amountResult.amount === 'number',
72
+ hasCategory: Boolean(categoryResult.category),
73
+ hasDatePhrase: !dateResult.isDefaultToday,
74
+ hasNote: Boolean(note)
75
+ })
76
+ };
77
+ };
78
+ export { parseAmount, parseCategory, parseDate };
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ParseAmountResult } from '../types';
2
+ export declare const parseAmount: (input: string) => ParseAmountResult;
3
+ //# sourceMappingURL=parseAmount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAmount.d.ts","sourceRoot":"","sources":["../../src/parser/parseAmount.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AASlD,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,iBAyC3C,CAAC"}
@@ -0,0 +1,42 @@
1
+ const SYMBOL_TO_CURRENCY = {
2
+ '₹': 'INR',
3
+ '$': 'USD'
4
+ };
5
+ const NUMBER_CAPTURE = '(\\d{1,3}(?:,\\d{2,3})*(?:\\.\\d+)?|\\d+(?:\\.\\d+)?)';
6
+ export const parseAmount = (input) => {
7
+ const text = input.trim();
8
+ const symbolBeforeRegex = new RegExp(`([₹$])\\s*${NUMBER_CAPTURE}`);
9
+ const symbolBeforeMatch = text.match(symbolBeforeRegex);
10
+ if (symbolBeforeMatch) {
11
+ const symbol = symbolBeforeMatch[1];
12
+ const amountRaw = symbolBeforeMatch[2].replace(/,/g, '');
13
+ const amount = Number.parseFloat(amountRaw);
14
+ if (!Number.isNaN(amount)) {
15
+ return {
16
+ amount,
17
+ currency: SYMBOL_TO_CURRENCY[symbol],
18
+ matchedText: symbolBeforeMatch[0]
19
+ };
20
+ }
21
+ }
22
+ const numberRegex = new RegExp(`\\b${NUMBER_CAPTURE}\\b`, 'g');
23
+ let numberMatch = numberRegex.exec(text);
24
+ while (numberMatch) {
25
+ const fullMatch = numberMatch[0];
26
+ const nextSlice = text.slice(numberMatch.index + fullMatch.length);
27
+ const ordinalSuffix = nextSlice.match(/^(st|nd|rd|th)\b/i);
28
+ if (!ordinalSuffix) {
29
+ const amountRaw = numberMatch[1].replace(/,/g, '');
30
+ const amount = Number.parseFloat(amountRaw);
31
+ if (!Number.isNaN(amount)) {
32
+ return {
33
+ amount,
34
+ matchedText: fullMatch
35
+ };
36
+ }
37
+ }
38
+ numberMatch = numberRegex.exec(text);
39
+ }
40
+ return {};
41
+ };
42
+ //# sourceMappingURL=parseAmount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseAmount.js","sourceRoot":"","sources":["../../src/parser/parseAmount.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAA2B;IACjD,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;CACX,CAAC;AAEF,MAAM,cAAc,GAAG,uDAAuD,CAAC;AAE/E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAqB,EAAE;IAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE1B,MAAM,iBAAiB,GAAG,IAAI,MAAM,CAAC,aAAa,cAAc,EAAE,CAAC,CAAC;IACpE,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACxD,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAE5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,MAAM;gBACN,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC;gBACpC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;aAClC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,MAAM,cAAc,KAAK,EAAE,GAAG,CAAC,CAAC;IAC/D,IAAI,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,WAAW,EAAE,CAAC;QACnB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACnE,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAC3D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAE5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,OAAO;oBACL,MAAM;oBACN,WAAW,EAAE,SAAS;iBACvB,CAAC;YACJ,CAAC;QACH,CAAC;QAED,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ParseCategoryResult } from '../types';
2
+ export declare const parseCategory: (input: string, categories: string[]) => ParseCategoryResult;
3
+ //# sourceMappingURL=parseCategory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCategory.d.ts","sourceRoot":"","sources":["../../src/parser/parseCategory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAUpD,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,YAAY,MAAM,EAAE,KAAG,mBA4BnE,CAAC"}
@@ -0,0 +1,35 @@
1
+ const DEFAULT_CATEGORY_ALIASES = {
2
+ food: ['lunch', 'dinner', 'breakfast', 'meal'],
3
+ groceries: ['grocery'],
4
+ coffee: ['cafe']
5
+ };
6
+ const toLowerTrim = (value) => value.trim().toLowerCase();
7
+ export const parseCategory = (input, categories) => {
8
+ var _a;
9
+ const text = input.toLowerCase();
10
+ const normalizedCategories = categories.map(toLowerTrim);
11
+ for (const category of normalizedCategories) {
12
+ const exactMatch = text.match(new RegExp(`\\b${escapeRegex(category)}\\b`, 'i'));
13
+ if (exactMatch) {
14
+ return {
15
+ category,
16
+ matchedText: exactMatch[0]
17
+ };
18
+ }
19
+ }
20
+ for (const category of normalizedCategories) {
21
+ const aliases = (_a = DEFAULT_CATEGORY_ALIASES[category]) !== null && _a !== void 0 ? _a : [];
22
+ for (const alias of aliases) {
23
+ const aliasMatch = text.match(new RegExp(`\\b${escapeRegex(alias)}\\b`, 'i'));
24
+ if (aliasMatch) {
25
+ return {
26
+ category,
27
+ matchedText: aliasMatch[0]
28
+ };
29
+ }
30
+ }
31
+ }
32
+ return {};
33
+ };
34
+ const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
35
+ //# sourceMappingURL=parseCategory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCategory.js","sourceRoot":"","sources":["../../src/parser/parseCategory.ts"],"names":[],"mappings":"AAEA,MAAM,wBAAwB,GAA6B;IACzD,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC;IAC9C,SAAS,EAAE,CAAC,SAAS,CAAC;IACtB,MAAM,EAAE,CAAC,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAE1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,UAAoB,EAAuB,EAAE;;IACxF,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,oBAAoB,GAAG,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAEzD,KAAK,MAAM,QAAQ,IAAI,oBAAoB,EAAE,CAAC;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QACjF,IAAI,UAAU,EAAE,CAAC;YACf,OAAO;gBACL,QAAQ;gBACR,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;aAC3B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,oBAAoB,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAA,wBAAwB,CAAC,QAAQ,CAAC,mCAAI,EAAE,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9E,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO;oBACL,QAAQ;oBACR,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;iBAC3B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ParseDateResult } from '../types';
2
+ export declare const parseDate: (input: string, locale?: string, referenceDate?: Date) => ParseDateResult;
3
+ //# sourceMappingURL=parseDate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseDate.d.ts","sourceRoot":"","sources":["../../src/parser/parseDate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAUhD,eAAO,MAAM,SAAS,GACpB,OAAO,MAAM,EACb,eAAgB,EAChB,oBAA0B,KACzB,eAgBF,CAAC"}
@@ -0,0 +1,24 @@
1
+ import * as chrono from 'chrono-node';
2
+ const getChronoForLocale = (locale) => {
3
+ if (locale.toLowerCase().startsWith('en')) {
4
+ return chrono;
5
+ }
6
+ return chrono;
7
+ };
8
+ export const parseDate = (input, locale = 'en-IN', referenceDate = new Date()) => {
9
+ const chronoParser = getChronoForLocale(locale);
10
+ const parsedResults = chronoParser.parse(input, referenceDate);
11
+ if (parsedResults.length > 0) {
12
+ const bestMatch = parsedResults[0];
13
+ return {
14
+ date: bestMatch.start.date(),
15
+ matchedText: bestMatch.text,
16
+ isDefaultToday: false
17
+ };
18
+ }
19
+ return {
20
+ date: referenceDate,
21
+ isDefaultToday: true
22
+ };
23
+ };
24
+ //# sourceMappingURL=parseDate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseDate.js","sourceRoot":"","sources":["../../src/parser/parseDate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAItC,MAAM,kBAAkB,GAAG,CAAC,MAAc,EAAiB,EAAE;IAC3D,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,KAAa,EACb,MAAM,GAAG,OAAO,EAChB,aAAa,GAAG,IAAI,IAAI,EAAE,EACT,EAAE;IACnB,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAC/D,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE;YAC5B,WAAW,EAAE,SAAS,CAAC,IAAI;YAC3B,cAAc,EAAE,KAAK;SACtB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,cAAc,EAAE,IAAI;KACrB,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,37 @@
1
+ export type ExpenseResult = {
2
+ amount: number;
3
+ currency: string;
4
+ category?: string;
5
+ date: Date;
6
+ note?: string;
7
+ confidence: number;
8
+ };
9
+ export type NLPExpenseInputProps = {
10
+ locale?: string;
11
+ currency: string;
12
+ categories: string[];
13
+ onParsed: (result: ExpenseResult) => void;
14
+ placeholder?: string;
15
+ debounceMs?: number;
16
+ showHint?: boolean;
17
+ };
18
+ export type ParseContext = {
19
+ locale: string;
20
+ fallbackCurrency: string;
21
+ categories: string[];
22
+ };
23
+ export type ParseAmountResult = {
24
+ amount?: number;
25
+ currency?: string;
26
+ matchedText?: string;
27
+ };
28
+ export type ParseDateResult = {
29
+ date: Date;
30
+ matchedText?: string;
31
+ isDefaultToday: boolean;
32
+ };
33
+ export type ParseCategoryResult = {
34
+ category?: string;
35
+ matchedText?: string;
36
+ };
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "react-native-nlp-expense",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight offline rule-based NLP parser for React Native expense input.",
5
+ "license": "MIT",
6
+ "author": "Yuyutsu",
7
+
8
+ "type": "module",
9
+
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+
20
+ "files": [
21
+ "dist"
22
+ ],
23
+
24
+ "react-native": "src/index.ts",
25
+
26
+ "sideEffects": false,
27
+
28
+ "scripts": {
29
+ "build": "tsc",
30
+ "prepare": "npm run build",
31
+ "lint": "tsc --noEmit",
32
+ "test": "jest --passWithNoTests"
33
+ },
34
+
35
+ "peerDependencies": {
36
+ "react": ">=18.0.0",
37
+ "react-native": ">=0.72.0"
38
+ },
39
+
40
+ "dependencies": {
41
+ "chrono-node": "^2.9.0"
42
+ },
43
+
44
+ "devDependencies": {
45
+ "typescript": "^5.3.0",
46
+
47
+ "react": "18.3.1",
48
+ "react-native": "0.76.5",
49
+ "react-test-renderer": "18.3.1",
50
+
51
+ "jest": "^29.7.0",
52
+ "babel-jest": "^29.7.0",
53
+ "ts-jest": "^29.4.6",
54
+ "@types/jest": "^29.5.0",
55
+
56
+ "@testing-library/react-native": "^12.9.0",
57
+
58
+ "@types/react": "^18.3.0",
59
+ "@types/react-native": "^0.73.0"
60
+ },
61
+
62
+ "jest": {
63
+ "preset": "react-native",
64
+ "testMatch": [
65
+ "**/__tests__/**/*.test.(ts|tsx)"
66
+ ],
67
+ "moduleFileExtensions": [
68
+ "ts",
69
+ "tsx",
70
+ "js",
71
+ "jsx",
72
+ "json"
73
+ ],
74
+ "transformIgnorePatterns": [
75
+ "node_modules/(?!(react-native|@react-native|@testing-library)/)"
76
+ ]
77
+ },
78
+
79
+ "repository": {
80
+ "type": "git",
81
+ "url": "https://github.com/Yuyutsu/react-native-nlp-expense.git"
82
+ },
83
+
84
+ "homepage": "https://github.com/Yuyutsu/react-native-nlp-expense#readme",
85
+
86
+ "bugs": {
87
+ "url": "https://github.com/Yuyutsu/react-native-nlp-expense/issues"
88
+ },
89
+
90
+ "keywords": [
91
+ "react-native",
92
+ "nlp",
93
+ "expense",
94
+ "parser",
95
+ "typescript",
96
+ "offline"
97
+ ],
98
+
99
+ "engines": {
100
+ "node": ">=18"
101
+ }
102
+ }