"use client"; import { getLevelBadge } from "@/lib/utils"; import type { RecommendationData } from "@/lib/api"; export default function StockCard({ rec }: { rec: RecommendationData }) { const badge = getLevelBadge(rec.level); const aiConviction = rec.llm_score != null ? Math.round(rec.llm_score) : null; const recallLabels: Record = { sector_recall: "主线召回", trend_scan: "趋势召回", intraday_active: "盘中异动", hot_sector_core: "板块核心", sector_leader: "前排线索", moneyflow_support: "资金支撑", volume_active: "量能活跃", }; const prefilterLabel: Record = { priority: "AI优先深看", watch: "AI保留观察", ignore: "AI建议忽略", "": "待AI预筛", }; // 入场信号标签 const signalTypeMap: Record = { breakout: { label: "突破型", style: "bg-red-500/15 text-red-400 border-red-500/20" }, pullback: { label: "回踩型", style: "bg-blue-500/15 text-blue-400 border-blue-500/20" }, launch: { label: "启动型", style: "bg-orange-500/15 text-orange-400 border-orange-500/20" }, }; // 向后兼容:旧数据使用 strategy 字段 const signalInfo = signalTypeMap[rec.entry_signal_type || ""]; const legacyStrategy = rec.strategy === "potential" ? { label: "潜在启动", style: "bg-cyan-500/15 text-cyan-400 border-cyan-500/20" } : rec.strategy === "momentum" ? { label: "强中选强", style: "bg-amber-500/15 text-amber-400 border-amber-500/20" } : null; const tag = signalInfo || legacyStrategy; const actionPlanStyle: Record = { "可操作": "bg-red-500/15 text-red-400 border-red-500/20", "重点关注": "bg-amber-500/15 text-amber-400 border-amber-500/20", "观察": "bg-surface-3 text-text-muted border-border-default", }; const lifecycleLabel: Record = { candidate: "观察池", actionable: "可操作", tracking: "跟踪中", closed_win: "盈利结束", closed_loss: "亏损结束", expired: "到期复盘", invalidated: "已失效", }; const actionPlanCopy: Record = { "可操作": "触发条件成立时才执行", "重点关注": "等待确认,不提前交易", "观察": "只记录,不主动出手", }; const evidence = [ rec.prefilter_reason, rec.focus_points?.[0], rec.reasons?.[0], rec.entry_timing, rec.data_freshness, ].filter(Boolean).slice(0, 3) as string[]; return (
{/* Clickable top section — navigates to stock detail */} {/* Header: Name + Action state */}
{rec.name} {rec.signal === "BUY" && ( 买入 )} {tag && ( {tag.label} )} {rec.action_plan && ( {rec.action_plan} )}
{rec.ts_code} · {rec.sector}
结论
{rec.action_plan ?? "观察"}
{aiConviction != null ? (
AI {aiConviction}/10
) : null}
{(rec.action_plan || rec.trigger_condition || rec.invalidation_condition || rec.suggested_position_pct) && (
AI 操作计划
{rec.action_plan ? actionPlanCopy[rec.action_plan] ?? rec.action_plan : "等待结论"}
{rec.trigger_condition && (
触发:{rec.trigger_condition}
)} {rec.invalidation_condition && (
失效:{rec.invalidation_condition}
)}
{rec.suggested_position_pct != null && ( 仓位 {rec.suggested_position_pct}% )} {rec.review_after_days ? ( {rec.review_after_days}日复盘 ) : null} {aiConviction != null ? ( AI置信 {aiConviction}/10 ) : rec.score ? ( 参考分 {rec.score.toFixed(0)} ) : null} {rec.level}
)} {evidence.length > 0 && (
AI 关注点
{evidence.map((item, index) => (
{item}
))}
{(rec.recall_tags ?? []).slice(0, 3).map((tag) => ( {recallLabels[tag] ?? tag} ))} {prefilterLabel[rec.prefilter_decision ?? ""] ?? "AI预筛"}
)} {(rec.focus_points?.length ?? 0) > 0 && (
深裁决前重点观察
{(rec.focus_points ?? []).slice(0, 3).map((item, index) => (
{item}
))}
)} {/* Price reference */} {rec.entry_price && (
买入 {rec.entry_price}
目标 {rec.target_price}
止损 {rec.stop_loss}
)} {rec.tracking && (
生命周期 · {lifecycleLabel[rec.lifecycle_status || ""] ?? rec.lifecycle_status ?? "跟踪"} {rec.tracking.days_since_recommendation ?? 0}日 · {rec.tracking.track_date}
{rec.tracking.review_note && (
{rec.tracking.review_note}
)}
)} {/* Reasons */}
{rec.reasons.slice(0, 3).map((r, i) => (
{r}
))}
召回、预筛与推演链路已归档 {aiConviction != null && ( AI {aiConviction}/10 )}
查看推演
{/* Risk note */} {rec.risk_note && (
⚠ {rec.risk_note}
)}
); } function TrackingMetric({ label, value }: { label: string; value: number | null }) { const num = value ?? 0; const color = num > 0 ? "text-red-400" : num < 0 ? "text-emerald-400" : "text-text-secondary"; return (
{label}
{num > 0 ? "+" : ""}{num.toFixed(2)}%
); }