from pydantic import Field from common.models.gmodel import Res_WebPacketProtocol, WebPacketProtocol class StatisticsProtocol(WebPacketProtocol): pass class StatKpi(WebPacketProtocol): total_savings: int = 0 # 총 절감액(목표가 대비, 낙찰세션 target−bid 합) savings_rate: float = 0.0 # 평균 절감률 = 절감합/목표합 award_rate: float = 0.0 # 낙찰률 = 낙찰 마감 / 전체 마감 anchor_reach_rate: float = 0.0 # 평균 앵커 도달률 = (목표−투찰)/(목표−앵커) savings_delta_mom: int = 0 # 전월 대비 절감액 증감 closed_count: int = 0 # 마감 견적 수(창) regen_avg_round: float = 0.0 # 평균 재견적 라운드 markup_suppression_rate: float = 0.0 # 인상억제율(재협상: 직전 라운드 투찰가 대비 이번 투찰가 인하율, 파생) offline_award_count: int = 0 # 낙찰 중 오프라인 협상 결과를 담당자가 반영한 건수(절감액엔 포함) class StatMonthPoint(WebPacketProtocol): month: str # 'YYYY-MM' savings: int = 0 rate: float = 0.0 # 절감률 class StatMarkupPoint(WebPacketProtocol): month: str # 'YYYY-MM' rate: float = 0.0 # 인상억제율(직전 라운드 투찰가 대비 인하율) class StatOutcome(WebPacketProtocol): awarded: int = 0 # CloseReason 1 open_price: int = 0 # 5 가격 미달 open_equal: int = 0 # 6 동가 open_noshow: int = 0 # 7 미응찰 open_reject: int = 0 # 8 거부 class StatParticipation(WebPacketProtocol): bid: int = 0 # 응찰(SessionStatus DONE) no_participate: int = 0 # 미응찰(NOT_PARTICIPATED) rejected: int = 0 # 거부(REJECTED) class StatTypeRow(WebPacketProtocol): label: str award_rate: float = 0.0 avg_savings: int = 0 count: int = 0 class StatCategory(WebPacketProtocol): category: str savings: int = 0 count: int = 0 class StatCardUsage(WebPacketProtocol): type: str # 'nego' | 'wild' label: str uses: int = 0 avg_drop: int = 0 # 사용 직후 상대 제시가 평균 하락. TODO: v1은 0(유형별 빈도만) — chats seq 델타 계산은 다음 단계. class StatScope(WebPacketProtocol): kpi: StatKpi = Field(default_factory=StatKpi) trend: list[StatMonthPoint] = [] markup_trend: list[StatMarkupPoint] = [] # 월별 인상억제율(재협상) outcome: StatOutcome = Field(default_factory=StatOutcome) participation: StatParticipation = Field(default_factory=StatParticipation) type_split: list[StatTypeRow] = [] categories: list[StatCategory] = [] cards: list[StatCardUsage] = [] class Res_StatisticsSummary(Res_WebPacketProtocol): company: StatScope = Field(default_factory=StatScope) # 회사 전체(company_id 스코프) mine: StatScope = Field(default_factory=StatScope) # 내가 만든 견적(user_id 추가 스코프)