feat:新增ai分析,新增储值账户,优化通知规则
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import notificationRulesSource from '../config/notificationRules.js'
|
||||
import { DEFAULT_LEDGER_BY_ENTRY_TYPE } from '../config/ledger.js'
|
||||
import notificationRulesSource from '../config/notificationRules.js'
|
||||
|
||||
const fallbackRules = notificationRulesSource.map((rule) => ({
|
||||
...rule,
|
||||
@@ -10,13 +11,36 @@ const fallbackRules = notificationRulesSource.map((rule) => ({
|
||||
const amountPatterns = [
|
||||
/(?:¥|¥|RMB|CNY|人民币)\s*(-?\d+(?:\.\d{1,2})?)/i,
|
||||
/(-?\d+(?:\.\d{1,2})?)\s*(?:元|块|块钱)/,
|
||||
/(?:金额|实付|扣费|扣款|消费|支出|收入|收款|到账|入账|转入|转出|支付|票价|车费|本次乘车)\D{0,8}?(-?\d+(?:\.\d{1,2})?)/,
|
||||
/(?:金额|实付|扣费|扣款|消费|支出|收入|收款|到账|入账|转入|转出|支付|票价|车费|本次乘车|充值)\D{0,8}?(-?\d+(?:\.\d{1,2})?)/,
|
||||
/(-?\d+(?:\.\d{1,2})?)\s*(?:元|块|块钱)?\s*(?:已支付|已收款|到账|入账)/,
|
||||
]
|
||||
|
||||
let cachedRules = [...fallbackRules]
|
||||
let remoteRuleFetcher = async () => []
|
||||
|
||||
const buildLedgerPayload = (rule, notification, merchant) => {
|
||||
const entryType = rule?.entryType || 'expense'
|
||||
const defaults = DEFAULT_LEDGER_BY_ENTRY_TYPE[entryType] || DEFAULT_LEDGER_BY_ENTRY_TYPE.expense
|
||||
const channelName = notification?.channel || ''
|
||||
|
||||
const resolveName = (field, fromChannelFlag) => {
|
||||
if (rule?.[fromChannelFlag] && channelName) return channelName
|
||||
return rule?.[field] || defaults[field] || ''
|
||||
}
|
||||
|
||||
return {
|
||||
entryType,
|
||||
fundSourceType: rule?.fundSourceType || defaults.fundSourceType,
|
||||
fundSourceName: resolveName('fundSourceName', 'fundSourceNameFromChannel'),
|
||||
fundTargetType: rule?.fundTargetType || defaults.fundTargetType,
|
||||
fundTargetName:
|
||||
resolveName('fundTargetName', 'fundTargetNameFromChannel') ||
|
||||
(entryType === 'expense' ? merchant : defaults.fundTargetName),
|
||||
impactExpense: rule?.impactExpense ?? defaults.impactExpense,
|
||||
impactIncome: rule?.impactIncome ?? defaults.impactIncome,
|
||||
}
|
||||
}
|
||||
|
||||
const buildEmptyTransaction = (notification, options = {}) => ({
|
||||
id: options.id,
|
||||
merchant: options.merchant || 'Unknown',
|
||||
@@ -25,6 +49,7 @@ const buildEmptyTransaction = (notification, options = {}) => ({
|
||||
date: new Date(options.date || notification?.createdAt || new Date().toISOString()).toISOString(),
|
||||
note: notification?.text || '',
|
||||
syncStatus: 'pending',
|
||||
...DEFAULT_LEDGER_BY_ENTRY_TYPE.expense,
|
||||
})
|
||||
|
||||
export const setRemoteRuleFetcher = (fetcher) => {
|
||||
@@ -78,6 +103,13 @@ const looksLikeAmount = (raw = '') => {
|
||||
return /^[-\d.,]+\s*(?:元|块|块钱|¥|¥|人民币)?$/i.test(value)
|
||||
}
|
||||
|
||||
const hasTransitRouteAmountFallback = (rule, text = '') => {
|
||||
if (rule?.id !== 'transit-card-expense') return false
|
||||
return /[\u4e00-\u9fa5]{2,}\s*(?:-|->|→|至|到)\s*[\u4e00-\u9fa5]{2,}[::]?\s*\d+(?:\.\d{1,2})?\s*(?:元|块|块钱)/.test(
|
||||
text,
|
||||
)
|
||||
}
|
||||
|
||||
const extractMerchant = (text, rule) => {
|
||||
const content = text || ''
|
||||
|
||||
@@ -134,7 +166,8 @@ const matchRule = (notification, rule) => {
|
||||
const keywordHit = !rule.keywords?.length || rule.keywords.some((word) => text.includes(word))
|
||||
const requiredTextHit =
|
||||
!rule.requiredTextPatterns?.length ||
|
||||
rule.requiredTextPatterns.some((word) => text.includes(word))
|
||||
rule.requiredTextPatterns.some((word) => text.includes(word)) ||
|
||||
hasTransitRouteAmountFallback(rule, text)
|
||||
|
||||
return channelHit && keywordHit && requiredTextHit
|
||||
}
|
||||
@@ -161,8 +194,7 @@ export const transformNotificationToTransaction = (notification, options = {}) =
|
||||
}
|
||||
|
||||
const direction = options.direction || rule.direction || 'expense'
|
||||
const normalizedAmount =
|
||||
direction === 'income' ? Math.abs(amountFromText) : -Math.abs(amountFromText)
|
||||
const normalizedAmount = direction === 'income' ? Math.abs(amountFromText) : -Math.abs(amountFromText)
|
||||
|
||||
let merchant = options.merchant || extractMerchant(notification?.text || '', rule) || 'Unknown'
|
||||
if (merchant === 'Unknown' && rule.defaultMerchant) {
|
||||
@@ -170,6 +202,7 @@ export const transformNotificationToTransaction = (notification, options = {}) =
|
||||
}
|
||||
|
||||
const date = options.date || notification?.createdAt || new Date().toISOString()
|
||||
const ledger = buildLedgerPayload(rule, notification, merchant)
|
||||
|
||||
const transaction = {
|
||||
id: options.id,
|
||||
@@ -179,6 +212,7 @@ export const transformNotificationToTransaction = (notification, options = {}) =
|
||||
date: new Date(date).toISOString(),
|
||||
note: notification?.text || '',
|
||||
syncStatus: 'pending',
|
||||
...ledger,
|
||||
}
|
||||
|
||||
const requiresConfirmation =
|
||||
|
||||
Reference in New Issue
Block a user