using System; using System.Collections.Generic; using static mxdemo.Mind.IStudentClassDispatchService; namespace mxdemo.Mind { // 选科数据与决策会有很多重合的地方 public enum EnumElectiveGeneration { Init = 0, // 初始化态,选科正式开启前的状态,开启之后往下一代推进 Primary = 1, // 初选(正常报名)阶段,初选时间结束时往下一代推进 (先暂定进入PrimaryDM之前选科设置还可以修改,之后禁止,防止扰乱数据) PrimaryDM, // (PrimaryDecisionMaking)初选录取阶段,应用决策之后,开启补录报名(时间段)并往下一代推进 BackTracking, // 补录报名时间结束后往下一代推进 BackTrackingDM, // 调剂OR提醒,如果为直接调剂,则跳过FinalAdjust,如果为提醒(设时间段)并往下一代推进 FinalAdjust, // 调剂报名期间 FinalAdjustDM, // 调剂决策,这个阶段会最终定版 RankBalance, // 可选步骤,行为类似强制调剂 // Dispatch, // 分班?分班是否要进入此结构?貌似结构不太统一!应该还是要分出去做 Terminate // 最终代,选科流程数据全部封存 } public enum EnumElectiveOperation { None = 0, Canceled, Disabled, Optional, Rejected, Approved, Forced } // 待思考问题: /* 1 哪个结点开始不允许修改选科设置 2 未操作志愿、未选完志愿、未响应决策的学生是否需要区分显示 3 补录决策时,是否存在部分调剂、部分提醒的情况? 4 调剂行为定义:直接修改学生最终录取组合,触发发签名 */ #region models /// /// Generation init时生成的缓存数据,方便后续计算 /// 生成开放组合的数据即可 /// 每次修改选科设置(在允许范围内修改)需要刷新 /// public class ElectiveStudentScore { long id; long roundId; long studentId; long groupId; decimal scoreTotal; // 9科成绩,看是否有必要存 decimal scoreGroup; // 6科成绩 int rankTotal; // 9科全校排名,看是否有必要存 int rankGroup; // 6科全校排名 } /// /// 每一代都为体现学生的选科数据变化,任何操作都可以被记录下来,只是最终哪条生效的问题 /// 当然也可以根据实际情况减少部分记录,类下面的注解将讨论这些问题 /// public class ElectiveGenerationFlowData { long id; long roundId; long studentId; long groupId; EnumElectiveGeneration generation; EnumElectiveOperation operation; long operateBy; DateTime operateTime; int rank; // Primary中可能存在多志愿,用来控制此阶段的志愿优先级,如第1志愿=0,第2志愿=1,第三志愿=2 string remark; // 以下2个字段用来挂载生命周期关键引用对象,具体每个环节挂什么,需要再斟酌。 string refType; long refId; } public class ElectiveGenerationFlowLog : ElectiveGenerationFlowData { // + 区分ElectiveGenerationFlowData的字段 } public class RoundForFlow : mxdemo.Mind.IStudentClassDispatchService.Round // Round到时候合并在一起 { bool allMatched; // 所有学生录取完毕,可以开始推进排名均衡或者分班 EnumElectiveGeneration currentGeneration; // 内部字段 // + new fields int disenrollCount; // 未录取学生数量 - 用于图表展示,或者内部判定allMatched bool enablePushNextDMGeneration; // 是否可以强制推进下一代决策进程 = 当前为报名进程,且学生均已报名 bool allowDMAlgorithm; // 当前为决策进程,支持运行匹配算法(BackTrackingDM,FinalAdjustDM) bool doneDMAlgorithm; // 当前为决策进程,且已经运行了匹配算法 bool allowForce; // 当前进程代是否允许强制调剂录取(原来BackTrackingDM,FinalAdjustDM,RankBalance),现在调整为(FinalAdjustDM,RankBalance) } // 1 初选决策时,需要进行设置 // 2 补录决策时,需要进行设置,此时要根据具体数据来定 // 如果所有学生都已经匹配完毕(Approved,Forced都属性已匹配),则可以推进排名均衡或者分班 // 如果还有未匹配的,则推进到FinalAdjust public class FlowPushSetting { long id; long roundId; DateTime begin; DateTime end; bool onlyRecommand; // 仅允许学生选择推荐组合, 否则允许学生填报有空缺的组合 bool onlyAgree; // 调剂阶段,只允许学生选择同意, 否则允许学生拒绝 bool useRecommandWhileDisagree; // 如果拒绝,按推荐报名处理 bool useRecommandWhileNonaction; // 如果不报名,按推荐报名处理 } public class ElectiveEsign { long id; long roundId; long flowDataId; // 对应哪条匹配数据(录取或者调剂)【匹配算法结果表也可如此挂载flowDataId,这样可以追溯匹配原因】 string esignPath; // 签字保存图片 bool actived; // 有效标记,可以考虑:如果学生需要签名且未签名时,如果再遇到新的调剂时,旧的签名还要继续么?我觉得应该是不需要的 // 可能还有其它字段 } /* 总则:ElectiveGenerationFlowData记录的是每一代数据的最终结果,如果想要更精细的记录,可以结合ElectiveGenerationFlowLog 1 Generation.Init 可有可无,不需要生成初始数据? 2 Generation.Primary 报名时: ADD operation=Approved 取消时: UPDATE operation=Cancel 排名挤出时-被挤出人: UPDATE operation=Disabled, remark = '排名挤出' refType = 'ElectiveGenerationFlowData' refId = ElectiveGenerationFlowData.id // 报名解发挤出那条记录的ElectiveGenerationFlowData.id 取消时恢复补挤出的人(只有当被影响的人还有剩余报名次数的时候才自动恢复,因为期间他可能自己又补填了一个志愿): QueryBy refId UPDATE operation=Approved WHEN COUNT(operation=Approved) /// 校长-选科状态 /// RoundForFlow getElectiveStatus({year, roundId}); #region getElectiveSummary /// /// 选科过程中的统计数据值,除了要展示之外还需要做查询支持 /// public class ElectiveSummaryValue { // 展示字段 int groupId; // 组合ID,没有组合为0比如未参与报名人数(实际只有两种统计数据,针对某个组合的统计和不针对组合的统计) int value; // 数值 string color; // 支持'R','G','B','',或者'#FA1234'等hexString bool bold; // 加粗,重点展示 bool star; // 带*,重点展示 // 反向查询代码 将统计逻辑的条件关键字放入此代码,回传后台能反向解析并恢复明细 // 想法:让统计全部基于ElectiveGenerationFlowData,形成一种或多种QueryDto{roundId:'',generation:'',groupId:''...}等, 序列化实际查询条件 // string queryCode; // 4.18 此字段往上提升了1级放在了ElectiveSummaryCategory上,详情请看注释 bool disabled; // 不支持查询 } /// /// 查询的某一列 /// public class ElectiveSummaryCategory { string category; // 身份标识,类似字段名/列名,可能被前端用来定制渲染模板 string displayName; // 显示名称 string detailName; // 4.19 详情页对应的groupId的业务意义:如报名组合、录取组合、推荐组合、初录报名组合、... string queryCode; // 4.18 如果直接查询,就定义此queryCode,前端会收拢此代所有的queryCode,在2级页面形成查询条件选项卡; // 4.18 后台需要一个配置,比如PrimaryDM代之后,整列没有数据直接隐藏, // 4.18 不向前端输出,以减少2级页面的条件项,因为随着选科进行,残留的学生越来越少,减少条件项可以帮助校长更快定位学生 // 4.18 从原来ElectiveSummaryValue内部的queryCode升级而来,反查明细时由方法其它参数提供groupId, roundId, generation ElectiveSummaryValue[] values; } public interface IElectiveGenerationSummary { int roundId { get; set; } // 选科轮次 EnumElectiveGeneration generation { get; set; } // 进程代 } /// /// 进程代的统计结果从primary开始出数据 /// public class ElectivePrimarySummary : IElectiveGenerationSummary { ElectiveSummaryCategory[][] categories; // 是多维数组,以应对多志愿的情况 public int roundId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public EnumElectiveGeneration generation { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } /// /// 非Primary非DM的某一代的统计结果 /// public class ElectiveGenerationSummary: IElectiveGenerationSummary { /// /// 对某一代的统计项目,比如在初选决策、补录报名的统计等,下面接口会细述 /// ElectiveSummaryCategory[] categories; int IElectiveGenerationSummary.roundId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } EnumElectiveGeneration IElectiveGenerationSummary.generation { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } public class ElectiveGenerationDMSummary: ElectiveGenerationSummary { /// /// 迭加到某决策代的统计项目,比如当前选科的总录取人数、待签人数、已签人数等,下面接口会细述 /// ElectiveSummaryCategory[] accumulates; } /// /// 获取选科进程的统计数据,一直汇总到当前进程代 /// /// IElectiveGenerationSummary[] getElectiveSummary(); // 下面开始按代来描述ElectiveGenerationSummary需要返回的具体内容 /* 一、ElectivePrimarySummary,所需ElectiveSummaryCategory->{category:'',displayName:''}定义如下: * // hht.22/4/10 这个属性不要了 {'expectedCount','设置人数'}, -- 选科设置的人数, 为方便输出与图表展示,可能后续会有重复输出 * {'actualCount','实际人数'}, -- 填报第X志愿的人数 * {'unfinishedCount','未完成人数'}, -- 未填报第X志愿的人数(根据ElectiveGenerationFlowData实际Approved&Rank情况统计即可,因为如果报名还在进行中,本身就是动态变化的东西) * 按第一志愿、第二志愿、...的顺序输出数据 * * 二、ElectiveGenerationDMSummary.accumulates * 仅决策代需要统计此数据,决策代[PrimaryDM BackTrackingDM FinalAdjustDM RankBalance] * // hht.22/4/12 同上,这个属性也不要了 {'expectedCount', '设置人数'} -- 选科设置人数 * {'approvedCount', '录取人数'} -- 到此阶段为止,正常录取人数 * {'forcedCount', '调剂录取人数'} -- 到此阶段为止,调剂录取人数 * {'esignedCount', '已签人数'} -- 到此阶段为止,触发的有效签名统计 这个先 * {'esigningCount', '待签人数'} -- 到此阶段为止,阶段触发有效的应签未签统计 [4.18 因为签字提前和强制调剂更改,所以只有FinalAdjustDM RankBalance这两代会产生待签数据了] 注意:上述一与二的数据不能随意变动,会影响后续图表展示 * 三、ElectiveGenerationSummary.categories 只统计某代的数据 * A PrimaryDM * {'indicateCount','指标'} -- 正负表示超过设置的人数,负数表示低于设置的人数 * {'approvedCount, '录取人数'} -- 正常录取人数 * {'adjustCount','调剂人数'} -- 未录取人数,理论上应该<=指标的绝对值 * {'matchedCount','专业符合'} -- 未录取可被调剂的人(志愿不满足但自选专业满足) // 4.19 为保存自选专业与报告一致,每次进入初选决策之后关闭自选专业功能,校长开启选科(或者选科结束???)时打开 * {'nonmatchedCount','专业不符'} -- 未录取不可被调剂的人(志愿与自选专业均不满足) * * B BackTracking FinalAdjust * {'matchedApproved','专业符合同意'} -- 统计有效补录报名,且报名与推荐相同 * {'matchedNotOptional','专业符合改填'} -- 统计有效补录报名,且报名与推荐不同 * {'matchedRejected','专业符合拒绝'} -- 统计拒绝报名的人数 * {'matchedNonaction','专业符合未填'} -- 统计未参的人数(因为有可能被排名挤出) * {'nonmatchedApproved','专业不符同意'} * {'nonmatchedNotOptional','专业不符改填'} * {'nonmatchedRejected','专业不符拒绝'} * {'nonmatchedNonaction','专业不符未填'} * * C BackTrackingDM FinalAdjustDM RankBalance * {'matchedApproved','专业符合填报录取'} -- 正常录取 [BackTrackingDM FinalAdjustDM] * {'matchedApprovedByConfig','专业符合拒填录取'} -- 正常录取 [BackTrackingDM FinalAdjustDM] * {'matchedApprovedByNoaction','专业符合未填录取'} -- 正常录取 [BackTrackingDM FinalAdjustDM] * {'matchedRejectByRankout','专业符合填报未录'} -- 正常录取 [BackTrackingDM FinalAdjustDM] // 4.19需要区分填报后被匹配算法踢出的人 * 「专业不符」* 4 * {'forcedCount','调剂录取'} -- 强制调剂录取 [FinalAdjustDM RankBalance] * {'indicateCount','指标'} -- 正负表示超过设置的人数,负数表示低于设置的人数 * {'matchedCount','专业符合'} -- 正常录取 [BackTrackingDM] * {'nonmatchedCount','专业不符'} -- 正常录取 [BackTrackingDM] * */ #endregion #region getElectiveSummaryDetail 汇总明细清单 /// /// 明细肯定是通过 某个组合某个学生 来呈现的 /// public interface IElectiveGenerationDetail { long id { get; set; } EnumElectiveGeneration generation { get; set; } string category { get; set; } // ElectiveSummaryCategory.category int roundId { get; set; } int studentId { get; set; } string studentName { get; set; } int classId { get; set; } string className { get; set; } int groupId { get; set; } string groupName { get; set; } string datetime { get; set; } } public class ElectiveGenerationRankDescriptor { string key; // 类似字段名,相同的业务定义相同的名称,在代数据迭代到一个格子里显示时,同字段将被覆盖掉 int value; // 实际值 string description; // 描述文字 } public class ElectiveGenerationFlowHistory // : IElectiveGenerationDetail 可以考虑接收IElectiveGenerationDetail接口约束,也可以不,因为history只会展示关键信息 { EnumElectiveGeneration generation; // <=ElectiveGenerationDetail.generation int groupId; // ElectiveGenerationFlowData.groupId string description; // 一志愿 二志愿 三志愿 初选报名 初选录取 未录取(能显示匹配算法具体原因更好) 推荐 补录报名 ... ElectiveGenerationRankDescriptor[] rankDescriptors; // 与匹配算法相关的数据提供,之所以定义成这样,是这些项极有可能还会进一步调整 /* rankDescriptors 估计需要边做边整理了: * Primary [{ 'expected', 250, '设置人数'},{'inTimeRankInGrade', 267, '实时排名'}] 所有报名的人都会产生 * PrimaryDM [{'indicate', 25, '补录指标'},{'bestGroupSum', '最优分组人数'}, {'rankInIndicate', 19, '成绩最优排名'}] 未录取需要参与补录的人会产生新的统计维度 * 注:最优分组人数与成绩最优排名是指:有补录指标的组合,在剩余待补录的人员中的最好成绩所在组合的总人数与自己的排名情况 * ... 后面的generation以此类推 * */ } /// /// 此类相当于ElectiveGenerationFlowData的DTO,将字段转义为UI需要的内容 /// 明细的学生组合信息, 主要包含报名信息; /// 还有一部分自选专业的信息,需要借助其它接口 /// public class ElectiveGenerationDetail : IElectiveGenerationDetail { ElectiveGenerationFlowHistory[] histories; // 4.19 当前student截止到当前代的全部generation flow data数据,但需要转换为业务格式 // 5.5 hht 允许此条数据触发强制调剂。具体进行到哪代可以调剂、哪些数据可以调剂??? // 5.5 hht 暂定:所有学生录取完毕的决策代或者二次补录决策代允许调剂,可调剂对象为已录取(包含正常录与调剂录)和二级补录决策中未录取的。 public bool enableForce; // 5.5 hht 如果允许强制调剂,禁止调剂到哪个组合?一般禁止调剂到被录取组合,因为原地不动不叫调剂。没有禁止的给0或者null public int disableForceGroupId; public long id { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string category { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public EnumElectiveGeneration generation { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int roundId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int studentId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string studentName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int classId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string className { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public int groupId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string groupName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string datetime { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } public class ElectiveGroupGenerationStatistic { public int groupId; ElectiveGenerationRankDescriptor[] descriptors; } public class ElectiveGenerationDetailWrapper { ElectiveGroupGenerationStatistic[] groupDescriptors; // 迭代至当前代的组合统计信息,主要统计指标(缺少人数)与实报(实际报名人数) ElectiveGenerationDetail[] details; // 学生明细 } /// /// 初选报名搭配使用,展示其它可报组合情况 /// public class ElectiveGenerationOptionalMajor { ElectiveOptionalMajor[] majors; // 自选专业列表 int bestMatchedGroupId; // 最佳匹配 } /// /// Map: {key: studentid, value: ElectiveGenerationOptionalMajor} /// /// /// 批量取学生自选专业匹配情况 Dictionary getGenerationOptionalMajorsBatch(int[] studentIds); /// /// 通过summary中输出的queryCode反查明细 /// /// /// 当前进程代 /// 针对组合的查询会传,否则传0或者''(具体看返回的时候给的什么值,比如未报名的) 按行 /// 按列 /// ElectiveGenerationDetailWrapper getElectiveGenerationDetails(int pageNo, int pageSize, int roundId, int generation, int groupId, string queryCode); #endregion #region decision-making /// /// 匹配算法,算法名称代表的具体逻辑请看注解(不要顾名思义) /// public enum EnumElectiveDMAlgorithm { /// /// 按成绩: /// ElectiveEngine中,相当于[未录学生]/[组合*ALL]/[志愿*ALL]一并运行,排序时排名优先; /// 以上逻辑循环迭代,每次只补空缺组合,直至未录学生=0。 /// 第1次迭代之后运算的学生均属于可调剂名单,不会有不可调剂名单 /// RankFirst, /// /// 按专业: /// ElectiveEngine中,相当于[未录学生]/[组合*ALL]/[志愿1][志愿2,志愿3][自选专业伪志愿]/三波运行,排序时排名优先; /// 以上逻辑循环迭代,每次只补空缺组合,直至伪志愿运算结束。 /// [志愿1][志愿2,志愿3]迭代的录取学生为正常录取;自选专业伪志愿为可调剂名单;运行结束后的未录学生为不可调剂名单。 /// MajorFirst, // } /// /// 执行选科匹配算法。为减少错误,对DM代数据全删全加可能好点。 /// /// void applyElectiveDMAlgorithm(EnumElectiveDMAlgorithm algorithm); #endregion #region forced /// /// 决策阶段,强制调剂录取 /// /// /// void enrollByForce(long studentId, int groupId); /// /// 取消强制调剂操作 /// /// ElectiveGenerationFlowData.id void cancelEnrollByForce(long id); #endregion #region generation推进 /// /// 决策完毕时,推进下一代进行 /// /// 推进配置 void pushGenerationSetting(FlowPushSetting setting); /// /// 如果在所有学生全部录取的情况,可以在任意决策结点跳转至排名均衡 /// void jumpGenerationRankBalance(); /// /// 如果在所有学生全部录取的情况,可以在任意决策结点跳转至终止态,封存数据 /// void terminateGeneration(); /// /// 在任意报名阶段,如果校长发现数据已经完全OK,则可以强制推进进程,提前进入决策 /// void flushIntoGenerationDM(); #endregion #region 排名均衡(略)先完成前面的 #endregion } }