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 +206 -0
- package/dist/NLPExpenseInput.d.ts +4 -0
- package/dist/NLPExpenseInput.d.ts.map +1 -0
- package/dist/NLPExpenseInput.js +62 -0
- package/dist/NLPExpenseInput.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/index.d.ts +7 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +79 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/parseAmount.d.ts +3 -0
- package/dist/parser/parseAmount.d.ts.map +1 -0
- package/dist/parser/parseAmount.js +42 -0
- package/dist/parser/parseAmount.js.map +1 -0
- package/dist/parser/parseCategory.d.ts +3 -0
- package/dist/parser/parseCategory.d.ts.map +1 -0
- package/dist/parser/parseCategory.js +35 -0
- package/dist/parser/parseCategory.js.map +1 -0
- package/dist/parser/parseDate.d.ts +3 -0
- package/dist/parser/parseDate.d.ts.map +1 -0
- package/dist/parser/parseDate.js +24 -0
- package/dist/parser/parseDate.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +102 -0
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 @@
|
|
|
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|