Переглянути джерело

优化商邀AI公务名称与团组信息关联及校验

本次提交优化了商邀AI相关功能,完善了团组信息与主表的关联逻辑,新增公务名称和出访目的的必填校验,调整了InvitationAISetPromptDto结构,增强了日志记录,并优化了名称去重与排序逻辑,提升了数据一致性和准确性。
Lyyyi 2 днів тому
батько
коміт
d7e10a4722

+ 24 - 23
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -26,6 +26,7 @@ using Quartz.Util;
 using System.ComponentModel.DataAnnotations;
 using System.Data;
 using System.Diagnostics;
+using System.Security.AccessControl;
 using TencentCloud.Common;
 using static NodaTime.TimeZones.TzdbZone1970Location;
 using static OASystem.API.OAMethodLib.GeneralMethod;
@@ -2297,15 +2298,12 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             var info = await _sqlSugar.Queryable<Res_InvitationAI>()
                 .FirstAsync(x => x.IsDel == 0 && x.InvName == name);
 
-            Grp_DelegationInfo groupInfo = null;
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().FirstAsync(x => x.IsDel == 0 && x.TeamName.Equals(name));
             string baseUrl = AppSettingsHelper.Get("OfficeBaseUrl")?.TrimEnd('/');
 
             // 2. 分支处理:若无主表记录,尝试从团组表同步基本信息
             if (info == null)
             {
-                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
-                    .FirstAsync(x => x.IsDel == 0 && x.TeamName == name);
-
                 return Ok(JsonView(true, "暂无数据", new
                 {
                     Id = 0,
@@ -2314,33 +2312,32 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                     AiCrawledDetails = new List<InvitationAIInfo>(),
                     Entry = new
                     {
+                        Objective = groupInfo?.VisitPurpose ?? "商务会谈",
                         OriginUnit = groupInfo?.ClientUnit ?? "",
                         TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "")
                     }
                 }));
             }
 
-            // 3. 补全关联团组信息
-            if (info.GroupId > 0)
-            {
-                groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().InSingleAsync(info.GroupId);
-            }
-
-            // 4. 数据填充(使用 ??= 语法确保 Null 安全)
+            // 3. 数据填充(使用 ??= 语法确保 Null 安全)
             info.EntryInfo ??= new EntryInfo();
+
+            if (string.IsNullOrEmpty(info.EntryInfo.Objective))
+                info.EntryInfo.Objective = groupInfo?.VisitPurpose ?? "商务会谈";
+
             if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
                 info.EntryInfo.OriginUnit = groupInfo?.ClientUnit ?? "";
 
             if (info.EntryInfo.TargetCountry?.Any() != true)
                 info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.VisitCountry ?? "");
 
-            // 5. 排序:ThenByDescending 确保多级排序生效
+            // 4. 排序:ThenByDescending 确保多级排序生效
             info.AiCrawledDetails = info.AiCrawledDetails
                 .OrderByDescending(x => x.IsChecked)
                 .ThenByDescending(x => x.OperatedAt)
                 .ToList();
 
-            // 6. 路径映射:将相对路径转换为带域名的全路径
+            // 5. 路径映射:将相对路径转换为带域名的全路径
             foreach (var detail in info.AiCrawledDetails.Where(d => d.EmailInfo?.AttachmentPaths?.Any() == true))
             {
                 detail.EmailInfo.AttachmentPaths = detail.EmailInfo.AttachmentPaths
@@ -2462,6 +2459,8 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
                     await HttpContext.SendSseStepAsync(60, $"AI 正在跨境检索缺失的 {string.Join(", ", countryTasksGroupBy.Select(x => $"{x.Region}({x.Counrt}条)"))} 单位资料...");
                     string searchQuestion = BuildHunyuanPrompt(aiTasks, countryTasks, entryInfo);
+
+                    _logger.LogInformation(@"公务名称:{InvName};  混元AI查询提示词:{searchQuestion}", invAiInfo.InvName, searchQuestion);
                     string searchRaw = await _hunyuanService.ChatCompletionsHunyuan_t1_latestAsync(searchQuestion);
 
                     var aiParsed = CleanAndParseJson<List<InvitationAIInfo>>(searchRaw);
@@ -3073,23 +3072,25 @@ BusinessConstraints 参数为结构化文本,解析规则如下:
         public async Task<IActionResult> InvitationAISetPrompt(InvitationAISetPromptDto dto)
         {
             // 基础校验
-            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || dto.TargetCountry == null || dto.TargetCountry.Count == 0
-                || dto.Industries == null || dto.Industries.Count == 0 || dto.ScaleTypes == null || dto.ScaleTypes.Count == 0
+            if (string.IsNullOrWhiteSpace(dto.InvName) || string.IsNullOrWhiteSpace(dto.Objective) || string.IsNullOrWhiteSpace(dto.OriginUnit) || 
+                dto.TargetCountry == null || dto.TargetCountry.Count == 0 || dto.Industries == null || dto.Industries.Count == 0 || 
+                dto.ScaleTypes == null || dto.ScaleTypes.Count == 0
                 )
-                return Ok(JsonView(false, "请传入有效的单位名称、国家、行业和规模类型!"));
+                return Ok(JsonView(false, "请传入有效的公务名称、单位名称、国家、出访目的、行业和规模类型!"));
 
+            var invName = dto.InvName;
             var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
-                .Where(x => x.IsDel == 0 && x.Id == dto.GroupId)
-                .Select(x => new { x.TeamName, x.VisitPurpose })
+                .Where(x => x.IsDel == 0 && x.TeamName.Equals(invName))
+                .Select(x => new {x.Id,x.TeamName, x.VisitPurpose })
                 .FirstAsync();
 
-            string invName = $"{dto.OriginUnit}拜访{string.Join("、",dto.TargetCountry)}";
+            int groupId = groupInfo?.Id ?? 0;
 
             #region 数据库操作
 
             // 数据库信息获取方式
             // 1.通过团组 GroupId 关联查询 
-            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.GroupId == dto.GroupId).FirstAsync();
+            var dataInfo = await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.GroupId == groupId).FirstAsync();
 
             // 2.通过InvName 关联查询(考虑到可能存在同名情况,优先使用 GroupId 关联查询,确保数据准确性)
             dataInfo ??= await _sqlSugar.Queryable<Res_InvitationAI>().Where(x => x.IsDel == 0 && x.InvName == invName).FirstAsync();
@@ -3102,7 +3103,7 @@ BusinessConstraints 参数为结构化文本,解析规则如下:
             {
                 OriginUnit = dto.OriginUnit,
                 TargetCountry = dto.TargetCountry,
-                Objective = groupInfo?.VisitPurpose ?? "商务考察与合作对接",
+                Objective = dto.Objective,
                 Industries = dto.Industries,
                 ScaleTypes = dto.ScaleTypes,
                 IsBackground = dto.IsBackground,
@@ -3118,8 +3119,8 @@ BusinessConstraints 参数为结构化文本,解析规则如下:
                 // 3.1 新数据,需要添加到数据库
                 dataInfo = new Res_InvitationAI()
                 {
-                    InvName = groupInfo?.TeamName ?? invName,  //默认团组名称
-                    GroupId = dto.GroupId,
+                    InvName = invName, 
+                    GroupId = groupId,
                     EntryInfo = entryInfo,
                     CreateUserId = dto.CurrUserId
                 };

+ 10 - 5
OASystem/OASystem.Api/OAMethodLib/GeneralMethod.cs

@@ -1354,11 +1354,16 @@ namespace OASystem.API.OAMethodLib
 
             var unionQuery = _sqlSugar.UnionAll(query1, query2);
 
-            return await _sqlSugar.Queryable(unionQuery)
-                    .PartitionBy(it => it.Name)            // 按 Name 分组
-                    .OrderBy(it => it.Source)              // 优先级:Source=1 会排在 Source=2 之前
-                    .OrderByDescending(it => it.SortTime)  // 同 Source 下保留最新时间
-                    .Select(it => it)                      // 此时 SqlSugar 会自动应用 RowNumber == 1 的过滤
+            // 先进行去重逻辑处理
+            var distinctQuery = _sqlSugar.Queryable(unionQuery)
+                    .PartitionBy(it => it.Name)
+                    .OrderByDescending(it => it.SortTime) 
+                    .Select(it => it);
+
+            // 将去重后的结果包装,再进行全局
+            return await _sqlSugar.Queryable(distinctQuery) 
+                    .OrderByDescending(it => it.Source)     
+                    .OrderByDescending(it => it.SortTime)   
                     .ToListAsync();
         }
 

+ 11 - 0
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -12,6 +12,12 @@ namespace OASystem.Domain.Dtos.Resource
     /// </summary>
     public class InvitationAISetPromptDto
     {
+        /// <summary>
+        /// 公务名称
+        /// 默认团组名称 团组名称不改变的情况下则用团组名称作为公务名称
+        /// </summary>
+        public string InvName { get; set; }
+
         public int GroupId { get; set; }
 
         /// <summary>
@@ -23,6 +29,11 @@ namespace OASystem.Domain.Dtos.Resource
         /// </summary>
         public List<string> TargetCountry { get; set; }
 
+        /// <summary>
+        /// 出访目的
+        /// </summary>
+        public string Objective { get; set; }
+
         /// <summary>
         /// 行业信息
         /// 信息技术、金融与财会、工业制造、医疗保健、政府与公共服务、消费与贸易