feat(sector_radar): enhance sector radar functionality with active moneyflow and detailed metrics

- Introduced ActiveMoneyflowSource to fetch optional active-order flow, enhancing the sector radar's data capabilities.
- Updated StockFactRecord and DailyAggregateRecord to include pct_change and active_buy_net_amount_yuan for improved financial insights.
- Modified the build process to incorporate active moneyflow data without invalidating main rankings on failure.
- Enhanced the HTTP API to return detailed sector history and metrics, including pct_change and active buy metrics for members.
- Updated tests to validate the new functionality and ensure data integrity across various scenarios.
This commit is contained in:
yuxuanhui
2026-09-06 16:06:17 +08:00
parent 68282f5d46
commit 7f93d6b0f5
48 changed files with 4883 additions and 95 deletions
@@ -12,6 +12,7 @@ import { SectorRadarPage } from "./sector-radar-page"
const navigate = vi.hoisted(() => vi.fn())
const useSectorRadarDates = vi.fn()
const useSectorRadarRankings = vi.fn()
const useSectorRadarHistory = vi.fn()
const refetchDates = vi.fn()
const refetchRankings = vi.fn()
const fetchNextPage = vi.fn()
@@ -24,6 +25,7 @@ vi.mock("@tanstack/react-router", () => ({
}))
vi.mock("@/features/sector-radar/api/sector-radar.query", () => ({
useSectorRadarHistory: (...args: unknown[]) => useSectorRadarHistory(...args),
useSectorRadarDates: () => useSectorRadarDates(),
useSectorRadarRankings: (...args: unknown[]) =>
useSectorRadarRankings(...args),
@@ -91,6 +93,11 @@ const rankingsResponse: RadarRankingsResponse = {
rank_percentile: 100,
rank_change_days: 1,
rank_change: 3,
pct_change: -1.25,
daily_net_amount_yuan: 1250000000,
daily_ratio: -0.02,
on_list_count: 3,
history_available_days: 10,
},
{
trade_date: "2026-08-28",
@@ -111,6 +118,11 @@ const rankingsResponse: RadarRankingsResponse = {
rank_percentile: null,
rank_change_days: 1,
rank_change: null,
pct_change: null,
daily_net_amount_yuan: null,
daily_ratio: null,
on_list_count: null,
history_available_days: 0,
},
],
}
@@ -163,6 +175,11 @@ describe("SectorRadarPage", () => {
refetch: refetchDates,
})
useSectorRadarRankings.mockReturnValue(rankingQueryResult())
useSectorRadarHistory.mockReturnValue({
isPending: false,
isError: false,
data: { status: "no_data" },
})
navigate.mockReset()
refetchDates.mockReset()
refetchRankings.mockReset()
@@ -175,14 +192,21 @@ describe("SectorRadarPage", () => {
expect(screen.getAllByText("+12.5 亿元")[0]).toBeInTheDocument()
expect(screen.getAllByText("机器人")[0]).toBeInTheDocument()
expect(screen.getAllByText("19/20")[0]).toBeInTheDocument()
expect(screen.getAllByText("100%")[0]).toBeInTheDocument()
expect(screen.queryByText("19/20")).not.toBeInTheDocument()
expect(screen.queryByText("BK0001.DC")).not.toBeInTheDocument()
expect(
screen.getAllByRole("columnheader", { name: "排名百分位" }),
screen.queryByRole("columnheader", { name: "排名百分位" }),
).not.toBeInTheDocument()
expect(
screen.queryByRole("columnheader", { name: "样本" }),
).not.toBeInTheDocument()
expect(
screen.getAllByRole("columnheader", { name: "涨跌幅" }),
).toHaveLength(2)
expect(screen.getAllByRole("columnheader", { name: "样本" })).toHaveLength(
2,
)
expect(
screen.getAllByRole("columnheader", { name: "单日流入率" }),
).toHaveLength(2)
expect(screen.getAllByText("-2%")).toHaveLength(2)
expect(
screen.queryByRole("columnheader", { name: "资金覆盖率" }),
).not.toBeInTheDocument()
@@ -206,6 +230,66 @@ describe("SectorRadarPage", () => {
).not.toBeInTheDocument()
})
it("mirrors ratio columns and expands only the selected sector history, resetting on date change", () => {
routeSearch = { ...routeSearch, view: "ratio" }
const ratioPage = {
...rankingsResponse,
view: "ratio",
rows: rankingsResponse.rows.map((row) => ({
...row,
unit: "ratio",
metric_value: -0.01,
})),
}
useSectorRadarRankings.mockReturnValue(
rankingQueryResult({ data: { pages: [ratioPage], pageParams: [1] } }),
)
const { rerender } = render(<SectorRadarPage />)
expect(screen.getAllByRole("columnheader", { name: "在榜" })).toHaveLength(
2,
)
expect(screen.getAllByText("-1%")).toHaveLength(4)
const trigger = screen.getAllByRole("button", {
name: "机器人展开在榜历史",
})[0]!
fireEvent.click(trigger)
expect(trigger).toHaveAttribute("aria-expanded", "true")
expect(useSectorRadarHistory).toHaveBeenLastCalledWith({
sectorType: "concept",
sectorCode: "BK0001.DC",
tradeDate: "2026-08-28",
})
expect(screen.getByText("暂无在榜历史数据。")).toBeInTheDocument()
fireEvent.click(trigger)
expect(screen.queryByText("暂无在榜历史数据。")).not.toBeInTheDocument()
fireEvent.click(trigger)
useSectorRadarRankings.mockReturnValue(
rankingQueryResult({
data: {
pages: [
{
...ratioPage,
publication: {
...successPublication,
publication_id: "next-day",
},
rows: ratioPage.rows.map((row) => ({
...row,
trade_date: "2026-08-31",
})),
},
],
pageParams: [1],
},
}),
)
rerender(<SectorRadarPage />)
expect(screen.queryByText("暂无在榜历史数据。")).not.toBeInTheDocument()
expect(
screen.getAllByRole("button", { name: "机器人展开在榜历史" })[0],
).toHaveAttribute("aria-expanded", "false")
})
it("resets filters to the first batch in router search state", () => {
routeSearch = { ...routeSearch, page: 3 }
render(<SectorRadarPage />)