443 lines
12 KiB
Python
443 lines
12 KiB
Python
# AI_CHANGELOG
|
||
# - 2026-03-20 | Prompt: R3 项目类型筛选接口重建 | SkillFilterEnum 和 ProjectFilterEnum
|
||
# 枚举值从 all/chinese/snooker/mahjong/karaoke 改为 ALL/BILLIARD/SNOOKER/MAHJONG/KTV,
|
||
# 与 dws.cfg_area_category.category_code 一致,消除前后端映射层。
|
||
|
||
"""三看板接口 Pydantic Schema(BOARD-1/2/3 请求参数枚举 + 响应模型)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from enum import Enum
|
||
|
||
from app.schemas.base import CamelModel
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 请求参数枚举(Task 2.1)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class CoachSortEnum(str, Enum):
|
||
"""BOARD-1 排序维度。"""
|
||
perf_desc = "perf_desc"
|
||
perf_asc = "perf_asc"
|
||
salary_desc = "salary_desc"
|
||
salary_asc = "salary_asc"
|
||
sv_desc = "sv_desc"
|
||
task_desc = "task_desc"
|
||
|
||
|
||
class SkillFilterEnum(str, Enum):
|
||
"""BOARD-1 技能筛选(值与 dws.cfg_area_category.category_code 一致)。"""
|
||
# CHANGE 2026-03-20 | R3 修复:枚举值从 chinese/snooker 等前端自定义值
|
||
# 改为数据库 category_code(BILLIARD/SNOOKER/MAHJONG/KTV),消除映射层。
|
||
ALL = "ALL"
|
||
BILLIARD = "BILLIARD"
|
||
SNOOKER = "SNOOKER"
|
||
MAHJONG = "MAHJONG"
|
||
KTV = "KTV"
|
||
|
||
|
||
class BoardTimeEnum(str, Enum):
|
||
"""BOARD-1 时间范围。"""
|
||
month = "month"
|
||
quarter = "quarter"
|
||
last_month = "last_month"
|
||
last_3m = "last_3m"
|
||
last_quarter = "last_quarter"
|
||
last_6m = "last_6m"
|
||
|
||
|
||
class CustomerDimensionEnum(str, Enum):
|
||
"""BOARD-2 客户维度。"""
|
||
recall = "recall"
|
||
potential = "potential"
|
||
balance = "balance"
|
||
recharge = "recharge"
|
||
recent = "recent"
|
||
spend60 = "spend60"
|
||
freq60 = "freq60"
|
||
loyal = "loyal"
|
||
|
||
|
||
class ProjectFilterEnum(str, Enum):
|
||
"""BOARD-2 项目筛选(值与 dws.cfg_area_category.category_code 一致)。"""
|
||
# CHANGE 2026-03-20 | R3 修复:枚举值从 chinese/snooker 等前端自定义值
|
||
# 改为数据库 category_code(BILLIARD/SNOOKER/MAHJONG/KTV),消除映射层。
|
||
ALL = "ALL"
|
||
BILLIARD = "BILLIARD"
|
||
SNOOKER = "SNOOKER"
|
||
MAHJONG = "MAHJONG"
|
||
KTV = "KTV"
|
||
|
||
|
||
class FinanceTimeEnum(str, Enum):
|
||
"""BOARD-3 时间范围。"""
|
||
month = "month"
|
||
lastMonth = "lastMonth"
|
||
week = "week"
|
||
lastWeek = "lastWeek"
|
||
quarter3 = "quarter3"
|
||
quarter = "quarter"
|
||
lastQuarter = "lastQuarter"
|
||
half6 = "half6"
|
||
|
||
|
||
class AreaFilterEnum(str, Enum):
|
||
"""BOARD-3 区域筛选。"""
|
||
all = "all"
|
||
hall = "hall"
|
||
hallA = "hallA"
|
||
hallB = "hallB"
|
||
hallC = "hallC"
|
||
mahjong = "mahjong"
|
||
teamBuilding = "teamBuilding"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# BOARD-1 响应 Schema(Task 2.2)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class CoachSkillItem(CamelModel):
|
||
text: str
|
||
cls: str
|
||
|
||
|
||
class CoachBoardItem(CamelModel):
|
||
"""助教看板单条记录(扁平结构,包含所有维度字段)。"""
|
||
|
||
# 基础字段(所有维度共享)
|
||
id: int
|
||
name: str
|
||
initial: str
|
||
avatar_gradient: str
|
||
level: str # star/senior/middle/junior
|
||
skills: list[CoachSkillItem]
|
||
top_customers: list[str] # ["💖 王先生", "💛 李女士"]
|
||
|
||
# perf 维度
|
||
perf_hours: float = 0.0
|
||
perf_hours_before: float | None = None
|
||
perf_gap: str | None = None # "距升档 13.8h" 或 None
|
||
perf_reached: bool = False
|
||
|
||
# salary 维度
|
||
salary: float = 0.0
|
||
salary_perf_hours: float = 0.0
|
||
salary_perf_before: float | None = None
|
||
|
||
# sv 维度
|
||
sv_amount: float = 0.0
|
||
sv_customer_count: int = 0
|
||
sv_consume: float = 0.0
|
||
|
||
# task 维度
|
||
task_recall: int = 0
|
||
task_callback: int = 0
|
||
|
||
|
||
class CoachBoardResponse(CamelModel):
|
||
items: list[CoachBoardItem]
|
||
dim_type: str # perf/salary/sv/task
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# BOARD-2 响应 Schema(Task 2.3)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class CustomerAssistant(CamelModel):
|
||
name: str
|
||
cls: str
|
||
heart_score: float
|
||
badge: str | None = None
|
||
badge_cls: str | None = None
|
||
|
||
|
||
class CustomerBoardItemBase(CamelModel):
|
||
"""客户看板基础字段(所有维度共享)。"""
|
||
id: int
|
||
name: str
|
||
initial: str
|
||
avatar_cls: str
|
||
assistants: list[CustomerAssistant]
|
||
|
||
|
||
class RecallItem(CustomerBoardItemBase):
|
||
ideal_days: int
|
||
elapsed_days: int
|
||
overdue_days: int
|
||
visits_30d: int
|
||
balance: str
|
||
recall_index: float
|
||
|
||
|
||
class PotentialTag(CamelModel):
|
||
text: str
|
||
theme: str
|
||
|
||
|
||
class PotentialItem(CustomerBoardItemBase):
|
||
potential_tags: list[PotentialTag]
|
||
spend_30d: float
|
||
avg_visits: float
|
||
avg_spend: float
|
||
|
||
|
||
class BalanceItem(CustomerBoardItemBase):
|
||
balance: str
|
||
last_visit: str # "3天前"
|
||
monthly_consume: float
|
||
available_months: str # "约0.8个月"
|
||
|
||
|
||
class RechargeItem(CustomerBoardItemBase):
|
||
last_recharge: str
|
||
recharge_amount: float
|
||
recharges_60d: int
|
||
current_balance: str
|
||
|
||
|
||
class RecentItem(CustomerBoardItemBase):
|
||
days_ago: int
|
||
visit_freq: str # "6.2次/月"
|
||
ideal_days: int
|
||
visits_30d: int
|
||
avg_spend: float
|
||
|
||
|
||
class Spend60Item(CustomerBoardItemBase):
|
||
spend_60d: float
|
||
visits_60d: int
|
||
high_spend_tag: bool
|
||
avg_spend: float
|
||
|
||
|
||
class WeeklyVisit(CamelModel):
|
||
val: int
|
||
pct: int # 0-100
|
||
|
||
|
||
class Freq60Item(CustomerBoardItemBase):
|
||
visits_60d: int
|
||
avg_interval: str # "5.0天"
|
||
weekly_visits: list[WeeklyVisit] # 固定长度 8
|
||
spend_60d: float
|
||
|
||
|
||
class CoachDetail(CamelModel):
|
||
name: str
|
||
cls: str
|
||
heart_score: float
|
||
badge: str | None = None
|
||
avg_duration: str
|
||
service_count: int
|
||
coach_spend: float
|
||
relation_idx: float
|
||
|
||
|
||
class LoyalItem(CustomerBoardItemBase):
|
||
intimacy: float
|
||
top_coach_name: str
|
||
top_coach_heart: float
|
||
top_coach_score: float
|
||
coach_name: str
|
||
coach_ratio: str # "78%"
|
||
coach_details: list[CoachDetail]
|
||
|
||
|
||
class CustomerBoardResponse(CamelModel):
|
||
items: list[dict] # 实际类型取决于 dimension
|
||
total: int
|
||
page: int
|
||
page_size: int
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# BOARD-3 响应 Schema(Task 2.4)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
class OverviewPanel(CamelModel):
|
||
"""经营一览:8 项核心指标 + 各 3 个环比字段(Optional,compare=0 时为 None)。"""
|
||
occurrence: float
|
||
discount: float # 负值
|
||
discount_rate: float
|
||
confirmed_revenue: float
|
||
cash_in: float
|
||
cash_out: float
|
||
cash_balance: float
|
||
balance_rate: float
|
||
# occurrence 环比
|
||
occurrence_compare: str | None = None
|
||
occurrence_down: bool | None = None
|
||
occurrence_flat: bool | None = None
|
||
# discount 环比
|
||
discount_compare: str | None = None
|
||
discount_down: bool | None = None
|
||
discount_flat: bool | None = None
|
||
# discount_rate 环比
|
||
discount_rate_compare: str | None = None
|
||
discount_rate_down: bool | None = None
|
||
discount_rate_flat: bool | None = None
|
||
# confirmed_revenue 环比
|
||
confirmed_revenue_compare: str | None = None
|
||
confirmed_revenue_down: bool | None = None
|
||
confirmed_revenue_flat: bool | None = None
|
||
# cash_in 环比
|
||
cash_in_compare: str | None = None
|
||
cash_in_down: bool | None = None
|
||
cash_in_flat: bool | None = None
|
||
# cash_out 环比
|
||
cash_out_compare: str | None = None
|
||
cash_out_down: bool | None = None
|
||
cash_out_flat: bool | None = None
|
||
# cash_balance 环比
|
||
cash_balance_compare: str | None = None
|
||
cash_balance_down: bool | None = None
|
||
cash_balance_flat: bool | None = None
|
||
# balance_rate 环比
|
||
balance_rate_compare: str | None = None
|
||
balance_rate_down: bool | None = None
|
||
balance_rate_flat: bool | None = None
|
||
|
||
|
||
class GiftCell(CamelModel):
|
||
value: float
|
||
compare: str | None = None
|
||
down: bool | None = None
|
||
flat: bool | None = None
|
||
|
||
|
||
class GiftRow(CamelModel):
|
||
"""赠送卡矩阵一行:合计 / 酒水卡 / 台费卡 / 抵用券。"""
|
||
label: str # "新增" / "消费" / "余额"
|
||
total: GiftCell
|
||
liquor: GiftCell
|
||
table_fee: GiftCell
|
||
voucher: GiftCell
|
||
|
||
|
||
class RechargePanel(CamelModel):
|
||
"""预收资产板块:储值卡 5 指标 + 赠送卡 3×4 矩阵 + 全卡余额。"""
|
||
actual_income: float
|
||
first_charge: float
|
||
renew_charge: float
|
||
consumed: float
|
||
card_balance: float
|
||
gift_rows: list[GiftRow] # 3 行
|
||
all_card_balance: float
|
||
# 储值卡各项环比字段
|
||
actual_income_compare: str | None = None
|
||
actual_income_down: bool | None = None
|
||
actual_income_flat: bool | None = None
|
||
first_charge_compare: str | None = None
|
||
first_charge_down: bool | None = None
|
||
first_charge_flat: bool | None = None
|
||
renew_charge_compare: str | None = None
|
||
renew_charge_down: bool | None = None
|
||
renew_charge_flat: bool | None = None
|
||
consumed_compare: str | None = None
|
||
consumed_down: bool | None = None
|
||
consumed_flat: bool | None = None
|
||
card_balance_compare: str | None = None
|
||
card_balance_down: bool | None = None
|
||
card_balance_flat: bool | None = None
|
||
|
||
|
||
class RevenueStructureRow(CamelModel):
|
||
id: str
|
||
name: str
|
||
desc: str | None = None
|
||
is_sub: bool = False
|
||
amount: float
|
||
discount: float
|
||
booked: float
|
||
booked_compare: str | None = None
|
||
|
||
|
||
class RevenueItem(CamelModel):
|
||
label: str
|
||
amount: float
|
||
|
||
|
||
class ChannelItem(CamelModel):
|
||
label: str
|
||
amount: float
|
||
|
||
|
||
class RevenuePanel(CamelModel):
|
||
structure_rows: list[RevenueStructureRow]
|
||
price_items: list[RevenueItem] # 4 项
|
||
total_occurrence: float
|
||
discount_items: list[RevenueItem] # 4 项
|
||
confirmed_total: float
|
||
channel_items: list[ChannelItem] # 3 项
|
||
|
||
|
||
class CashflowItem(CamelModel):
|
||
label: str
|
||
amount: float
|
||
|
||
|
||
class CashflowPanel(CamelModel):
|
||
consume_items: list[CashflowItem] # 3 项
|
||
recharge_items: list[CashflowItem] # 1 项
|
||
total: float
|
||
|
||
|
||
class ExpenseItem(CamelModel):
|
||
label: str
|
||
amount: float
|
||
compare: str | None = None
|
||
down: bool | None = None
|
||
flat: bool | None = None
|
||
|
||
|
||
class ExpensePanel(CamelModel):
|
||
operation_items: list[ExpenseItem] # 3 项
|
||
fixed_items: list[ExpenseItem] # 4 项
|
||
coach_items: list[ExpenseItem] # 4 项
|
||
platform_items: list[ExpenseItem] # 3 项
|
||
total: float
|
||
total_compare: str | None = None
|
||
total_down: bool | None = None
|
||
total_flat: bool | None = None
|
||
|
||
|
||
class CoachAnalysisRow(CamelModel):
|
||
level: str
|
||
pay: float
|
||
share: float
|
||
hourly: float
|
||
pay_compare: str | None = None
|
||
pay_down: bool | None = None
|
||
share_compare: str | None = None
|
||
share_down: bool | None = None
|
||
hourly_compare: str | None = None
|
||
hourly_flat: bool | None = None
|
||
|
||
|
||
class CoachAnalysisTable(CamelModel):
|
||
total_pay: float
|
||
total_share: float
|
||
avg_hourly: float
|
||
total_pay_compare: str | None = None
|
||
total_pay_down: bool | None = None
|
||
total_share_compare: str | None = None
|
||
total_share_down: bool | None = None
|
||
avg_hourly_compare: str | None = None
|
||
avg_hourly_flat: bool | None = None
|
||
rows: list[CoachAnalysisRow] # 4 行:初级/中级/高级/星级
|
||
|
||
|
||
class CoachAnalysisPanel(CamelModel):
|
||
basic: CoachAnalysisTable # 基础课/陪打
|
||
incentive: CoachAnalysisTable # 激励课/超休
|
||
|
||
|
||
class FinanceBoardResponse(CamelModel):
|
||
overview: OverviewPanel
|
||
recharge: RechargePanel | None # area≠all 时为 null
|
||
revenue: RevenuePanel
|
||
cashflow: CashflowPanel
|
||
expense: ExpensePanel
|
||
coach_analysis: CoachAnalysisPanel
|