| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import { Injectable } from '@angular/core';
- import { completionJSON } from 'fmode-ng/core/agent/chat/completion';
- /**
- * 支付凭证AI分析服务
- * 使用AI识别支付凭证图片中的金额、时间、支付方式等信息
- */
- @Injectable({
- providedIn: 'root'
- })
- export class PaymentVoucherAIService {
- constructor() {}
- /**
- * 分析支付凭证图片
- * @param imageUrl 图片URL
- * @param onProgress 进度回调
- * @returns 分析结果
- */
- async analyzeVoucher(options: {
- imageUrl: string;
- onProgress?: (progress: string) => void;
- }): Promise<{
- amount: number;
- paymentMethod: string;
- paymentTime?: Date;
- transactionId?: string;
- payer?: string;
- receiver?: string;
- confidence: number;
- rawText?: string;
- }> {
- try {
- options.onProgress?.('正在识别支付凭证...');
- // 构建AI识别提示词
- const prompt = `请识别这张支付凭证图片中的信息,并按以下JSON格式输出:
- {
- "amount": 支付金额(数字),
- "paymentMethod": "支付方式(bank_transfer/alipay/wechat/cash/other)",
- "paymentTime": "支付时间(YYYY-MM-DD HH:mm:ss格式,如无法识别则为null)",
- "transactionId": "交易流水号或订单号",
- "payer": "付款人姓名或账号",
- "receiver": "收款人姓名或账号",
- "confidence": 识别置信度(0-1的小数),
- "rawText": "图片中的原始文字内容"
- }
- 要求:
- 1. 准确识别金额,包括小数点
- 2. 正确判断支付方式(如支付宝、微信、银行转账等)
- 3. 提取交易时间(年月日时分秒)
- 4. 提取交易流水号/订单号
- 5. 识别付款人和收款人信息
- 6. 评估识别的置信度
- 7. 保留原始文字内容作为参考
- 支付方式识别规则:
- - 看到"支付宝"、"Alipay" → alipay
- - 看到"微信"、"WeChat" → wechat
- - 看到"银行"、"转账"、"Bank" → bank_transfer
- - 看到"现金"、"Cash" → cash
- - 其他情况 → other`;
- const outputSchema = `{
- "amount": 0,
- "paymentMethod": "bank_transfer",
- "paymentTime": null,
- "transactionId": "",
- "payer": "",
- "receiver": "",
- "confidence": 0.95,
- "rawText": ""
- }`;
- options.onProgress?.('AI正在分析凭证内容...');
- // 使用fmode-ng的completionJSON进行视觉识别
- const result = await completionJSON(
- prompt,
- outputSchema,
- (content) => {
- options.onProgress?.(`正在分析... ${Math.min(Math.round((content?.length || 0) / 10), 100)}%`);
- },
- 2, // 最大重试次数
- {
- // 使用豆包1.6视觉模型(与教辅名师项目一致)
- model: 'doubao-1.6',
- vision: true,
- images: [options.imageUrl]
- }
- );
- console.log('✅ 支付凭证AI分析完成:', result);
- // 转换支付时间
- let paymentTime: Date | undefined;
- if (result.paymentTime) {
- try {
- paymentTime = new Date(result.paymentTime);
- } catch (e) {
- console.warn('支付时间解析失败:', result.paymentTime);
- }
- }
- return {
- amount: parseFloat(result.amount) || 0,
- paymentMethod: result.paymentMethod || 'other',
- paymentTime,
- transactionId: result.transactionId || '',
- payer: result.payer || '',
- receiver: result.receiver || '',
- confidence: result.confidence || 0.8,
- rawText: result.rawText || ''
- };
- } catch (error) {
- console.error('❌ 支付凭证AI分析失败:', error);
-
- // 返回默认值,表示分析失败
- return {
- amount: 0,
- paymentMethod: 'other',
- confidence: 0,
- rawText: '分析失败: ' + (error.message || '未知错误')
- };
- }
- }
- /**
- * 批量分析多张支付凭证
- * @param imageUrls 图片URL数组
- * @param onProgress 进度回调
- * @returns 分析结果数组
- */
- async analyzeBatchVouchers(options: {
- imageUrls: string[];
- onProgress?: (progress: string, index: number, total: number) => void;
- }): Promise<Array<{
- imageUrl: string;
- result: Awaited<ReturnType<typeof this.analyzeVoucher>>;
- }>> {
- const results: Array<{
- imageUrl: string;
- result: Awaited<ReturnType<typeof this.analyzeVoucher>>;
- }> = [];
- for (let i = 0; i < options.imageUrls.length; i++) {
- const imageUrl = options.imageUrls[i];
-
- options.onProgress?.(
- `正在分析第 ${i + 1} / ${options.imageUrls.length} 张凭证...`,
- i + 1,
- options.imageUrls.length
- );
- const result = await this.analyzeVoucher({
- imageUrl,
- onProgress: (progress) => {
- options.onProgress?.(
- `第 ${i + 1} / ${options.imageUrls.length} 张:${progress}`,
- i + 1,
- options.imageUrls.length
- );
- }
- });
- results.push({ imageUrl, result });
- }
- return results;
- }
- /**
- * 验证分析结果的合理性
- * @param result 分析结果
- * @returns 是否合理
- */
- validateResult(result: Awaited<ReturnType<typeof this.analyzeVoucher>>): {
- valid: boolean;
- errors: string[];
- } {
- const errors: string[] = [];
- // 检查金额
- if (result.amount <= 0) {
- errors.push('金额必须大于0');
- }
- if (result.amount > 10000000) {
- errors.push('金额超出合理范围(>1000万)');
- }
- // 检查置信度
- if (result.confidence < 0.5) {
- errors.push('识别置信度过低,建议人工核对');
- }
- // 检查支付方式
- const validMethods = ['bank_transfer', 'alipay', 'wechat', 'cash', 'other'];
- if (!validMethods.includes(result.paymentMethod)) {
- errors.push('支付方式无效');
- }
- // 检查支付时间
- if (result.paymentTime) {
- const now = new Date();
- const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
- const oneMonthLater = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
- if (result.paymentTime < oneYearAgo) {
- errors.push('支付时间过早(超过1年前)');
- }
- if (result.paymentTime > oneMonthLater) {
- errors.push('支付时间为未来时间');
- }
- }
- return {
- valid: errors.length === 0,
- errors
- };
- }
- /**
- * 格式化支付方式显示文本
- * @param method 支付方式
- * @returns 显示文本
- */
- formatPaymentMethod(method: string): string {
- const methodMap: Record<string, string> = {
- 'bank_transfer': '银行转账',
- 'alipay': '支付宝',
- 'wechat': '微信支付',
- 'cash': '现金',
- 'other': '其他'
- };
- return methodMap[method] || method;
- }
- /**
- * 获取置信度等级描述
- * @param confidence 置信度
- * @returns 等级描述
- */
- getConfidenceLevel(confidence: number): {
- level: 'high' | 'medium' | 'low';
- text: string;
- color: string;
- } {
- if (confidence >= 0.8) {
- return { level: 'high', text: '高置信度', color: '#34c759' };
- } else if (confidence >= 0.5) {
- return { level: 'medium', text: '中等置信度', color: '#ff9500' };
- } else {
- return { level: 'low', text: '低置信度', color: '#ff3b30' };
- }
- }
- }
|