|
|
@@ -371,7 +371,7 @@ export default function RecallResultTable({
|
|
|
(a._breakdown?.composite ?? -Infinity) - (b._breakdown?.composite ?? -Infinity),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
sortOrder: compositeSort ?? null,
|
|
|
- render: (_v, item) => <CompositeScoreCell breakdown={item._breakdown} modality={item.modality} />,
|
|
|
+ render: (_v, item) => <CompositeScoreCell breakdown={item._breakdown} modality={item.modality} quality={item.signals?.quality} />,
|
|
|
}
|
|
|
const simNormColumn: ColumnsType<RowItem>[number] = {
|
|
|
title: '相关性分',
|
|
|
@@ -554,10 +554,69 @@ export default function RecallResultTable({
|
|
|
/** 长文专属列 */
|
|
|
const articleOnlyCols: ColumnsType<RowItem> = [
|
|
|
{
|
|
|
- title: '来源',
|
|
|
- key: 'article.source',
|
|
|
- width: 120,
|
|
|
- render: (_v, item) => textOrDash(item.articleDetail?.source ?? undefined),
|
|
|
+ title: '总阅读',
|
|
|
+ key: 'q.articleTotalRead',
|
|
|
+ width: 90,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.totalRead ?? -1) - (b.signals?.quality?.totalRead ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.totalRead
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '阅读均值',
|
|
|
+ key: 'q.articleAvgRead',
|
|
|
+ width: 90,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.avgRead ?? -1) - (b.signals?.quality?.avgRead ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.avgRead
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(Math.round(v)))}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '打开率',
|
|
|
+ key: 'q.articleOpenRate',
|
|
|
+ width: 80,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.openRate ?? -1) - (b.signals?.quality?.openRate ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.openRate
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(1)}%</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '裂变率',
|
|
|
+ key: 'q.articleFissionRate',
|
|
|
+ width: 80,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.fissionRate ?? -1) - (b.signals?.quality?.fissionRate ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.fissionRate
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(1)}%</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '发文',
|
|
|
+ key: 'q.articlePublishCount',
|
|
|
+ width: 60,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.publishCount ?? -1) - (b.signals?.quality?.publishCount ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.publishCount
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v}</span>
|
|
|
+ },
|
|
|
},
|
|
|
{
|
|
|
title: '标签',
|
|
|
@@ -615,6 +674,49 @@ export default function RecallResultTable({
|
|
|
pointsCol('解构:目的点', '目的点', 240),
|
|
|
]
|
|
|
|
|
|
+ /** 文章质量维度列 (ARTICLE Tab) — 从 signals.quality 读取 */
|
|
|
+ const articleQualityCols: ColumnsType<RowItem> = [
|
|
|
+ {
|
|
|
+ title: '阅读分',
|
|
|
+ key: 'q.articleRead',
|
|
|
+ width: 80,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.readScore ?? -1) - (b.signals?.quality?.readScore ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.readScore
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '打开率分',
|
|
|
+ key: 'q.articleOpen',
|
|
|
+ width: 88,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.openScore ?? -1) - (b.signals?.quality?.openScore ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.openScore
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '裂变率分',
|
|
|
+ key: 'q.articleFission',
|
|
|
+ width: 88,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => ((a.signals?.quality?.fissionScore ?? -1) - (b.signals?.quality?.fissionScore ?? -1)),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.signals?.quality?.fissionScore
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
/** 视频质量分列 (VIDEO Tab) — rovNorm 作为质量分,参考素材 tab */
|
|
|
const videoQualityCol: ColumnsType<RowItem>[number] = {
|
|
|
title: '质量分',
|
|
|
@@ -792,7 +894,7 @@ export default function RecallResultTable({
|
|
|
if (activeModality === 'MATERIAL') {
|
|
|
columns = [titleCol, coverCol, configCodeCol, compositeCol, simNormColumn, ...materialQualityCols, scoreColumn, ...materialOnlyCols, statsDateColumn]
|
|
|
} else if (activeModality === 'ARTICLE') {
|
|
|
- columns = [titleCol, coverCol, configCodeCol, simNormColumn, scoreColumn, ...articleOnlyCols]
|
|
|
+ columns = [titleCol, coverCol, configCodeCol, compositeCol, simNormColumn, ...articleQualityCols, scoreColumn, ...articleOnlyCols]
|
|
|
} else {
|
|
|
// VIDEO + ALL 走视频列布局
|
|
|
columns = [titleCol, coverCol, configCodeCol, compositeCol, simNormColumn, videoQualityCol, scoreColumn, ...videoOnlyCols]
|
|
|
@@ -801,7 +903,7 @@ export default function RecallResultTable({
|
|
|
|
|
|
return (
|
|
|
<div>
|
|
|
- {!hideInlineWeights && onRankingParamsChange && activeModality !== 'ARTICLE' && (
|
|
|
+ {!hideInlineWeights && onRankingParamsChange && (
|
|
|
<RankingWeightsPanel
|
|
|
params={rankingParams}
|
|
|
onChange={onRankingParamsChange}
|
|
|
@@ -1170,9 +1272,11 @@ function RecommendStatusCell({ value }: { value: string | undefined }) {
|
|
|
function CompositeScoreCell({
|
|
|
breakdown,
|
|
|
modality,
|
|
|
+ quality,
|
|
|
}: {
|
|
|
breakdown: ScoreBreakdown | null
|
|
|
modality?: VideoMatchEnrichedVO['modality']
|
|
|
+ quality?: { hasData: boolean; readScore?: number | null; openScore?: number | null; fissionScore?: number | null } | null
|
|
|
}) {
|
|
|
if (!breakdown) {
|
|
|
return <Text type="secondary">--</Text>
|
|
|
@@ -1191,7 +1295,23 @@ function CompositeScoreCell({
|
|
|
modality === 'MATERIAL' ? (
|
|
|
<div style={{ fontSize: 12, lineHeight: 1.6 }}>
|
|
|
<div>相关性分 sim_norm = {simNorm.toFixed(3)}</div>
|
|
|
- <div>质量分 quality = {weightedQuality != null ? weightedQuality.toFixed(3) : '--'}</div>
|
|
|
+ <div>素材质量 quality = {weightedQuality != null ? weightedQuality.toFixed(3) : '--'}</div>
|
|
|
+ <div>c = {boost.toFixed(2)}</div>
|
|
|
+ <div style={{ borderTop: '1px solid rgba(255,255,255,0.3)', marginTop: 4, paddingTop: 4 }}>
|
|
|
+ composite = α·c·sim_norm + (1-α)·quality = <b>{text}</b>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : modality === 'ARTICLE' ? (
|
|
|
+ <div style={{ fontSize: 12, lineHeight: 1.6 }}>
|
|
|
+ <div>相关性分 sim_norm = {simNorm.toFixed(3)}</div>
|
|
|
+ <div style={{ borderTop: '1px solid rgba(255,255,255,0.2)', margin: '4px 0', paddingTop: 4 }}>
|
|
|
+ 阅读分 readScore = {quality?.readScore != null ? quality.readScore.toFixed(3) : '--'}
|
|
|
+ </div>
|
|
|
+ <div>打开率分 openScore = {quality?.openScore != null ? quality.openScore.toFixed(3) : '--'}</div>
|
|
|
+ <div>裂变率分 fissionScore = {quality?.fissionScore != null ? quality.fissionScore.toFixed(3) : '--'}</div>
|
|
|
+ <div style={{ borderTop: '1px solid rgba(255,255,255,0.3)', marginTop: 4, paddingTop: 4 }}>
|
|
|
+ 加权质量 quality = {weightedQuality != null ? weightedQuality.toFixed(3) : '--'}
|
|
|
+ </div>
|
|
|
<div>c = {boost.toFixed(2)}</div>
|
|
|
<div style={{ borderTop: '1px solid rgba(255,255,255,0.3)', marginTop: 4, paddingTop: 4 }}>
|
|
|
composite = α·c·sim_norm + (1-α)·quality = <b>{text}</b>
|