Browse Source

商邀AI多个国家查询调整

Lyyyi 2 weeks ago
parent
commit
349a04d483

+ 71 - 33
OASystem/OASystem.Api/Controllers/ResourceController.cs

@@ -2,6 +2,7 @@
 using Aspose.Words;
 using Aspose.Words.Tables;
 using EyeSoft.Extensions;
+using NodaTime;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.UserModel;
 using OASystem.API.OAMethodLib;
@@ -20,6 +21,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Data;
 using System.Diagnostics;
 using TencentCloud.Common;
+using static NodaTime.TimeZones.TzdbZone1970Location;
 using static OASystem.API.OAMethodLib.GeneralMethod;
 using static OASystem.API.OAMethodLib.JWTHelper;
 using static OpenAI.GPT3.ObjectModels.Models;
@@ -2328,11 +2330,23 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 }));
             }
 
+            var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>().Where(x => x.IsDel == 0 && x.Id == info.GroupId).FirstAsync();
+            // 设置国家、单位默认值
+            if (string.IsNullOrEmpty(info.EntryInfo.OriginUnit))
+            {
+                info.EntryInfo.OriginUnit = groupInfo?.ClientUnit ?? "";
+            }
+
+            if (info.EntryInfo.TargetCountry == null || info.EntryInfo.TargetCountry.Count < 1)
+            {
+                info.EntryInfo.TargetCountry = _delegationInfoRep.GroupSplitCountry(groupInfo?.TeamName ?? "");
+            }
+
             var view = new
             {
                 info.Id,
-                info.InvName,
                 info.GroupId,
+                info.InvName,
                 info.AiCrawledDetails,
                 Entry = info.EntryInfo,
             };
@@ -2368,7 +2382,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
             string operatorName = await _sqlSugar.Queryable<Sys_Users>().Where(x => x.IsDel == 0 && x.Id == dto.CurrUserId).Select(x => x.CnName).FirstAsync() ?? "-";
 
             var localInvDatas = new List<InvitationAIInfo>(); // 本地数据源(商邀资料)
-
+            var aiTasks = new List<CountryAIPormptInfo>();    // 记录:国家 -> 需要补齐的数量
             #region 本地数据源(商邀资料)
 
             var datas = await _sqlSugar.Queryable<Res_InvitationOfficialActivityData>()
@@ -2396,12 +2410,12 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 .Where(x => localDataIds.Contains(x.Id))
                 .ToListAsync();
 
-            // 解密 集合大于15条数据则取前15条数据
-            localInvDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
+            // 解密 
+            var localInvDecryptDatas = rawData.AsParallel().AsOrdered().Select(item => new InvitationAIInfo
             {
                 Guid = Guid.NewGuid().ToString("N"),
                 Source = 0, // 明确来源标识
-                Region = entryInfo.TargetCountry,
+                Region = AesEncryptionHelper.Decrypt(item.Country),
                 NameCn = AesEncryptionHelper.Decrypt(item.UnitName),
                 Address = AesEncryptionHelper.Decrypt(item.Address),
                 Scope = AesEncryptionHelper.Decrypt(item.Field),
@@ -2410,7 +2424,20 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 Email = AesEncryptionHelper.Decrypt(item.Email),
                 OperatedAt = DateTime.Now,
                 Operator = operatorName,
-            }).Take(15).ToList();
+            }).ToList();
+
+            // 国家数据条数
+            int targetPerCountry = invAiInfo.EntryInfo?.NeedCount ?? 10;
+            foreach (var countryName in invAiInfo.EntryInfo.TargetCountry)
+            {
+                var countryDatas = localInvDecryptDatas.Where(x => x.Region == countryName).Take(targetPerCountry).ToList();
+                if (countryDatas.Any())
+                {
+                    localInvDatas.AddRange(countryDatas);
+                    int gap = targetPerCountry - countryDatas.Count;
+                    if (gap > 0) aiTasks.Add(new() { Country = countryName, Count = gap });
+                }
+            }
 
             #endregion
 
@@ -2418,9 +2445,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var hunyuanAIInvDatas = new List<InvitationAIInfo>();
 
-            int aiNeedCount = Math.Max(0, totalTarget - localInvDatas.Count);
-
-            if (aiNeedCount > 0)
+            if (aiTasks != null && aiTasks.Count > 0)
             {
                 question = @$"# [SYSTEM_CONTEXT]
 - Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
@@ -2428,34 +2453,35 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 - Target Architecture: .NET 6 DTO Compatible Interface
 
 # [INPUT_CONFIG]
+- Tasks: {{{JsonConvert.SerializeObject(aiTasks)}}}
 - OriginUnit: {{{entryInfo.OriginUnit}}} 
-- TargetCountry: {{{entryInfo.TargetCountry}}}
 - Objective: {{{entryInfo.Objective}}}
-- DataCount: {{{aiNeedCount}}}
 - OtherConstraints: {{{entryInfo.OtherConstraints}}}
 # [ROLE_DEFINITION]
-你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+你是一位精通全球跨境经贸、海外园区政策及 Tasks 中的国家本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
 
 # [BUSINESS_LOGIC_CoT]
 在生成结果前,请严格执行以下思维链拆解:
+1. **ScopePartitioning**: 遍历 Tasks 中的每个国家。
 1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
-2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+2. **ParityMatching**: 针对每个国家,检索境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
 3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
-4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+4. **DataSynthesis**: 必须严格根据每个国家对应的 Count 生成数据条数。针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
 
 # [CONSTRAINTS_&_STANDARDS]
 - **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
 - **DataIntegrity**: 
-    - 必须输出精确的 {{DataCount}} 条记录
+    - 总条数必须等于 Tasks 中所有 Count 的总和
     - 优先级:Core (核心机构) > Backup (关联替代机构)。
 - **ValidationRules**:
-    - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
+    - `Phone`: 必须包含 Tasks 中的国家国际区号(如 +856, +66 等)。
     - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
     - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
 - **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
 
 # [INFORMATION_SCHEMA]
 请将结果填充至以下结构的 JSON 数组中:
+- Region:国家(必须与 Tasks 中的 Country 完全匹配)
 - NameCn: 单位名称(中文)
 - NameEn: 单位名称(英文)
 - Address: 详细地理位置(含省市区街道)
@@ -2536,7 +2562,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                             {
                                 x.Guid = Guid.NewGuid().ToString("N");
                                 x.Source = 1;
-                                x.Region = entryInfo.TargetCountry;
+                                //x.Region = entryInfo.TargetCountry;
                                 x.Operator = operatorName;
                                 x.OperatedAt = DateTime.Now;
                                 return x;
@@ -2589,7 +2615,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
         public async Task<IActionResult> InvitationAISetPrompt(InvitationAISetPromptDto dto)
         {
             // 基础校验
-            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || string.IsNullOrWhiteSpace(dto.TargetCountry))
+            if (string.IsNullOrWhiteSpace(dto.OriginUnit) || dto.TargetCountry == null || dto.TargetCountry.Count == 0)
                 return Ok(JsonView(false, "请传入有效的单位名称和国家!"));
 
             var groupInfo = await _sqlSugar.Queryable<Grp_DelegationInfo>()
@@ -2597,7 +2623,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                 .Select(x => new { x.TeamName, x.VisitPurpose })
                 .FirstAsync();
 
-            string invName = $"{dto.OriginUnit}拜访{dto.TargetCountry}";
+            string invName = $"{dto.OriginUnit}拜访{string.Join("、",dto.TargetCountry)}";
 
             #region 数据库操作
 
@@ -2760,15 +2786,28 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var entryInfo = invAiInfo.EntryInfo;
 
+            // 记录:国家 -> 需要补齐的数量
+            var aiTasks = new List<CountryAIPormptInfo>();
+
+            // 单个国家数据总条数
+            int targetPerCountry = invAiInfo.EntryInfo?.NeedCount ?? 10;
+
+            foreach (var countryName in entryInfo.TargetCountry)
+            {
+                var countryDataCount = invAiInfo.AiCrawledDetails.Count(x => x.Region == countryName);
+                int aiNeedCount = targetPerCountry - countryDataCount;
+                if (aiNeedCount > 0)
+                {
+                    aiTasks.Add(new() { Country = countryName, Count = aiNeedCount });
+                }
+            }
+
             // 已搜索出的数据(AI 数据源)
             var hunyuanAIDatas = invAiInfo.AiCrawledDetails.Where(x => x.Source == 1).ToList() ?? new List<InvitationAIInfo>();
 
             // 当前企业领域
             string industryFocus = string.Empty;
 
-            // 动态计算 AI 需求缺口
-            int totalTarget = entryInfo.NeedCount;
-
             // 词条
             string question = string.Empty;
 
@@ -2778,13 +2817,11 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 
             var hunyuanAIInvDatas = new List<InvitationAIInfo>();
 
-            int aiNeedCount = Math.Max(0, totalTarget - hunyuanAIDatas.Count);
-
             // 已存在数据名称整理
             var existingNames = new HashSet<string>(hunyuanAIDatas.Select(x => x.NameCn));
             entryInfo.OtherConstraints = $"请基于以下已存在的名称列表进行推荐,避免重复:{string.Join(", ", existingNames)}。{entryInfo.OtherConstraints}";
 
-            if (aiNeedCount > 0)
+            if (aiTasks != null && aiTasks.Count > 0)
             {
                 question = @$"# [SYSTEM_CONTEXT]
 - Role: Senior Business Consultant (Global Supply Chain & Cross-border Trade Expert)
@@ -2792,34 +2829,35 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
 - Target Architecture: .NET 6 DTO Compatible Interface
 
 # [INPUT_CONFIG]
+- Tasks: {{{JsonConvert.SerializeObject(aiTasks)}}}
 - OriginUnit: {{{entryInfo.OriginUnit}}} 
-- TargetCountry: {{{entryInfo.TargetCountry}}}
 - Objective: {{{entryInfo.Objective}}}
-- DataCount: {{{aiNeedCount}}}
 - OtherConstraints: {{{entryInfo.OtherConstraints}}}
 # [ROLE_DEFINITION]
-你是一位精通全球跨境经贸、海外园区政策及 {{TargetCountry}} 本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
+你是一位精通全球跨境经贸、海外园区政策及 Tasks 中的国家本地准入法规的【顶级商务咨询顾问】。你擅长通过“产业链对等原则(Chain-Parity Principle)”为跨国企业精准匹配具备落地价值的合作伙伴。
 
 # [BUSINESS_LOGIC_CoT]
 在生成结果前,请严格执行以下思维链拆解:
+1. **ScopePartitioning**: 遍历 Tasks 中的每个国家。
 1. **EntityProfiling**: 识别 {{OriginUnit}} 的核心生态位(如:Supply/Demand/Capital/Logistics)。
-2. **ParityMatching**: 检索 {{TargetCountry}} 境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
+2. **ParityMatching**: 针对每个国家,检索境内业务闭环对等机构(例如:若 Origin 为分销,则 Target 为源头工厂/种植园)。
 3. **RegulatoryCheck**: 验证目标机构的合规性(如:GACC 备案、SPS 协议、或当地政府特许经营权)。
-4. **DataSynthesis**: 针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
+4. **DataSynthesis**: 必须严格根据每个国家对应的 Count 生成数据条数。针对无法直接获取的动态(如 PostUrl),优先检索其官网 News 频道或 LinkedIn 企业号。
 
 # [CONSTRAINTS_&_STANDARDS]
 - **NamingConvention**: 所有 JSON Key 必须严格遵循 **PascalCase**(例如:`UnitNameCn` 而非 `unit_name_cn`)。
 - **DataIntegrity**: 
-    - 必须输出精确的 {{DataCount}} 条记录
+    - 总条数必须等于 Tasks 中所有 Count 的总和
     - 优先级:Core (核心机构) > Backup (关联替代机构)。
 - **ValidationRules**:
-    - `Phone`: 必须包含 {{TargetCountry}} 国际区号(如 +856, +66 等)。
+    - `Phone`: 必须包含 Tasks 中的国家国际区号(如 +856, +66 等)。
     - `IntgAdvice`: 必须包含“对等性分析”,解释该机构如何与 {{OriginUnit}} 形成业务闭环(50-100字)。
     - `Status`: 若字段确实无法获取,统一填充 'N/A',禁止编造。
 - **Safety**: 确保推荐机构不涉及敏感黑名单或已破产企业。
 
 # [INFORMATION_SCHEMA]
 请将结果填充至以下结构的 JSON 数组中:
+- Region:国家(必须与 Tasks 中的 Country 完全匹配)
 - NameCn: 单位名称(中文)
 - NameEn: 单位名称(英文)
 - Address: 详细地理位置(含省市区街道)
@@ -2900,7 +2938,7 @@ Inner Join Sys_Department as d With(Nolock) On u.DepId=d.Id Where m.Id={0} ", _m
                             {
                                 x.Guid = Guid.NewGuid().ToString("N");
                                 x.Source = 1;
-                                x.Region = entryInfo.TargetCountry;
+                                //x.Region = entryInfo.TargetCountry;
                                 x.Operator = operatorName;
                                 x.OperatedAt = DateTime.Now;
                                 return x;

+ 1 - 1
OASystem/OASystem.Domain/Dtos/Resource/InvitationAI.cs

@@ -20,7 +20,7 @@ namespace OASystem.Domain.Dtos.Resource
         /// <summary>
         /// 出访国家
         /// </summary>
-        public string TargetCountry { get; set; }
+        public List<string> TargetCountry { get; set; }
         /// <summary>
         /// 备注信息
         /// </summary>

+ 9 - 2
OASystem/OASystem.Domain/Entities/Resource/Res_InvitationAI.cs

@@ -135,15 +135,16 @@ namespace OASystem.Domain.Entities.Resource
         /// <summary>
         /// 拜访国家
         /// </summary>
-        public string TargetCountry { get; set; }
+        public List<string> TargetCountry { get; set; } = new List<string>();
         /// <summary>
         /// 出访目的
         /// </summary>
         public string Objective { get; set; }
         /// <summary>
         /// 数据条数
+        /// 每个国家获取数据总条数,默认10条
         /// </summary>
-        public int NeedCount { get; set; } = 20;
+        public int NeedCount { get; set; } = 10;
         /// <summary>
         /// 其他规则
         /// </summary>
@@ -200,4 +201,10 @@ namespace OASystem.Domain.Entities.Resource
         public string Subject { get; set; }
         public string Content { get; set; }
     }
+
+
+    public class CountryAIPormptInfo {
+        public string Country { get; set; }
+        public int Count { get; set; }
+    }
 }