|
|
@@ -759,7 +759,7 @@ export default function RecallResultTable({
|
|
|
},
|
|
|
}
|
|
|
|
|
|
- /** 素材质量列 (MATERIAL Tab) */
|
|
|
+ /** 素材质量列 (MATERIAL Tab) — 效率指标 + 百分位得分 */
|
|
|
const materialQualityCols: ColumnsType<RowItem> = [
|
|
|
{
|
|
|
title: '质量分',
|
|
|
@@ -772,105 +772,157 @@ export default function RecallResultTable({
|
|
|
render: (_v, item) => <WeightedQualityCell item={item} rankingParams={rankingParams} />,
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日打开率',
|
|
|
- key: 'q.ctr7d',
|
|
|
- width: 100,
|
|
|
+ title: 'CTR',
|
|
|
+ key: 'q.ctr',
|
|
|
+ width: 80,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.ctr7d ?? -1) - (b.materialDetail?.quality?.ctr7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.ctr ?? -1) - (b.materialDetail?.quality?.ctr ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.ctr7d
|
|
|
+ const v = item.materialDetail?.quality?.ctr
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(2)}%</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日裂变率',
|
|
|
- key: 'q.viralRate7d',
|
|
|
- width: 100,
|
|
|
+ title: 'CVR',
|
|
|
+ key: 'q.cvr',
|
|
|
+ width: 80,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.viralRate7d ?? -1) - (b.materialDetail?.quality?.viralRate7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.cvr ?? -1) - (b.materialDetail?.quality?.cvr ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.viralRate7d
|
|
|
+ const v = item.materialDetail?.quality?.cvr
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(2)}%</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日ROI',
|
|
|
- key: 'q.roi7d',
|
|
|
- width: 90,
|
|
|
+ title: 'ROI',
|
|
|
+ key: 'q.roi',
|
|
|
+ width: 80,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.roi7d ?? -1) - (b.materialDetail?.quality?.roi7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.roi ?? -1) - (b.materialDetail?.quality?.roi ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.roi7d
|
|
|
+ const v = item.materialDetail?.quality?.roi
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日转化',
|
|
|
- key: 'q.totalConversion7d',
|
|
|
- width: 110,
|
|
|
+ title: '打开率',
|
|
|
+ key: 'q.openRate',
|
|
|
+ width: 80,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.openRate ?? -1) - (b.materialDetail?.quality?.openRate ?? -1),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.materialDetail?.quality?.openRate
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(2)}%</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '裂变率',
|
|
|
+ key: 'q.fissionRate',
|
|
|
+ width: 80,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.fissionRate ?? -1) - (b.materialDetail?.quality?.fissionRate ?? -1),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.materialDetail?.quality?.fissionRate
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{(v * 100).toFixed(2)}%</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '曝光',
|
|
|
+ key: 'q.impressions',
|
|
|
+ width: 90,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.totalConversion7d ?? -1) - (b.materialDetail?.quality?.totalConversion7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.impressions ?? -1) - (b.materialDetail?.quality?.impressions ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.totalConversion7d
|
|
|
+ const v = item.materialDetail?.quality?.impressions
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日首层UV',
|
|
|
- key: 'q.firstUv7d',
|
|
|
- width: 110,
|
|
|
+ title: '点击',
|
|
|
+ key: 'q.clicks',
|
|
|
+ width: 90,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.firstUv7d ?? -1) - (b.materialDetail?.quality?.firstUv7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.clicks ?? -1) - (b.materialDetail?.quality?.clicks ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.firstUv7d
|
|
|
+ const v = item.materialDetail?.quality?.clicks
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日裂变',
|
|
|
- key: 'q.t0ViralCount7d',
|
|
|
- width: 100,
|
|
|
+ title: '转化',
|
|
|
+ key: 'q.conversions',
|
|
|
+ width: 90,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.t0ViralCount7d ?? -1) - (b.materialDetail?.quality?.t0ViralCount7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.conversions ?? -1) - (b.materialDetail?.quality?.conversions ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.t0ViralCount7d
|
|
|
+ const v = item.materialDetail?.quality?.conversions
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日收入',
|
|
|
- key: 'q.revenue7d',
|
|
|
- width: 100,
|
|
|
+ title: '成本',
|
|
|
+ key: 'q.cost',
|
|
|
+ width: 90,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.cost ?? -1) - (b.materialDetail?.quality?.cost ?? -1),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.materialDetail?.quality?.cost
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '收入',
|
|
|
+ key: 'q.income',
|
|
|
+ width: 90,
|
|
|
+ align: 'right',
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.income ?? -1) - (b.materialDetail?.quality?.income ?? -1),
|
|
|
+ sortDirections: ['descend', 'ascend'],
|
|
|
+ render: (_v, item) => {
|
|
|
+ const v = item.materialDetail?.quality?.income
|
|
|
+ if (v == null) return <Text type="secondary">--</Text>
|
|
|
+ return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{v.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '首层UV',
|
|
|
+ key: 'q.firstUv',
|
|
|
+ width: 90,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.revenue7d ?? -1) - (b.materialDetail?.quality?.revenue7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.firstUv ?? -1) - (b.materialDetail?.quality?.firstUv ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.revenue7d
|
|
|
+ const v = item.materialDetail?.quality?.firstUv
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- title: '近7日消耗',
|
|
|
- key: 'q.cost7d',
|
|
|
- width: 100,
|
|
|
+ title: 'T0裂变',
|
|
|
+ key: 'q.fission0Uv',
|
|
|
+ width: 90,
|
|
|
align: 'right',
|
|
|
- sorter: (a, b) => (a.materialDetail?.quality?.cost7d ?? -1) - (b.materialDetail?.quality?.cost7d ?? -1),
|
|
|
+ sorter: (a, b) => (a.materialDetail?.quality?.fission0Uv ?? -1) - (b.materialDetail?.quality?.fission0Uv ?? -1),
|
|
|
sortDirections: ['descend', 'ascend'],
|
|
|
render: (_v, item) => {
|
|
|
- const v = item.materialDetail?.quality?.cost7d
|
|
|
+ const v = item.materialDetail?.quality?.fission0Uv
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
return <span style={{ fontVariantNumeric: 'tabular-nums', fontSize: 12 }}>{formatNumber(String(v))}</span>
|
|
|
},
|
|
|
@@ -878,14 +930,14 @@ export default function RecallResultTable({
|
|
|
]
|
|
|
|
|
|
const statsDateColumn: ColumnsType<RowItem>[number] = {
|
|
|
- title: '统计日期',
|
|
|
+ title: '统计天数',
|
|
|
key: 'q.dt',
|
|
|
- width: 90,
|
|
|
+ width: 80,
|
|
|
align: 'center',
|
|
|
render: (_v, item) => {
|
|
|
const v = item.materialDetail?.quality?.dt
|
|
|
if (v == null) return <Text type="secondary">--</Text>
|
|
|
- return <span style={{ fontSize: 12 }}>{v}</span>
|
|
|
+ return <span style={{ fontSize: 12 }}>{v}天</span>
|
|
|
},
|
|
|
}
|
|
|
|
|
|
@@ -1296,6 +1348,9 @@ function CompositeScoreCell({
|
|
|
<div style={{ fontSize: 12, lineHeight: 1.6 }}>
|
|
|
<div>相关性分 sim_norm = {simNorm.toFixed(3)}</div>
|
|
|
<div>素材质量 quality = {weightedQuality != null ? weightedQuality.toFixed(3) : '--'}</div>
|
|
|
+ <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 10 }}>
|
|
|
+ = wCtr·ctr + wCvr·cvr + wRoi·roi + wOpenRate·openRate + wFissionRate·fissionRate
|
|
|
+ </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>
|
|
|
@@ -1369,11 +1424,10 @@ function WeightedQualityCell({
|
|
|
const tip = (
|
|
|
<div style={{ fontSize: 12, lineHeight: 1.6 }}>
|
|
|
<div>
|
|
|
- quality = (wCtr·ctr + wViral·viral + wRoi·roi) / Σw
|
|
|
+ quality = wCtr·ctr + wCvr·cvr + wRoi·roi + wOpenRate·openRate + wFissionRate·fissionRate
|
|
|
</div>
|
|
|
<div>
|
|
|
- wCtr={rankingParams.wCtr.toFixed(2)} wViral={rankingParams.wViral.toFixed(2)} wRoi=
|
|
|
- {rankingParams.wRoi.toFixed(2)}
|
|
|
+ wCtr={rankingParams.wCtr.toFixed(2)} wCvr={rankingParams.wCvr.toFixed(2)} wRoi={rankingParams.wRoi.toFixed(2)} wOpenRate={rankingParams.wOpenRate.toFixed(2)} wFissionRate={rankingParams.wFissionRate.toFixed(2)}
|
|
|
</div>
|
|
|
{offline != null && (
|
|
|
<div style={{ marginTop: 4, color: 'rgba(255,255,255,0.65)' }}>
|