轉轉C2B驗機報告演進之路
1 問題背景
1.1 什么是驗機報告?
眾所周知,轉轉是一個官方驗的二手交易平臺,對于用戶在其平臺上交易的商品,均會由專業(yè)的質檢工程師進行驗證。這項驗證將會產(chǎn)生一份詳盡的報告,這就是所謂的驗機報告。
1.2 為什么要做驗機報告的統(tǒng)一?
對于C2B線上回收來說,質檢部門提供的驗機報告具有原始性,其中可能包含一些專業(yè)術語。如果直接展示給用戶,用戶可能會感到困惑。因此我們都會對驗機報告進行一層包裝再呈現(xiàn)給用戶。
早期C2B線上回收給用戶展示的驗機報告是瑕疵項報告,這些報告在手機品類和數(shù)碼品類的展示方式也存在差異。此外,客服人員所能查看的用戶驗機報告與用戶自己所看到的報告也存在差異。再后來隨著差異項報告的加入,就更加混亂了。同一個商品,C2B存在多份驗機報告,驗機報告的鏈接也不一樣。為了解決這個問題,我們推動了驗機報告的統(tǒng)一。
2 C2B用戶驗機報告的演進
2.1 瑕疵項報告
瑕疵項報告將質檢結果中的優(yōu)點和問題逐一列舉,以供用戶查閱。判斷質檢項的優(yōu)劣是根據(jù)我們自身的阿波羅配置進行的。阿波羅的配置工作由運營人員負責維護,如果質檢側出現(xiàn)了新的判定屬性,就需要同步更新阿波羅的配置。瑕疵項驗機報告的效果如下圖所示:
圖片
2.2 差異項報告
差異項報告通過比較用戶選擇的內容與驗機報告中的實際內容,可以體現(xiàn)出偏好、偏差,或者一致性。將用戶選擇的內容與實際檢測結果并排展示,可以使用戶一目了然,從而提升用戶體驗。如何將實際質檢項與用戶選擇的項映射起來,判定結果好壞與否,這個數(shù)據(jù)我們是維護在數(shù)據(jù)庫中,同時也做了對應的后臺交給運營去配置。差異項驗機報告的效果如下圖所示:
圖片
2.3 驗機報告統(tǒng)一
所謂驗機報告的統(tǒng)一,意味著前后端都進行了一致的優(yōu)化。前端頁面上的驗機報告入口鏈接已經(jīng)得到了一致的處理,后端驗機報告接口也得到了統(tǒng)一的收口管理。通過采用不同的策略,不同的商品能夠匹配到相應的報告類型。經(jīng)過多次實驗驗證,我們確定了優(yōu)先級順序:差異項報告 > 瑕疵項報告 > 兜底報告。
圖片
圖片
3 差異項報告系統(tǒng)設計
差異項驗機報告對比了用戶預估報告和實際驗機報告,很多業(yè)務都會有用戶和實際質檢兩份報告,對比這兩份報告是一個通用的能力,因此我們對差異項報告能力做了下沉。
3.1 對比映射模板
對比映射模板本質上是實際質檢項與用戶預估項之間的映射關系。由于實際質檢的項數(shù)可能遠多于用戶選擇的項數(shù),并且實際質檢中的許多項需要映射到用戶選擇的某一項,因此我們需要配置這些項之間的映射規(guī)則。
- 項比較多:使用Excel表格來進行數(shù)據(jù)管理,并將其存儲到數(shù)據(jù)庫中。
- 品類:以品類為維度進行區(qū)分,針對不同品類配置不同模板。
- 特殊情況:同品類下可針對特殊機型配置單獨的模板。
3.2 業(yè)務配置
在映射模板配置完成后,進一步進行業(yè)務線的配置變得必要。由于預質檢和實際質檢是兩個獨立的業(yè)務線,必須明確指定業(yè)務線,以確保映射關系能夠正確對應。此外,加入業(yè)務線的配置也為未來其他業(yè)務的接入提供了方便。
圖片
3.3 映射解析
在差異項配置完成后,當符合條件的商品出現(xiàn)時,驗機報告會根據(jù)我們預先配置的模板進行解析。
/**
* 對比報告 A' 和 B
* 1.設備屬性信息對比
* 2.單選項對比
* 3.多選項對比
* 4.根據(jù)選項類別分組,單選項先按照A價格影響因子倒序+多選項的排序結果
* 5.組合標簽標題
*/
public void compareReport(QcInfoAfterMappingBo qcInfoAfterMappingSrcBo, QcInfoAfterMappingBo qcInfoTargetBo,QcDiffTemplateBo qcDiffTemplateBo, QcDiffDetailDTO qcDiffDetailDTO) {
//B報告到A報告的映射
Map<String, List<QcDiffItemsMapping>> targetToSrcMap = qcDiffTemplateBo.getQcDiffItemsMappings().stream().collect(Collectors.groupingBy(QcDiffItemsMapping::getValueIdTarget));
//A報告 valueId -> qcItemMap
Map<String, QcInfoAfterMappingBo.ItemInfo> qcSrcValueIdMap = qcInfoAfterMappingSrcBo.getNoMappingItemInfoList().stream().collect(Collectors.toMap(QcInfoAfterMappingBo.ItemInfo::getValueId,Function.identity()));
//B報告基本信息映射 valueIdTarget -> targetConfigMap
Map<String, QcDiffTargetConf> targetConfMap = qcDiffTemplateBo.getDiffTargetConfMap();
//以A'為模板來對比
Map<String, QcInfoAfterMappingBo.ItemInfo> singleSelectMap = qcInfoAfterMappingSrcBo.getMappingSingleSelectMap();
List<QcInfoAfterMappingBo.ItemInfo> noMappingTargetItemInfoList = qcInfoTargetBo.getNoMappingItemInfoList();
if (CollectionUtils.isEmpty(noMappingTargetItemInfoList)) {
return;
}
Map<String, List<QcInfoAfterMappingBo.ItemInfo>> targetItemMap = noMappingTargetItemInfoList.stream().collect(Collectors.groupingBy(QcInfoAfterMappingBo.ItemInfo::getItemId));
QcDiffCompareBo compareBo = QcDiffCompareBo.builder().singleSelectItemsDetailList(Lists.newArrayList()).multiSelectItemsDetailList(Lists.newArrayList()).optionTypeDiffLevelNumMap(new HashMap<>()).build();
//設備屬性對比
deviceInfoCompare(qcInfoAfterMappingSrcBo, qcInfoTargetBo, qcDiffDetailDTO);
//單選結果組合
singleSelectCompare(singleSelectMap, targetConfMap, targetItemMap, targetToSrcMap, qcSrcValueIdMap, compareBo);
//多選結果組合
Map<String, List<QcInfoAfterMappingBo.ItemInfo>> multiSelectMap = qcInfoAfterMappingSrcBo.getMappingMultiSelectMap();
multiSelectCompare(multiSelectMap,targetItemMap, targetToSrcMap, qcSrcValueIdMap, targetConfMap, compareBo);
//根據(jù)組合結果中報告類型分組
Map<Integer, List<QcDiffItemsInfo.ItemsDetail>> singleResultListMap = compareBo.getSingleSelectItemsDetailList().stream().collect(Collectors.groupingBy(QcDiffItemsInfo.ItemsDetail::getOptionType));
Map<Integer, List<QcDiffItemsInfo.ItemsDetail>> multiResultListMap = compareBo.getMultiSelectItemsDetailList().stream().collect(Collectors.groupingBy(QcDiffItemsInfo.ItemsDetail::getOptionType));
List<QcDiffItemsInfo> qcDiffItemsInfos = new ArrayList<>();
for (OptionTypeEnum optionTypeEnum : OptionTypeEnum.values()) {
QcDiffItemsInfo qcDiffItemsInfo = new QcDiffItemsInfo();
qcDiffItemsInfo.setName(optionTypeEnum.getDesc());
List<QcDiffItemsInfo.ItemsDetail> itemsDetailList = new ArrayList<>();
if (singleResultListMap.containsKey(optionTypeEnum.getCode())) {
//組合標簽內容
itemsDetailList = singleResultListMap.get(optionTypeEnum.getCode()).stream().sorted(Comparator.comparing(QcDiffItemsInfo.ItemsDetail::getImpactLevelSrc).reversed()).collect(Collectors.toList());
}
if (multiResultListMap.containsKey(optionTypeEnum.getCode())) {
//單選+多選
List<QcDiffItemsInfo.ItemsDetail> multiItemsDetail = multiResultListMap.get(optionTypeEnum.getCode());
if (!CollectionUtils.isEmpty(multiItemsDetail)) {
List<QcDiffItemsInfo.ItemsDetail> itemsDetails = multiItemsDetail.stream().sorted(Comparator.comparing(QcDiffItemsInfo.ItemsDetail::getImpactLevelSrc).reversed()).collect(Collectors.toList());
itemsDetailList.addAll(itemsDetails);
}
}
Map<Integer, MutableTriple<Integer, Integer, Integer>> levelNumMap = compareBo.getOptionTypeDiffLevelNumMap();
if (levelNumMap.containsKey(optionTypeEnum.getCode())) {
qcDiffItemsInfo.setBadTerm(levelNumMap.get(optionTypeEnum.getCode()).getLeft());
qcDiffItemsInfo.setCoincidenceTerm(levelNumMap.get(optionTypeEnum.getCode()).getMiddle());
qcDiffItemsInfo.setGoodTerm(levelNumMap.get(optionTypeEnum.getCode()).getRight());
}
qcDiffItemsInfo.setItemsDetails(itemsDetailList);
qcDiffItemsInfos.add(qcDiffItemsInfo);
}
qcDiffDetailDTO.setQcDiffItemsInfos(qcDiffItemsInfos);
}
4 遇到的問題
在前文中,我們提到了瑕疵項驗機報告的瑕疵項配置存放在阿波羅中,然而隨著業(yè)務的發(fā)展,瑕疵項報告逐漸暴露出了一些問題:
- 質檢項逐漸增多,導致阿波羅放不下的問題。
- 品類越來越多,每擴展一個新品類,就需要配置一整份配置,運營的工作量越來越大。
- 每擴展一個新品類,需要及時配置上,若出現(xiàn)延遲或者遺漏配置,會導致瑕疵項報告沒法加載。
為了解決這些問題,我們采取了以下解決方案:
將瑕疵項配置從阿波羅遷移到數(shù)據(jù)庫中,并創(chuàng)建一個后臺管理系統(tǒng),以方便運營人員進行配置。
此外,我們還針對未及時配置的品類制定了兜底方案:直接解析原始驗機報告。
圖片
圖片
至此,我們已成功完成所有驗機報告統(tǒng)一的工作。不僅如此,差異項報告和瑕疵項報告的后臺管理系統(tǒng)也已經(jīng)搭建完畢,使得在未來,即便質檢標準發(fā)生調整導致質檢項的增減或映射關系的變更,都能夠在零成本的前提下輕松進行,無需對現(xiàn)有代碼進行任何修改。這必將為我們的開發(fā)工作帶來極大的便利和高效。
5 總結
本文詳細介紹了轉轉C2B業(yè)務的驗機報告統(tǒng)一流程以及差異項驗機報告能力的下沉。驗機報告的統(tǒng)一化顯著減少了用戶對驗機報告的咨詢率,極大地提升了用戶體驗。未來,我們將繼續(xù)優(yōu)化和完善系統(tǒng)功能,使更多的業(yè)務能夠順利接入并使用。
關于作者
方和斌,轉轉C2B業(yè)務研發(fā)工程師