HarmonyOS 推荐排序快照实战:中式美食如何用 score 和 explain 回放首页推荐问题
推荐排序一旦上线最怕用户说“不准”开发者却只能凭感觉猜。中式美食首页推荐会同时参考收藏、浏览、购物清单食材和分类点击如果不留快照后面根本说不清某一道菜为什么排在前面。我的处理方式很直接每次排序都能导出一份score explain快照先让问题能回放再谈怎么调权重。图 1 - 中式美食推荐排序快照重点不是展示一个分数而是把输入、候选和解释串起来方便排查。栏目内容应用名称中式美食工程环境HarmonyOS、ArkTS、ArkUI、Stage 模型本文主题推荐排序快照、score/explain、问题回放、权重调试核心文件HomeRecommendViewModel.ets、RecommendRanker.ets、DishRepository.ets、FavoriteRepository.ets验收入口构造固定用户信号输出前五名快照检查排序原因读完能带走什么推荐问题怎么从“感觉不准”变成可复现、可讨论、可修正本章导读这次只讲推荐排序的排错方式不重复讲完整首页推荐流。为什么推荐排序要保存输入信号。为什么score不能单独看必须带explain。快照里要记录哪些字段才能回放问题。权重调整以后怎么对比前后结果。验收时怎么判断快照真的能帮开发排错。当前验证环境本文讨论的是中式美食本地推荐规则不依赖服务端推荐系统。排序输入来自本地仓储输出给首页推荐流使用。项目说明技术栈HarmonyOS、ArkTS、ArkUI主要入口首页推荐流信号来源收藏、最近浏览、购物清单食材、分类点击调试方式固定样本 快照输出验收重点同一份输入能不能稳定复现同一份排序一、推荐问题不能靠感觉排查如果首页推荐里番茄炒蛋排第一开发者至少要回答三个问题。问题没有快照时有快照时为什么排第一只能猜看命中了哪些信号是哪路权重太高很难判断看每路分数调权后有没有变好靠肉眼比较对比前后快照用户反馈能不能复现很难用同一份输入重跑所以我不把推荐排序当成一个只返回数组的函数。它必须能把排序过程讲出来。二、快照先记录输入信号输入信号是推荐排序的起点。没有输入就没法解释输出。exportinterfaceRecommendSignalSnapshot{favoriteRecipeIds:string[]recentlyViewedIds:string[]shoppingIngredients:string[]clickedCategories:string[]generatedAt:number}这里我会保留generatedAt不是为了做复杂统计而是为了排查“这个推荐结果是什么时候算出来的”。如果用户刚刚收藏了菜但快照时间在收藏之前那排序没变化就不是算法问题。三、候选菜也要入快照只保存用户信号不够。候选池如果漏菜排序再正确也没用。exportinterfaceCandidateSnapshot{recipeId:stringtitle:stringcategory:stringingredients:string[]}exportinterfaceRecommendDebugSnapshot{signal:RecommendSignalSnapshot candidates:CandidateSnapshot[]result:RankedSnapshotItem[]}候选池快照能帮我排除一类问题不是排序没把某道菜排上来而是这道菜根本没有进入候选集合。异常现象优先检查明明有番茄却没推荐番茄菜候选池有没有番茄相关菜收藏菜没有出现收藏 id 是否能在候选池找到同类菜太多候选池是不是本身就偏一类推荐结果为空仓储查询是否返回空集合四、score要拆开不要只留总分总分只能告诉我谁高谁低不能告诉我为什么高。exportinterfaceScorePart{source:favorite|viewed|ingredient|categoryscore:numberreason:string}exportinterfaceRankedSnapshotItem{recipeId:stringtitle:stringtotalScore:numberparts:ScorePart[]}这样排查时就能看到每一道菜的分数组成。菜谱favoriteviewedingredientcategorytotal番茄炒蛋360281074鸡蛋炒饭01214026莲藕排骨汤0100010这张表比一个总分有用得多。比如番茄炒蛋排第一原因很清楚收藏、食材、分类同时命中鸡蛋炒饭靠前是因为最近浏览和鸡蛋命中。五、生成快照时不要改排序逻辑快照只是记录不应该影响排序结果。排序函数先算结果再把结果转换成快照。exportfunctionbuildRecommendSnapshot(signal:RecommendSignalSnapshot,candidates:RecipeCandidate[],ranked:ScoredRecipe[]):RecommendDebugSnapshot{return{signal,candidates:candidates.map((item)({recipeId:item.recipeId,title:item.title,category:item.category,ingredients:item.ingredients})),result:ranked.map((item)({recipeId:item.recipe.recipeId,title:item.recipe.title,totalScore:item.score,parts:item.parts}))}}这里不要为了调试去改原排序逻辑。调试代码一旦改变业务结果后面看到的就不是同一个问题了。六、调权重要对比前后快照调权重不能靠“感觉更顺眼”。我会把同一份输入跑两遍一遍旧权重一遍新权重。exportinterfaceSnapshotDiffItem{recipeId:stringbeforeRank:numberafterRank:numberchangedReason:string}exportfunctiondiffSnapshot(before:RankedSnapshotItem[],after:RankedSnapshotItem[]):SnapshotDiffItem[]{returnafter.map((item,index){constoldIndexbefore.findIndex((old)old.recipeIditem.recipeId)return{recipeId:item.recipeId,beforeRank:oldIndex0?-1:oldIndex1,afterRank:index1,changedReason:item.parts.map((part)part.reason).join(、)}})}我重点看两类变化。变化判断强命中菜上升通常合理无命中菜上升要警惕同类菜连续霸榜需要加多样性约束收藏菜全部压前收藏权重可能太高工程验收记录推荐快照的验收不看页面好不好看只看能不能排查问题。验收项操作方式通过标准输入可复现固定收藏、浏览、食材样本多次运行输出一致候选可检查打印候选池能确认目标菜是否进入候选分数可解释查看parts每个总分都有来源调权可对比调整食材权重后重跑排名变化能解释异常可定位构造空候选池能看出是仓储问题不误判为排序问题这套验收的目的很朴素推荐出了问题先知道问题在哪里而不是先猜怎么改。常见问题复盘1. 为什么不只保存总分总分无法排错。看到 74 分只知道它高看到收藏 36、食材 28、分类 10才知道它为什么高。2. 为什么候选池也要保存因为很多推荐问题不是排序问题而是候选池问题。菜没进候选再好的排序也排不出来。3. 快照会不会影响性能线上不需要每次都持久保存完整快照。开发阶段可以输出必要时只保留前几名和关键字段。4. 为什么先做本地快照不先接服务端日志当前中式美食的推荐规则还在本地验证阶段本地快照更快、更直接。等规则稳定后再考虑把关键字段上报或接服务端分析。本章小结推荐排序不能只看结果数组必须能回放输入、候选、分数和解释。score要拆成多个来源排查时才知道哪路信号影响了排序。调权重要对比前后快照不要靠肉眼觉得顺眼。

相关新闻

最新新闻

日新闻

周新闻

月新闻