chainshift-dashboard / core /api_client_sentiment.py
GitHub Action
Sync from GitHub
ef78361
Raw
History Blame Contribute Delete
10.4 kB
"""Sentiment API methods for ChainShiftClient.
Mixin class extracted from api_client.py.
Methods access self._get, self._post, self._session, self.base_url, self.headers from the parent class.
"""
from typing import Any
class SentimentApiMixin:
"""Gen3 Nudge, Brand Mentions, LLM Verification, Feedback, Keyword APIs."""
# ========================================================================
# Gen3 APIs - Nudge Detection
# ========================================================================
def get_nudge_candidates(
self,
campaign_id: int,
page: int = 1,
page_size: int = 50,
bit_quadrant: str | None = None,
platform: str | None = None,
) -> dict:
"""Get answers flagged for nudge (in-house brand negative mentions).
Returns:
- total_nudge_candidates: int
- by_confidence_tier: {HIGH: n, MEDIUM: n, LOW: n}
- by_platform: {CHATGPT: n, GOOGLE_AI: n, ...}
- by_cej: {AWARENESS_COMPARISON: n, PURCHASE: n, ...}
- by_bit_quadrant: {neutral: n, ...}
- candidates: list of nudge items
"""
params = {"page": page, "page_size": page_size}
if bit_quadrant:
params["bit_quadrant"] = bit_quadrant
if platform:
params["platform"] = platform
return self._get(f"/api/v1/sentiment/campaigns/{campaign_id}/nudge-candidates", params)
def get_brand_mentions(
self,
campaign_id: int,
page: int = 1,
page_size: int = 50,
brand_type: str | None = None,
polarity: str | None = None,
llm_verified: str | None = None,
brand_name: str | None = None,
sort_by: str | None = "analyzed_at",
sort_desc: bool = True,
) -> dict:
"""Get brand mention analysis."""
params = {"page": page, "page_size": page_size, "sort_desc": sort_desc}
if brand_type:
params["brand_type"] = brand_type
if polarity:
params["polarity"] = polarity
if llm_verified:
params["llm_verified"] = llm_verified
if brand_name:
params["brand_name"] = brand_name
if sort_by:
params["sort_by"] = sort_by
return self._get(f"/api/v1/sentiment/campaigns/{campaign_id}/brand-mentions", params)
def export_brand_mentions(
self,
campaign_id: int,
brand_type: str | None = None,
polarity: str | None = None,
llm_verified: str | None = None,
brand_name: str | None = None,
include_full_answers: bool = False,
) -> bytes:
"""Export brand mentions to Excel."""
params = {}
if brand_type:
params["brand_type"] = brand_type
if polarity:
params["polarity"] = polarity
if llm_verified:
params["llm_verified"] = llm_verified
if brand_name:
params["brand_name"] = brand_name
if include_full_answers:
params["include_full_answers"] = "true"
url = f"{self.base_url}/api/v1/sentiment/campaigns/{campaign_id}/brand-mentions/export"
response = self._session.get(url, params=params, headers=self.headers, timeout=120)
response.raise_for_status()
return response.content
# ========================================================================
# LLM Verification & Human Feedback APIs
# ========================================================================
def verify_answer(self, answer_id: int, force: bool = False) -> dict:
"""Request LLM 2차 검증 for an answer."""
return self._post("/api/v1/sentiment/verify", {
"answer_id": answer_id,
"force": force,
})
def submit_feedback(
self,
answer_id: int,
campaign_id: int,
feedback_type: str,
corrected_polarity: str | None = None,
wrong_reason: str | None = None,
comment: str | None = None,
) -> dict:
"""Submit human feedback for an answer sentiment."""
data = {
"answer_id": answer_id,
"campaign_id": campaign_id,
"feedback_type": feedback_type,
}
if corrected_polarity:
data["corrected_polarity"] = corrected_polarity
if wrong_reason:
data["wrong_reason"] = wrong_reason
if comment:
data["comment"] = comment
return self._post("/api/v1/sentiment/feedback", data)
def get_feedback_stats(self, campaign_id: int) -> dict:
"""Get feedback statistics for a campaign."""
return self._get(f"/api/v1/sentiment/campaigns/{campaign_id}/feedback/stats")
def get_full_answers(self, answer_ids: list[int]) -> dict:
"""Get full answer text for multiple answers."""
return self._post("/api/v1/sentiment/answers/full", {"answer_ids": answer_ids})
def export_nudge_candidates(
self,
campaign_id: int,
include_full_answers: bool = False,
include_evidence: bool = True,
llm_verified_only: bool = False,
platform: str | None = None,
llm_is_negative: bool | None = None,
) -> bytes:
"""Export nudge candidates to Excel."""
url = f"{self.base_url}/api/v1/sentiment/campaigns/{campaign_id}/nudge-candidates/export"
params = {
"include_full_answers": str(include_full_answers).lower(),
"include_evidence": str(include_evidence).lower(),
"llm_verified_only": str(llm_verified_only).lower(),
}
if platform:
params["platform"] = platform
if llm_is_negative is not None:
params["llm_is_negative"] = str(llm_is_negative).lower()
response = self._session.get(url, headers=self.headers, params=params, timeout=120)
response.raise_for_status()
return response.content
# ========================================================================
# Keyword Sentiment APIs
# ========================================================================
def start_keyword_analysis(self, campaign_id: int, keywords: list[str]) -> dict:
"""Start a keyword sentiment analysis job."""
return self._post(
f"/api/v1/sentiment/campaigns/{campaign_id}/keyword-analysis",
{"keywords": keywords},
)
def get_keyword_summary(
self,
campaign_id: int,
keyword: str | None = None,
page: int = 1,
page_size: int = 50,
) -> dict:
"""Get keyword sentiment summary with optional filter and pagination."""
params: dict[str, Any] = {"page": page, "page_size": page_size}
if keyword:
params["keyword"] = keyword
return self._get(f"/api/v1/sentiment/campaigns/{campaign_id}/keyword-summary", params)
def get_keyword_results(
self,
campaign_id: int,
keyword: str | None = None,
sentiment: str | None = None,
brand_name: str | None = None,
competitor: bool = False,
llm_verified: str | None = None,
llm_is_negative: bool | None = None,
platform: str | None = None,
brand_only: bool = False,
no_brand: bool = False,
page: int = 1,
page_size: int = 50,
) -> dict:
"""Get keyword results with filters."""
params: dict[str, Any] = {"page": page, "page_size": page_size}
if keyword:
params["keyword"] = keyword
if sentiment:
params["sentiment"] = sentiment
if brand_name:
params["brand_name"] = brand_name
if competitor:
params["competitor"] = "true"
if llm_verified:
params["llm_verified"] = llm_verified
if llm_is_negative is not None:
params["llm_is_negative"] = str(llm_is_negative).lower()
if platform:
params["platform"] = platform
if brand_only:
params["brand_only"] = "true"
if no_brand:
params["no_brand"] = "true"
return self._get(
f"/api/v1/sentiment/campaigns/{campaign_id}/keyword-results", params
)
def get_keyword_brand_analysis(
self,
campaign_id: int,
keyword: str | None = None,
brand_name: str | None = None,
competitor: bool = False,
) -> dict:
"""Get keyword x brand cross analysis."""
params: dict[str, Any] = {}
if keyword:
params["keyword"] = keyword
if brand_name:
params["brand_name"] = brand_name
if competitor:
params["competitor"] = "true"
return self._get(
f"/api/v1/sentiment/campaigns/{campaign_id}/keyword-brand-analysis", params
)
def export_keyword_results(
self,
campaign_id: int,
keyword: str | None = None,
sentiment: str | None = None,
brand_name: str | None = None,
competitor: bool = False,
llm_verified: str | None = None,
llm_is_negative: bool | None = None,
platform: str | None = None,
brand_only: bool = False,
no_brand: bool = False,
format: str = "xlsx",
) -> bytes:
"""Export keyword results to Excel or CSV with same filters as list endpoint."""
params: dict[str, Any] = {"format": format}
if keyword:
params["keyword"] = keyword
if sentiment:
params["sentiment"] = sentiment
if brand_name:
params["brand_name"] = brand_name
if competitor:
params["competitor"] = "true"
if llm_verified:
params["llm_verified"] = llm_verified
if llm_is_negative is not None:
params["llm_is_negative"] = str(llm_is_negative).lower()
if platform:
params["platform"] = platform
if brand_only:
params["brand_only"] = "true"
if no_brand:
params["no_brand"] = "true"
url = f"{self.base_url}/api/v1/sentiment/campaigns/{campaign_id}/keyword-results/export"
response = self._session.get(url, params=params, headers=self.headers, timeout=120)
response.raise_for_status()
return response.content