From 69e42400399a9f08f7e7e2a7eed8d61957e83165 Mon Sep 17 00:00:00 2001 From: Mina Choi Date: Thu, 13 Aug 2026 14:04:48 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20negodata:=20=ED=98=91=EC=83=81=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=84=B1=EA=B3=B5=EB=A5=A0=EC=97=90=20=ED=9A=8C?= =?UTF-8?q?=EC=82=AC=20=EC=8A=A4=EC=BD=94=ED=94=84=20=EB=88=84=EB=9D=BD=20?= =?UTF-8?q?=E2=80=94=20=ED=83=80=EC=82=AC=20=EA=B2=AC=EC=A0=81=20=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=EC=9D=B4=20=EC=84=9E=EC=9D=B4=EB=8D=98=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit card_success_map 이 quotations 조인 없이 DB 전체 chats 를 카드별로 집계해, 다른 회사 견적에서 쓰인 이력이 내 카드 목록의 used_count/success_rate 로 잡혔다. 통계 화면 '협상카드 성공률 TOP 5' 와 견적 생성 시 카드 추천 순위가 모두 이 값을 쓴다. - quotations 조인 + 작성자 회사(users.company_id) 스코프 추가 - 소프트 삭제분 제외(sessions.deleted / quotations.deleted 필터 누락분 보강) --- negodata/backend/crud/card_crud.py | 17 +++++++++++++---- negodata/backend/services/card_service.py | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/negodata/backend/crud/card_crud.py b/negodata/backend/crud/card_crud.py index 3e2b122..a7cc288 100644 --- a/negodata/backend/crud/card_crud.py +++ b/negodata/backend/crud/card_crud.py @@ -5,7 +5,7 @@ from sqlalchemy import select, func, and_, or_, update, case from sqlalchemy.ext.asyncio import AsyncSession from common.database.db_session_manager import DB_SESSION_MNG -from common.database.model.models import users, chats, sessions +from common.database.model.models import users, chats, sessions, quotations from common.enums import ErrorType, SessionStatus from common.logger import LOG from common.utils.gtime import GTime @@ -38,7 +38,7 @@ class ICardCRUD(ABC): pass @abstractmethod - async def card_success_map(self, cdb: AsyncSession) -> Tuple[ErrorType, dict]: + async def card_success_map(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, dict]: pass @abstractmethod @@ -83,9 +83,10 @@ class CardCRUD(ICardCRUD): LOG.e_no_callstack(ex) return ErrorType.DB_RUN_FAILED, [], 0 - async def card_success_map(self, cdb: AsyncSession) -> Tuple[ErrorType, dict]: + async def card_success_map(self, cdb: AsyncSession, company_id) -> Tuple[ErrorType, dict]: # 카드별 성공률: 카드 사용(card_used_yn) 채팅이 속한 세션의 타결(DONE) 비율. # {card_id(UUID): (used_sessions, won_sessions)}. 성공=세션 DONE(협상완료). + # 집계 범위는 내 회사 견적에서 쓰인 이력만 — quotations 까지 조인해 작성자 회사로 좁힌다. try: stmt = ( select( @@ -94,7 +95,15 @@ class CardCRUD(ICardCRUD): func.count(func.distinct(case((sessions.status == SessionStatus.DONE.value, chats.session_id)))).label("won"), ) .join(sessions, sessions.session_id == chats.session_id) - .where(chats.card_used_yn == True, chats.card_id.isnot(None), chats.deleted == False) # noqa: E712 + .join(quotations, quotations.qt_id == sessions.quotation_id) + .where( + chats.card_used_yn == True, # noqa: E712 + chats.card_id.isnot(None), + chats.deleted == False, # noqa: E712 + sessions.deleted == False, # noqa: E712 + quotations.deleted == False, # noqa: E712 + quotations.user_id.in_(select(users.user_id).where(users.company_id == company_id)), + ) .group_by(chats.card_id) ) err, rows = await DB_SESSION_MNG.execute(cdb, stmt) diff --git a/negodata/backend/services/card_service.py b/negodata/backend/services/card_service.py index b993f63..cff50b2 100644 --- a/negodata/backend/services/card_service.py +++ b/negodata/backend/services/card_service.py @@ -142,7 +142,7 @@ class CardService: # 카드 성공률(#12) — 카드 사용→타결 집계를 page 카드에 매핑. _e, success_map = await DB_SESSION_MNG.execute_lambda( nego_cards.DBType(), DBWRType.DB_READ.value, - lambda s: self.card_crud.card_success_map(s), + lambda s: self.card_crud.card_success_map(s, company_uuid), ) success_map = success_map or {} for c in page: