fix:修复通知调试状态和重复命中问题
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package com.echo.app.notification
|
package com.echo.app.notification
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
@@ -118,6 +118,14 @@ object NotificationDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasRawNotification(db: SQLiteDatabase, sourceId: String): Boolean {
|
||||||
|
if (sourceId.isBlank()) return false
|
||||||
|
val cursor = db.rawQuery("SELECT 1 FROM notifications_raw WHERE source_id = ? LIMIT 1", arrayOf(sourceId))
|
||||||
|
cursor.use {
|
||||||
|
return it.moveToFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun ensureSchema(db: SQLiteDatabase) {
|
private fun ensureSchema(db: SQLiteDatabase) {
|
||||||
if (schemaInitialized) return
|
if (schemaInitialized) return
|
||||||
db.execSQL(SQL_CREATE_TRANSACTIONS)
|
db.execSQL(SQL_CREATE_TRANSACTIONS)
|
||||||
@@ -140,6 +148,10 @@ object NotificationDb {
|
|||||||
val db = getHelper(context).writableDatabase
|
val db = getHelper(context).writableDatabase
|
||||||
ensureSchema(db)
|
ensureSchema(db)
|
||||||
|
|
||||||
|
if (hasRawNotification(db, raw.sourceId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val rawId = UUID.randomUUID().toString()
|
val rawId = UUID.randomUUID().toString()
|
||||||
var status = "unmatched"
|
var status = "unmatched"
|
||||||
var ruleId: String? = null
|
var ruleId: String? = null
|
||||||
@@ -174,6 +186,8 @@ object NotificationDb {
|
|||||||
val merchantKnown = parsed.merchant.isNotBlank() && parsed.merchant != "Unknown"
|
val merchantKnown = parsed.merchant.isNotBlank() && parsed.merchant != "Unknown"
|
||||||
val requiresConfirmation = !(parsed.autoCapture && hasAmount && merchantKnown)
|
val requiresConfirmation = !(parsed.autoCapture && hasAmount && merchantKnown)
|
||||||
|
|
||||||
|
status = if (requiresConfirmation) "review" else "matched"
|
||||||
|
|
||||||
if (!requiresConfirmation) {
|
if (!requiresConfirmation) {
|
||||||
val signedAmount = if (parsed.isExpense) -abs(parsed.amount) else abs(parsed.amount)
|
val signedAmount = if (parsed.isExpense) -abs(parsed.amount) else abs(parsed.amount)
|
||||||
transactionId = UUID.randomUUID().toString()
|
transactionId = UUID.randomUUID().toString()
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ internal object NotificationRuleData {
|
|||||||
impactIncome = false,
|
impactIncome = false,
|
||||||
defaultCategory = "Expense",
|
defaultCategory = "Expense",
|
||||||
autoCapture = false,
|
autoCapture = false,
|
||||||
merchantPattern = Regex("(?:商户|商家|消费商户)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)"),
|
merchantPattern = Regex("(?:在【([^】]+)】发生|商户|商家|消费商户)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)?"),
|
||||||
defaultMerchant = null,
|
defaultMerchant = null,
|
||||||
),
|
),
|
||||||
NotificationRuleDefinition(
|
NotificationRuleDefinition(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.echo.app.notification
|
package com.echo.app.notification
|
||||||
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ object NotificationRuleEngine {
|
|||||||
rule.merchantPattern?.let { regex ->
|
rule.merchantPattern?.let { regex ->
|
||||||
val match = regex.find(content)
|
val match = regex.find(content)
|
||||||
if (match != null && match.groupValues.size > 1) {
|
if (match != null && match.groupValues.size > 1) {
|
||||||
val candidate = match.groupValues[1].trim()
|
val candidate = match.groupValues.drop(1).firstOrNull { it.isNotBlank() }?.trim().orEmpty()
|
||||||
if (candidate.isNotEmpty()) return candidate
|
if (candidate.isNotEmpty()) return candidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
const transitStoredValueChannels = [
|
const transitStoredValueChannels = [
|
||||||
'杭州通互联互通卡',
|
'\u676d\u5dde\u901a\u4e92\u8054\u4e92\u901a\u5361',
|
||||||
'杭州通',
|
'\u676d\u5dde\u901a',
|
||||||
'北京一卡通',
|
'\u5317\u4eac\u4e00\u5361\u901a',
|
||||||
'上海公共交通卡',
|
'\u4e0a\u6d77\u516c\u5171\u4ea4\u901a\u5361',
|
||||||
'深圳通',
|
'\u6df1\u5733\u901a',
|
||||||
'苏州公交卡',
|
'\u82cf\u5dde\u516c\u4ea4\u5361',
|
||||||
'交通联合',
|
'\u4ea4\u901a\u8054\u5408',
|
||||||
'乘车码',
|
'\u4e58\u8f66\u7801',
|
||||||
'地铁',
|
'\u5730\u94c1',
|
||||||
'公交',
|
'\u516c\u4ea4',
|
||||||
'Mi Pay',
|
'Mi Pay',
|
||||||
'Huawei Pay',
|
'Huawei Pay',
|
||||||
'华为钱包',
|
'\u534e\u4e3a\u94b1\u5305',
|
||||||
'小米钱包',
|
'\u5c0f\u7c73\u94b1\u5305',
|
||||||
'OPPO 钱包',
|
'OPPO \u94b1\u5305',
|
||||||
]
|
]
|
||||||
|
|
||||||
const notificationRules = [
|
const notificationRules = [
|
||||||
{
|
{
|
||||||
id: 'alipay-expense',
|
id: 'alipay-expense',
|
||||||
label: '支付宝消费',
|
label: '\u652f\u4ed8\u5b9d\u6d88\u8d39',
|
||||||
channels: ['支付宝', 'Alipay'],
|
channels: ['\u652f\u4ed8\u5b9d', 'Alipay'],
|
||||||
keywords: ['支付', '扣款', '支出', '消费', '成功支付'],
|
keywords: ['\u652f\u4ed8', '\u6263\u6b3e', '\u652f\u51fa', '\u6d88\u8d39', '\u6210\u529f\u652f\u4ed8'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'expense',
|
direction: 'expense',
|
||||||
entryType: 'expense',
|
entryType: 'expense',
|
||||||
fundSourceType: 'cash',
|
fundSourceType: 'cash',
|
||||||
fundSourceName: '支付宝余额',
|
fundSourceName: '\u652f\u4ed8\u5b9d\u4f59\u989d',
|
||||||
fundTargetType: 'merchant',
|
fundTargetType: 'merchant',
|
||||||
fundTargetName: '',
|
fundTargetName: '',
|
||||||
impactExpense: true,
|
impactExpense: true,
|
||||||
@@ -34,43 +34,43 @@ const notificationRules = [
|
|||||||
defaultCategory: 'Food',
|
defaultCategory: 'Food',
|
||||||
autoCapture: false,
|
autoCapture: false,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:向|收款方|商户)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern: '(?:\u5411|\u6536\u6b3e\u65b9|\u5546\u6237)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
||||||
flags: 'i',
|
flags: 'i',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'alipay-income',
|
id: 'alipay-income',
|
||||||
label: '支付宝收款',
|
label: '\u652f\u4ed8\u5b9d\u6536\u6b3e',
|
||||||
channels: ['支付宝', 'Alipay'],
|
channels: ['\u652f\u4ed8\u5b9d', 'Alipay'],
|
||||||
keywords: ['到账', '收入', '收款', '入账'],
|
keywords: ['\u5230\u8d26', '\u6536\u5165', '\u6536\u6b3e', '\u5165\u8d26'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'income',
|
direction: 'income',
|
||||||
entryType: 'income',
|
entryType: 'income',
|
||||||
fundSourceType: 'external',
|
fundSourceType: 'external',
|
||||||
fundSourceName: '外部转入',
|
fundSourceName: '\u5916\u90e8\u8f6c\u5165',
|
||||||
fundTargetType: 'cash',
|
fundTargetType: 'cash',
|
||||||
fundTargetName: '支付宝余额',
|
fundTargetName: '\u652f\u4ed8\u5b9d\u4f59\u989d',
|
||||||
impactExpense: false,
|
impactExpense: false,
|
||||||
impactIncome: true,
|
impactIncome: true,
|
||||||
defaultCategory: 'Income',
|
defaultCategory: 'Income',
|
||||||
autoCapture: true,
|
autoCapture: true,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:来自|收款方|付款方|来源)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern: '(?:\u6765\u81ea|\u6536\u6b3e\u65b9|\u4ed8\u6b3e\u65b9|\u6765\u6e90)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
||||||
flags: 'i',
|
flags: 'i',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'wechat-expense',
|
id: 'wechat-expense',
|
||||||
label: '微信消费',
|
label: '\u5fae\u4fe1\u6d88\u8d39',
|
||||||
channels: ['微信支付', '微信', 'WeChat'],
|
channels: ['\u5fae\u4fe1\u652f\u4ed8', '\u5fae\u4fe1', 'WeChat'],
|
||||||
keywords: ['支付', '支出', '扣款', '消费成功'],
|
keywords: ['\u652f\u4ed8', '\u652f\u51fa', '\u6263\u6b3e', '\u6d88\u8d39\u6210\u529f'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'expense',
|
direction: 'expense',
|
||||||
entryType: 'expense',
|
entryType: 'expense',
|
||||||
fundSourceType: 'cash',
|
fundSourceType: 'cash',
|
||||||
fundSourceName: '微信余额',
|
fundSourceName: '\u5fae\u4fe1\u4f59\u989d',
|
||||||
fundTargetType: 'merchant',
|
fundTargetType: 'merchant',
|
||||||
fundTargetName: '',
|
fundTargetName: '',
|
||||||
impactExpense: true,
|
impactExpense: true,
|
||||||
@@ -78,93 +78,93 @@ const notificationRules = [
|
|||||||
defaultCategory: 'Groceries',
|
defaultCategory: 'Groceries',
|
||||||
autoCapture: false,
|
autoCapture: false,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:向|收款方|商户)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern: '(?:\u5411|\u6536\u6b3e\u65b9|\u5546\u6237)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
||||||
flags: '',
|
flags: '',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'wechat-income',
|
id: 'wechat-income',
|
||||||
label: '微信收款',
|
label: '\u5fae\u4fe1\u6536\u6b3e',
|
||||||
channels: ['微信支付', '微信', 'WeChat'],
|
channels: ['\u5fae\u4fe1\u652f\u4ed8', '\u5fae\u4fe1', 'WeChat'],
|
||||||
keywords: ['收款', '到账', '入账', '转入'],
|
keywords: ['\u6536\u6b3e', '\u5230\u8d26', '\u5165\u8d26', '\u8f6c\u5165'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'income',
|
direction: 'income',
|
||||||
entryType: 'income',
|
entryType: 'income',
|
||||||
fundSourceType: 'external',
|
fundSourceType: 'external',
|
||||||
fundSourceName: '外部转入',
|
fundSourceName: '\u5916\u90e8\u8f6c\u5165',
|
||||||
fundTargetType: 'cash',
|
fundTargetType: 'cash',
|
||||||
fundTargetName: '微信余额',
|
fundTargetName: '\u5fae\u4fe1\u4f59\u989d',
|
||||||
impactExpense: false,
|
impactExpense: false,
|
||||||
impactIncome: true,
|
impactIncome: true,
|
||||||
defaultCategory: 'Income',
|
defaultCategory: 'Income',
|
||||||
autoCapture: true,
|
autoCapture: true,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:来自|收款方|付款方|来源)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern: '(?:\u6765\u81ea|\u6536\u6b3e\u65b9|\u4ed8\u6b3e\u65b9|\u6765\u6e90)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
||||||
flags: '',
|
flags: '',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bank-income',
|
id: 'bank-income',
|
||||||
label: '银行入账',
|
label: '\u94f6\u884c\u5165\u8d26',
|
||||||
channels: [
|
channels: [
|
||||||
'招商银行',
|
'\u62db\u5546\u94f6\u884c',
|
||||||
'中国银行',
|
'\u4e2d\u56fd\u94f6\u884c',
|
||||||
'建设银行',
|
'\u5efa\u8bbe\u94f6\u884c',
|
||||||
'工商银行',
|
'\u5de5\u5546\u94f6\u884c',
|
||||||
'农业银行',
|
'\u519c\u4e1a\u94f6\u884c',
|
||||||
'交通银行',
|
'\u4ea4\u901a\u94f6\u884c',
|
||||||
'浦发银行',
|
'\u6d66\u53d1\u94f6\u884c',
|
||||||
'兴业银行',
|
'\u5174\u4e1a\u94f6\u884c',
|
||||||
'光大银行',
|
'\u5149\u5927\u94f6\u884c',
|
||||||
'中信银行',
|
'\u4e2d\u4fe1\u94f6\u884c',
|
||||||
'广发银行',
|
'\u5e7f\u53d1\u94f6\u884c',
|
||||||
'平安银行',
|
'\u5e73\u5b89\u94f6\u884c',
|
||||||
],
|
],
|
||||||
keywords: ['工资', '薪资', '代发', '到账', '入账', '收入'],
|
keywords: ['\u5de5\u8d44', '\u85aa\u8d44', '\u4ee3\u53d1', '\u5230\u8d26', '\u5165\u8d26', '\u6536\u5165'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'income',
|
direction: 'income',
|
||||||
entryType: 'income',
|
entryType: 'income',
|
||||||
fundSourceType: 'external',
|
fundSourceType: 'external',
|
||||||
fundSourceName: '外部转入',
|
fundSourceName: '\u5916\u90e8\u8f6c\u5165',
|
||||||
fundTargetType: 'bank',
|
fundTargetType: 'bank',
|
||||||
fundTargetName: '银行卡',
|
fundTargetName: '\u94f6\u884c\u5361',
|
||||||
impactExpense: false,
|
impactExpense: false,
|
||||||
impactIncome: true,
|
impactIncome: true,
|
||||||
defaultCategory: 'Income',
|
defaultCategory: 'Income',
|
||||||
autoCapture: true,
|
autoCapture: true,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:来自|付款方|来源)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern: '(?:\u6765\u81ea|\u4ed8\u6b3e\u65b9|\u6765\u6e90)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
||||||
flags: '',
|
flags: '',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bank-expense',
|
id: 'bank-expense',
|
||||||
label: '银行卡支出',
|
label: '\u94f6\u884c\u5361\u652f\u51fa',
|
||||||
channels: [
|
channels: [
|
||||||
'招商银行',
|
'\u62db\u5546\u94f6\u884c',
|
||||||
'中国银行',
|
'\u4e2d\u56fd\u94f6\u884c',
|
||||||
'建设银行',
|
'\u5efa\u8bbe\u94f6\u884c',
|
||||||
'工商银行',
|
'\u5de5\u5546\u94f6\u884c',
|
||||||
'农业银行',
|
'\u519c\u4e1a\u94f6\u884c',
|
||||||
'交通银行',
|
'\u4ea4\u901a\u94f6\u884c',
|
||||||
'浦发银行',
|
'\u6d66\u53d1\u94f6\u884c',
|
||||||
'兴业银行',
|
'\u5174\u4e1a\u94f6\u884c',
|
||||||
'光大银行',
|
'\u5149\u5927\u94f6\u884c',
|
||||||
'中信银行',
|
'\u4e2d\u4fe1\u94f6\u884c',
|
||||||
'广发银行',
|
'\u5e7f\u53d1\u94f6\u884c',
|
||||||
'平安银行',
|
'\u5e73\u5b89\u94f6\u884c',
|
||||||
'借记卡',
|
'\u501f\u8bb0\u5361',
|
||||||
'信用卡',
|
'\u4fe1\u7528\u5361',
|
||||||
],
|
],
|
||||||
keywords: ['支出', '消费', '扣款', '刷卡消费', '银联消费'],
|
keywords: ['\u652f\u51fa', '\u6d88\u8d39', '\u6263\u6b3e', '\u5237\u5361\u6d88\u8d39', '\u94f6\u8054\u6d88\u8d39'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'expense',
|
direction: 'expense',
|
||||||
entryType: 'expense',
|
entryType: 'expense',
|
||||||
fundSourceType: 'bank',
|
fundSourceType: 'bank',
|
||||||
fundSourceName: '银行卡',
|
fundSourceName: '\u94f6\u884c\u5361',
|
||||||
fundTargetType: 'merchant',
|
fundTargetType: 'merchant',
|
||||||
fundTargetName: '',
|
fundTargetName: '',
|
||||||
impactExpense: true,
|
impactExpense: true,
|
||||||
@@ -172,21 +172,22 @@ const notificationRules = [
|
|||||||
defaultCategory: 'Expense',
|
defaultCategory: 'Expense',
|
||||||
autoCapture: false,
|
autoCapture: false,
|
||||||
merchantPattern: {
|
merchantPattern: {
|
||||||
pattern: '(?:商户|商家|消费商户)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)',
|
pattern:
|
||||||
|
'(?:\u5728【([^】]+)】\u53d1\u751f|\u5546\u6237|\u5546\u5bb6|\u6d88\u8d39\u5546\u6237)\\s*([\\u4e00-\\u9fa5A-Za-z0-9\\s&-]+)?',
|
||||||
flags: '',
|
flags: '',
|
||||||
},
|
},
|
||||||
defaultMerchant: '',
|
defaultMerchant: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stored-value-topup',
|
id: 'stored-value-topup',
|
||||||
label: '储值账户充值',
|
label: '\u50a8\u503c\u8d26\u6237\u5145\u503c',
|
||||||
channels: transitStoredValueChannels,
|
channels: transitStoredValueChannels,
|
||||||
keywords: ['充值', '充值成功', '充值到账', '钱包充值', '卡内充值'],
|
keywords: ['\u5145\u503c', '\u5145\u503c\u6210\u529f', '\u5145\u503c\u5230\u8d26', '\u94b1\u5305\u5145\u503c', '\u5361\u5185\u5145\u503c'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'expense',
|
direction: 'expense',
|
||||||
entryType: 'transfer',
|
entryType: 'transfer',
|
||||||
fundSourceType: 'cash',
|
fundSourceType: 'cash',
|
||||||
fundSourceName: '现金账户',
|
fundSourceName: '\u73b0\u91d1\u8d26\u6237',
|
||||||
fundTargetType: 'stored_value',
|
fundTargetType: 'stored_value',
|
||||||
fundTargetName: '',
|
fundTargetName: '',
|
||||||
fundTargetNameFromChannel: true,
|
fundTargetNameFromChannel: true,
|
||||||
@@ -195,13 +196,13 @@ const notificationRules = [
|
|||||||
defaultCategory: 'Transfer',
|
defaultCategory: 'Transfer',
|
||||||
autoCapture: true,
|
autoCapture: true,
|
||||||
merchantPattern: null,
|
merchantPattern: null,
|
||||||
defaultMerchant: '储值账户充值',
|
defaultMerchant: '\u50a8\u503c\u8d26\u6237\u5145\u503c',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'stored-value-refund',
|
id: 'stored-value-refund',
|
||||||
label: '储值账户退款',
|
label: '\u50a8\u503c\u8d26\u6237\u9000\u6b3e',
|
||||||
channels: transitStoredValueChannels,
|
channels: transitStoredValueChannels,
|
||||||
keywords: ['退款', '退资', '退卡', '余额退回', '退款成功'],
|
keywords: ['\u9000\u6b3e', '\u9000\u8d44', '\u9000\u5361', '\u4f59\u989d\u9000\u56de', '\u9000\u6b3e\u6210\u529f'],
|
||||||
requiredTextPatterns: [],
|
requiredTextPatterns: [],
|
||||||
direction: 'income',
|
direction: 'income',
|
||||||
entryType: 'transfer',
|
entryType: 'transfer',
|
||||||
@@ -209,20 +210,20 @@ const notificationRules = [
|
|||||||
fundSourceName: '',
|
fundSourceName: '',
|
||||||
fundSourceNameFromChannel: true,
|
fundSourceNameFromChannel: true,
|
||||||
fundTargetType: 'cash',
|
fundTargetType: 'cash',
|
||||||
fundTargetName: '现金账户',
|
fundTargetName: '\u73b0\u91d1\u8d26\u6237',
|
||||||
impactExpense: false,
|
impactExpense: false,
|
||||||
impactIncome: false,
|
impactIncome: false,
|
||||||
defaultCategory: 'Transfer',
|
defaultCategory: 'Transfer',
|
||||||
autoCapture: true,
|
autoCapture: true,
|
||||||
merchantPattern: null,
|
merchantPattern: null,
|
||||||
defaultMerchant: '储值账户退款',
|
defaultMerchant: '\u50a8\u503c\u8d26\u6237\u9000\u6b3e',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transit-card-expense',
|
id: 'transit-card-expense',
|
||||||
label: '公交出行',
|
label: '\u516c\u4ea4\u51fa\u884c',
|
||||||
channels: transitStoredValueChannels,
|
channels: transitStoredValueChannels,
|
||||||
keywords: [],
|
keywords: [],
|
||||||
requiredTextPatterns: ['扣费', '扣款', '支付', '乘车码', '车费', '票价', '本次乘车', '实付', '优惠后'],
|
requiredTextPatterns: ['\u6263\u8d39', '\u6263\u6b3e', '\u652f\u4ed8', '\u4e58\u8f66\u7801', '\u8f66\u8d39', '\u7968\u4ef7', '\u672c\u6b21\u4e58\u8f66', '\u5b9e\u4ed8', '\u4f18\u60e0\u540e'],
|
||||||
direction: 'expense',
|
direction: 'expense',
|
||||||
entryType: 'expense',
|
entryType: 'expense',
|
||||||
fundSourceType: 'stored_value',
|
fundSourceType: 'stored_value',
|
||||||
@@ -238,7 +239,7 @@ const notificationRules = [
|
|||||||
pattern: '([\\u4e00-\\u9fa5]{2,}\\s*(?:-|->|→|至|到)\\s*[\\u4e00-\\u9fa5]{2,})',
|
pattern: '([\\u4e00-\\u9fa5]{2,}\\s*(?:-|->|→|至|到)\\s*[\\u4e00-\\u9fa5]{2,})',
|
||||||
flags: '',
|
flags: '',
|
||||||
},
|
},
|
||||||
defaultMerchant: '公交/地铁出行',
|
defaultMerchant: '\u516c\u4ea4/\u5730\u94c1\u51fa\u884c',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,28 @@ const mapRow = (row) => ({
|
|||||||
errorMessage: row.error_message || '',
|
errorMessage: row.error_message || '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const dedupeRecords = (rows = []) => {
|
||||||
|
const bucket = new Map()
|
||||||
|
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const item = mapRow(row)
|
||||||
|
const key = item.sourceId || item.id
|
||||||
|
if (!bucket.has(key)) {
|
||||||
|
bucket.set(key, item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = bucket.get(key)
|
||||||
|
const existingScore = Number(Boolean(existing.transactionId)) + Number(existing.status === NOTIFICATION_DEBUG_STATUS.matched) + Number(Boolean(existing.ruleId))
|
||||||
|
const nextScore = Number(Boolean(item.transactionId)) + Number(item.status === NOTIFICATION_DEBUG_STATUS.matched) + Number(Boolean(item.ruleId))
|
||||||
|
if (nextScore > existingScore) {
|
||||||
|
bucket.set(key, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(bucket.values())
|
||||||
|
}
|
||||||
|
|
||||||
const getLatestBySourceId = async (sourceId) => {
|
const getLatestBySourceId = async (sourceId) => {
|
||||||
if (!sourceId) return null
|
if (!sourceId) return null
|
||||||
const db = await getDb()
|
const db = await getDb()
|
||||||
@@ -150,5 +172,5 @@ export const fetchNotificationDebugRecords = async (options = {}) => {
|
|||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
|
|
||||||
return (result?.values || []).map(mapRow)
|
return dedupeRecords(result?.values || [])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DEFAULT_LEDGER_BY_ENTRY_TYPE } from '../config/ledger.js'
|
import { DEFAULT_LEDGER_BY_ENTRY_TYPE } from '../config/ledger.js'
|
||||||
import notificationRulesSource from '../config/notificationRules.js'
|
import notificationRulesSource from '../config/notificationRules.js'
|
||||||
|
|
||||||
const fallbackRules = notificationRulesSource.map((rule) => ({
|
const fallbackRules = notificationRulesSource.map((rule) => ({
|
||||||
@@ -115,7 +115,8 @@ const extractMerchant = (text, rule) => {
|
|||||||
|
|
||||||
if (rule?.merchantPattern instanceof RegExp) {
|
if (rule?.merchantPattern instanceof RegExp) {
|
||||||
const matched = content.match(rule.merchantPattern)
|
const matched = content.match(rule.merchantPattern)
|
||||||
if (matched?.[1]) return matched[1].trim()
|
const candidate = matched?.slice(1).find((item) => item && String(item).trim())
|
||||||
|
if (candidate) return candidate.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeMatch = content.match(/([\u4e00-\u9fa5]{2,}\s*(?:-|->|→|至|到)\s*[\u4e00-\u9fa5]{2,})/)
|
const routeMatch = content.match(/([\u4e00-\u9fa5]{2,}\s*(?:-|->|→|至|到)\s*[\u4e00-\u9fa5]{2,})/)
|
||||||
|
|||||||
Reference in New Issue
Block a user