优化邮件解析正则表达式,增强交易信息提取能力;调整界面样式,增加删除功能
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Failing after 5s
Docker Build & Deploy / Deploy to Production (push) Has been skipped

This commit is contained in:
2025-12-31 11:49:25 +08:00
parent e7d5c076d4
commit 545796aba9
5 changed files with 115 additions and 16 deletions

View File

@@ -35,13 +35,16 @@ public class EmailParseForm95555(
DateTime? occurredAt DateTime? occurredAt
)[]> ParseEmailContentAsync(string emailContent) )[]> ParseEmailContentAsync(string emailContent)
{ {
// 示例1您账户8826于12月31日09:34在财付通-微信支付-这有电快捷支付1.00元余额30.21
// 示例2: 您账户8826于12月31日10:47入账款项人民币1000.00余额人民币1030.21。
var pattern = var pattern =
"您账户(?<card>\\d+)" + "您账户(?<card>\\d+)" + // 卡号
"于.*?" + // 时间等信息统统吞掉 "于(?<time>\\d{1,2}月\\d{1,2}日\\d{1,2}:\\d{2})" + // 交易时间
"(?:(?<type>收入|支出|消费|转入|转出).*?)?" + // 可选 type "(?:(?<type>收入|支出|消费|转入|转出|入账款项))?" + // 交易类型(可选)
"(?:在(?<reason>.*?))?" + // 可选 reason“财付通-微信支付-这有电快捷支付” "(?:在(?<reason>[^\\d。]*?))?" + // 交易原因(可选
"(?<amount>\\d+\\.\\d{1,2})元,余额" + "?(?:人民币)?(?<amount>\\d+\\.\\d{1,2})(?:元)?" + // 金额,“元” 可有可无
"(?<balance>\\d+\\.\\d{1,2})"; ",余额(?:人民币)?(?<balance>\\d+\\.\\d{1,2})" + // 余额
"。?"; // 句号可有可无
var matches = Regex.Matches(emailContent, pattern); var matches = Regex.Matches(emailContent, pattern);
@@ -63,10 +66,14 @@ public class EmailParseForm95555(
foreach (Match match in matches) foreach (Match match in matches)
{ {
var card = match.Groups["card"].Value; var card = match.Groups["card"].Value;
var reason = match.Groups["reason"].Value;
var amountStr = match.Groups["amount"].Value; var amountStr = match.Groups["amount"].Value;
var balanceStr = match.Groups["balance"].Value; var balanceStr = match.Groups["balance"].Value;
var typeStr = match.Groups["type"].Value; var typeStr = match.Groups["type"].Value;
var reason = match.Groups["reason"].Value;
if(string.IsNullOrEmpty(reason))
{
reason = typeStr;
}
if (!string.IsNullOrEmpty(card) && if (!string.IsNullOrEmpty(card) &&
!string.IsNullOrEmpty(reason) && !string.IsNullOrEmpty(reason) &&
@@ -74,9 +81,27 @@ public class EmailParseForm95555(
decimal.TryParse(balanceStr, out var balance)) decimal.TryParse(balanceStr, out var balance))
{ {
var type = DetermineTransactionType(typeStr, reason, amount); var type = DetermineTransactionType(typeStr, reason, amount);
results.Add((card, reason, amount, balance, type, null)); var occurredAt = ParseOccurredAt(match.Groups["time"].Value);
results.Add((card, reason, amount, balance, type, occurredAt));
} }
} }
return results.ToArray(); return results.ToArray();
} }
private DateTime? ParseOccurredAt(string value)
{
// "12月31日09:34"
var now = DateTime.Now;
var dateTimeStr = $"{now.Year}年{value}";
if (DateTime.TryParse(dateTimeStr, out var occurredAt))
{
// 如果解析结果在未来,说明是上一年的交易
if (occurredAt > now)
{
occurredAt = occurredAt.AddYears(-1);
}
return occurredAt;
}
return null;
}
} }

View File

@@ -71,8 +71,18 @@ public abstract class EmailParseServicesBase(
DateTime? occurredAt DateTime? occurredAt
)[]?> ParseByAiAsync(string body) )[]?> ParseByAiAsync(string body)
{ {
var systemPrompt = "你是一个信息抽取助手。仅输出严格的JSON数组不要包含任何多余文本。每个交易记录包含字段: card(字符串), reason(字符串), amount(数字), balance(数字), type(字符串,值为'收入'或'支出'), occurredAt(字符串yyyy-MM-dd HH:mm:ss格式日期时间)。如果缺失,请推断或置空。"; var systemPrompt = $"""
var userPrompt = $"从下面这封银行账单相关邮件正文中提取所有交易记录返回JSON数组格式每个元素包含: card, reason, amount, balance, type(收入或支出), occurredAt(非必要)。正文如下:\n\n{body}"; 你是一个信息抽取助手。
仅输出严格的JSON数组不要包含任何多余文本。
每个交易记录包含字段: card(字符串), reason(字符串), amount(数字), balance(数字), type(字符串,值为'收入'或'支出'), occurredAt(字符串yyyy-MM-dd HH:mm:ss格式日期时间)。
如果缺失,请推断或置空。
[重要] 当前时间为{DateTime.Now:yyyy-MM-dd HH:mm:ss},请根据当前时间推断交易发生的时间。
""";
var userPrompt = $"""
从下面这封银行账单相关邮件正文中提取所有交易记录返回JSON数组格式
每个元素包含: card, reason, amount, balance, type(收入或支出), occurredAt(非必要)。
正文如下:\n\n{body}
""";
var contentText = await openAiService.ChatAsync(systemPrompt, userPrompt); var contentText = await openAiService.ChatAsync(systemPrompt, userPrompt);
if (string.IsNullOrWhiteSpace(contentText)) if (string.IsNullOrWhiteSpace(contentText))
@@ -190,12 +200,76 @@ public abstract class EmailParseServicesBase(
var lowerReason = reason.ToLower(); var lowerReason = reason.ToLower();
// 收入关键词 // 收入关键词
string[] incomeKeywords = { "工资", "奖金", "退款", "返现", "收入", "转入", "存入", "利息", "分红" }; string[] incomeKeywords =
{
"工资", "奖金", "退款",
"返现", "收入", "转入",
"存入", "利息", "分红",
"入账", "收款",
// 常见扩展
"实发工资", "薪资", "薪水", "薪酬",
"提成", "佣金", "劳务费",
"报销入账", "报销款", "补贴", "补助",
"退款成功", "退回", "退货退款",
"返现入账", "返利", "返佣",
"到账", "已到账", "入账成功",
"收款成功", "收到款项", "到账金额",
"资金转入", "资金收入",
"转账收入", "转账入账", "他行来账",
"工资代发", "代发工资", "单位打款",
"利息收入", "收益", "收益发放", "理财收益",
"分红收入", "股息", "红利",
// 平台常用词
"红包", "红包收入", "红包入账",
"奖励金", "活动奖励", "补贴金",
"现金奖励", "推广奖励", "返现奖励",
// 存取类
"现金存入", "柜台存入", "ATM存入",
"他人转入", "他人汇入"
};
if (incomeKeywords.Any(k => lowerReason.Contains(k))) if (incomeKeywords.Any(k => lowerReason.Contains(k)))
return TransactionType.Income; return TransactionType.Income;
// 支出关键词 // 支出关键词
string[] expenseKeywords = { "消费", "支付", "购买", "转出", "取款", "支出", "扣款", "缴费" }; string[] expenseKeywords =
{
"消费", "支付", "购买",
"转出", "取款", "支出",
"扣款", "缴费", "付款",
"刷卡",
// 常见扩展
"支出金额", "支出人民币", "已支出",
"已消费", "消费支出", "消费人民币",
"已支付", "成功支付", "支付成功", "交易支付",
"已扣款", "扣款成功", "扣费", "扣费成功",
"转账", "转账支出", "向外转账", "已转出",
"提现", "现金支出", "现金取款",
"扣除", "扣除金额", "记账支出",
// 账单/通知类用语
"本期应还", "本期应还金额", "本期账单金额",
"本期应还人民币", "最低还款额",
"本期欠款", "欠款金额",
// 线上平台常见用语
"订单支付", "订单扣款", "订单消费",
"交易支出", "交易扣款", "交易成功支出",
"话费充值", "流量充值", "水费", "电费", "燃气费",
"物业费", "服务费", "手续费", "年费", "会费",
"利息支出", "还款支出", "代扣", "代缴",
// 信用卡/花呗等场景
"信用卡还款", "花呗还款", "白条还款",
"分期还款", "账单还款", "自动还款"
};
if (expenseKeywords.Any(k => lowerReason.Contains(k))) if (expenseKeywords.Any(k => lowerReason.Contains(k)))
return TransactionType.Expense; return TransactionType.Expense;

View File

@@ -13,7 +13,7 @@
</div> </div>
<div class="popup-scroll-content"> <div class="popup-scroll-content">
<van-form @submit="onSubmit"> <van-form @submit="onSubmit" style="margin-top: 12px;">
<van-cell-group inset> <van-cell-group inset>
<van-cell title="卡号" :value="transaction.card" /> <van-cell title="卡号" :value="transaction.card" />
<van-cell title="交易时间" :value="formatDate(transaction.occurredAt)" /> <van-cell title="交易时间" :value="formatDate(transaction.occurredAt)" />

View File

@@ -69,7 +69,7 @@
<h3>{{ currentEmail.Subject || currentEmail.subject || '(无主题)' }}</h3> <h3>{{ currentEmail.Subject || currentEmail.subject || '(无主题)' }}</h3>
</div> </div>
<div class="popup-scroll-content"> <div class="popup-scroll-content">
<van-cell-group inset> <van-cell-group inset style="margin-top: 12px;">
<van-cell title="发件人" :value="currentEmail.From || currentEmail.from || '未知'" /> <van-cell title="发件人" :value="currentEmail.From || currentEmail.from || '未知'" />
<van-cell title="接收时间" :value="formatDate(currentEmail.ReceivedDate || currentEmail.receivedDate)" /> <van-cell title="接收时间" :value="formatDate(currentEmail.ReceivedDate || currentEmail.receivedDate)" />
<van-cell title="记录时间" :value="formatDate(currentEmail.CreateTime || currentEmail.createTime)" /> <van-cell title="记录时间" :value="formatDate(currentEmail.CreateTime || currentEmail.createTime)" />
@@ -127,7 +127,7 @@
:transactions="transactionList" :transactions="transactionList"
:loading="false" :loading="false"
:finished="true" :finished="true"
:show-delete="false" :show-delete="true"
@click="handleTransactionClick" @click="handleTransactionClick"
/> />
</PopupContainer> </PopupContainer>

View File

@@ -298,7 +298,7 @@
:transactions="categoryBills" :transactions="categoryBills"
:loading="billListLoading" :loading="billListLoading"
:finished="billListFinished" :finished="billListFinished"
:show-delete="false" :show-delete="true"
@load="loadCategoryBills" @load="loadCategoryBills"
@click="viewBillDetail" @click="viewBillDetail"
/> />