Files
crawler-plugin/admin-frontend-vue/src/pages/tasks/duplicate-distribution.ts
T
huangzd1997 b5a1a51403 task-116(任务与重复分析中心): 实现重复检查分布视图
新增 duplicate-distribution.ts:按国家/站点聚合撞款分布与撞款 ASIN top 店铺。

TDD: task-116.test.ts 8 用例先 RED 后 GREEN。
2026-09-05 17:24:34 +08:00

47 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 重复检查分布视图(任务 116):按国家/站点聚合撞款与 top 店铺分布;纯逻辑。 */
import type { DuplicateShop } from './duplicate-model.ts'
export interface CountryDistributionRow {
country: string
asinCount: number
shopCount: number
}
/** 按国家聚合分布(每店铺 country_codes 全量计入)。 */
export function countryDistribution(shops: readonly DuplicateShop[]): CountryDistributionRow[] {
const byCountry = new Map<string, { asinCount: number; shopCount: number }>()
for (const item of shops) {
for (const code of item.countryCodes) {
const country = code.trim().toUpperCase()
if (!country) continue
const entry = byCountry.get(country) ?? { asinCount: 0, shopCount: 0 }
entry.asinCount += item.asinCount
entry.shopCount += 1
byCountry.set(country, entry)
}
}
return [...byCountry.entries()]
.map(([country, entry]) => ({ country, asinCount: entry.asinCount, shopCount: entry.shopCount }))
.sort((a, b) => b.asinCount - a.asinCount || a.country.localeCompare(b.country))
}
export interface SiteDistributionRow {
site: string
asinCount: number
shopCount: number
}
/** 按站点(site)聚合:以单国家维度汇总撞款,站点取国家码。 */
export function duplicateSiteDistribution(shops: readonly DuplicateShop[]): SiteDistributionRow[] {
const rows = countryDistribution(shops)
return rows.map((row) => ({ site: row.country, asinCount: row.asinCount, shopCount: row.shopCount }))
}
/** 按撞款 ASIN 数取 top 店铺(降序)limit<=0 返回空。 */
export function topShopsByAsin(shops: readonly DuplicateShop[], limit: number): DuplicateShop[] {
if (!(limit > 0)) return []
return [...shops]
.sort((a, b) => b.asinCount - a.asinCount || a.shopName.localeCompare(b.shopName))
.slice(0, Math.floor(limit))
}