payment-voucher-ai.service.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import { Injectable } from '@angular/core';
  2. import { completionJSON } from 'fmode-ng/core/agent/chat/completion';
  3. /**
  4. * 支付凭证AI分析服务
  5. * 使用AI识别支付凭证图片中的金额、时间、支付方式等信息
  6. */
  7. @Injectable({
  8. providedIn: 'root'
  9. })
  10. export class PaymentVoucherAIService {
  11. constructor() {}
  12. /**
  13. * 分析支付凭证图片
  14. * @param imageUrl 图片URL
  15. * @param onProgress 进度回调
  16. * @returns 分析结果
  17. */
  18. async analyzeVoucher(options: {
  19. imageUrl: string;
  20. onProgress?: (progress: string) => void;
  21. }): Promise<{
  22. amount: number;
  23. paymentMethod: string;
  24. paymentTime?: Date;
  25. transactionId?: string;
  26. payer?: string;
  27. receiver?: string;
  28. confidence: number;
  29. rawText?: string;
  30. }> {
  31. try {
  32. options.onProgress?.('正在识别支付凭证...');
  33. // 构建AI识别提示词
  34. const prompt = `请识别这张支付凭证图片中的信息,并按以下JSON格式输出:
  35. {
  36. "amount": 支付金额(数字),
  37. "paymentMethod": "支付方式(bank_transfer/alipay/wechat/cash/other)",
  38. "paymentTime": "支付时间(YYYY-MM-DD HH:mm:ss格式,如无法识别则为null)",
  39. "transactionId": "交易流水号或订单号",
  40. "payer": "付款人姓名或账号",
  41. "receiver": "收款人姓名或账号",
  42. "confidence": 识别置信度(0-1的小数),
  43. "rawText": "图片中的原始文字内容"
  44. }
  45. 要求:
  46. 1. 准确识别金额,包括小数点
  47. 2. 正确判断支付方式(如支付宝、微信、银行转账等)
  48. 3. 提取交易时间(年月日时分秒)
  49. 4. 提取交易流水号/订单号
  50. 5. 识别付款人和收款人信息
  51. 6. 评估识别的置信度
  52. 7. 保留原始文字内容作为参考
  53. 支付方式识别规则:
  54. - 看到"支付宝"、"Alipay" → alipay
  55. - 看到"微信"、"WeChat" → wechat
  56. - 看到"银行"、"转账"、"Bank" → bank_transfer
  57. - 看到"现金"、"Cash" → cash
  58. - 其他情况 → other`;
  59. const outputSchema = `{
  60. "amount": 0,
  61. "paymentMethod": "bank_transfer",
  62. "paymentTime": null,
  63. "transactionId": "",
  64. "payer": "",
  65. "receiver": "",
  66. "confidence": 0.95,
  67. "rawText": ""
  68. }`;
  69. options.onProgress?.('AI正在分析凭证内容...');
  70. // 使用fmode-ng的completionJSON进行视觉识别
  71. const result = await completionJSON(
  72. prompt,
  73. outputSchema,
  74. (content) => {
  75. options.onProgress?.(`正在分析... ${Math.min(Math.round((content?.length || 0) / 10), 100)}%`);
  76. },
  77. 2, // 最大重试次数
  78. {
  79. // 使用豆包1.6视觉模型(与教辅名师项目一致)
  80. model: 'doubao-1.6',
  81. vision: true,
  82. images: [options.imageUrl]
  83. }
  84. );
  85. console.log('✅ 支付凭证AI分析完成:', result);
  86. // 转换支付时间
  87. let paymentTime: Date | undefined;
  88. if (result.paymentTime) {
  89. try {
  90. paymentTime = new Date(result.paymentTime);
  91. } catch (e) {
  92. console.warn('支付时间解析失败:', result.paymentTime);
  93. }
  94. }
  95. return {
  96. amount: parseFloat(result.amount) || 0,
  97. paymentMethod: result.paymentMethod || 'other',
  98. paymentTime,
  99. transactionId: result.transactionId || '',
  100. payer: result.payer || '',
  101. receiver: result.receiver || '',
  102. confidence: result.confidence || 0.8,
  103. rawText: result.rawText || ''
  104. };
  105. } catch (error) {
  106. console.error('❌ 支付凭证AI分析失败:', error);
  107. // 返回默认值,表示分析失败
  108. return {
  109. amount: 0,
  110. paymentMethod: 'other',
  111. confidence: 0,
  112. rawText: '分析失败: ' + (error.message || '未知错误')
  113. };
  114. }
  115. }
  116. /**
  117. * 批量分析多张支付凭证
  118. * @param imageUrls 图片URL数组
  119. * @param onProgress 进度回调
  120. * @returns 分析结果数组
  121. */
  122. async analyzeBatchVouchers(options: {
  123. imageUrls: string[];
  124. onProgress?: (progress: string, index: number, total: number) => void;
  125. }): Promise<Array<{
  126. imageUrl: string;
  127. result: Awaited<ReturnType<typeof this.analyzeVoucher>>;
  128. }>> {
  129. const results: Array<{
  130. imageUrl: string;
  131. result: Awaited<ReturnType<typeof this.analyzeVoucher>>;
  132. }> = [];
  133. for (let i = 0; i < options.imageUrls.length; i++) {
  134. const imageUrl = options.imageUrls[i];
  135. options.onProgress?.(
  136. `正在分析第 ${i + 1} / ${options.imageUrls.length} 张凭证...`,
  137. i + 1,
  138. options.imageUrls.length
  139. );
  140. const result = await this.analyzeVoucher({
  141. imageUrl,
  142. onProgress: (progress) => {
  143. options.onProgress?.(
  144. `第 ${i + 1} / ${options.imageUrls.length} 张:${progress}`,
  145. i + 1,
  146. options.imageUrls.length
  147. );
  148. }
  149. });
  150. results.push({ imageUrl, result });
  151. }
  152. return results;
  153. }
  154. /**
  155. * 验证分析结果的合理性
  156. * @param result 分析结果
  157. * @returns 是否合理
  158. */
  159. validateResult(result: Awaited<ReturnType<typeof this.analyzeVoucher>>): {
  160. valid: boolean;
  161. errors: string[];
  162. } {
  163. const errors: string[] = [];
  164. // 检查金额
  165. if (result.amount <= 0) {
  166. errors.push('金额必须大于0');
  167. }
  168. if (result.amount > 10000000) {
  169. errors.push('金额超出合理范围(>1000万)');
  170. }
  171. // 检查置信度
  172. if (result.confidence < 0.5) {
  173. errors.push('识别置信度过低,建议人工核对');
  174. }
  175. // 检查支付方式
  176. const validMethods = ['bank_transfer', 'alipay', 'wechat', 'cash', 'other'];
  177. if (!validMethods.includes(result.paymentMethod)) {
  178. errors.push('支付方式无效');
  179. }
  180. // 检查支付时间
  181. if (result.paymentTime) {
  182. const now = new Date();
  183. const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
  184. const oneMonthLater = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate());
  185. if (result.paymentTime < oneYearAgo) {
  186. errors.push('支付时间过早(超过1年前)');
  187. }
  188. if (result.paymentTime > oneMonthLater) {
  189. errors.push('支付时间为未来时间');
  190. }
  191. }
  192. return {
  193. valid: errors.length === 0,
  194. errors
  195. };
  196. }
  197. /**
  198. * 格式化支付方式显示文本
  199. * @param method 支付方式
  200. * @returns 显示文本
  201. */
  202. formatPaymentMethod(method: string): string {
  203. const methodMap: Record<string, string> = {
  204. 'bank_transfer': '银行转账',
  205. 'alipay': '支付宝',
  206. 'wechat': '微信支付',
  207. 'cash': '现金',
  208. 'other': '其他'
  209. };
  210. return methodMap[method] || method;
  211. }
  212. /**
  213. * 获取置信度等级描述
  214. * @param confidence 置信度
  215. * @returns 等级描述
  216. */
  217. getConfidenceLevel(confidence: number): {
  218. level: 'high' | 'medium' | 'low';
  219. text: string;
  220. color: string;
  221. } {
  222. if (confidence >= 0.8) {
  223. return { level: 'high', text: '高置信度', color: '#34c759' };
  224. } else if (confidence >= 0.5) {
  225. return { level: 'medium', text: '中等置信度', color: '#ff9500' };
  226. } else {
  227. return { level: 'low', text: '低置信度', color: '#ff3b30' };
  228. }
  229. }
  230. }