o2o-negosium-original/negodata/backend/router/v1/statistics/protocol.py
Mina Choi c32ee300e6 [feat] negodata: 통계 페이지(성과 분석) 추가 — 파생 집계 API + recharts 차트
- 백엔드 GET /v1/statistics/summary: 회사/내견적 스코프, 최근 6개월 파생 집계(저장 X, DDL 0)
- 지표: 총절감/절감률/낙찰률/앵커도달률/마감수/재견적라운드 + 월별추이·마감결과분해·참여율·유형별(협상/견적)·카테고리·카드빈도
- 프론트 recharts+shadcn Chart, 사이드바 통계 nav, /statistics

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:18:33 +09:00

73 lines
2.4 KiB
Python
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.

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 # 평균 재견적 라운드
class StatMonthPoint(WebPacketProtocol):
month: str # 'YYYY-MM'
savings: int = 0
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] = []
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 추가 스코프)