"""리포트 탭. Feature별 미리보기, HTML/CSV 다운로드, 전체 리포트 생성. """ from datetime import datetime, timedelta import streamlit as st from core.api_client import ChainShiftClient from core.supabase_client import get_campaign_date_range from .utils import render_feature_section from . import full_report # Period presets: (label, days or None for "all") PERIOD_PRESETS = [ ("최근 1일", 1), ("최근 7일", 7), ("최근 30일", 30), ("최근 90일", 90), ("최근 180일", 180), ("전체 기간", None), ("직접 선택", -1), ] def render(base_ctx: dict): """리포트 탭 렌더링.""" st.markdown("##### 리포트") st.caption("기간별 AI 가시성 분석 리포트를 생성하고, HTML로 다운로드할 수 있습니다.") # Get campaign date range for presets campaign_date_range = get_campaign_date_range(base_ctx["campaign_id"]) if campaign_date_range: first_date_str, last_date_str = campaign_date_range first_date = datetime.strptime(first_date_str, "%Y-%m-%d") last_date = datetime.strptime(last_date_str, "%Y-%m-%d") total_days = (last_date - first_date).days + 1 else: first_date = datetime.now() - timedelta(days=30) last_date = datetime.now() first_date_str = first_date.strftime("%Y-%m-%d") last_date_str = last_date.strftime("%Y-%m-%d") total_days = 31 # Period selection start_date_str, end_date_str = _render_period_selector( first_date, last_date, first_date_str, last_date_str, total_days ) # Show selected period info selected_days = (datetime.strptime(end_date_str, "%Y-%m-%d") - datetime.strptime(start_date_str, "%Y-%m-%d")).days + 1 st.caption(f"📅 선택된 기간: **{start_date_str} ~ {end_date_str}** ({selected_days}일)") st.markdown("---") # 4 Sub-tabs for Features + 1 for Full Report report_tab_summary, report_tab_visibility, report_tab_citation, report_tab_full = st.tabs([ "📊 Executive Summary", "👁️ Visibility & Content", "🔗 Citation Analysis", "📄 전체 리포트", ]) client = ChainShiftClient(api_key=base_ctx.get("api_key"), access_token=base_ctx.get("access_token")) # Tab 1: Executive Summary with report_tab_summary: render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="overview", title="가시성 개요", description="AI 플랫폼별 브랜드 노출 현황과 핵심 지표", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) # Tab 2: Visibility & Content with report_tab_visibility: render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="visibility", title="플랫폼별 가시성", description="ChatGPT, Gemini 등 AI 플랫폼별 자사 vs 경쟁사 노출 비교", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="content-types", title="콘텐츠 유형 분포", description="AI가 인용하는 콘텐츠 유형 (블로그, 뉴스, 공식 사이트 등)", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="sentiment", title="브랜드 감정 분석", description="브랜드별 긍정/부정/중립 감정 분포", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) # Tab 3: Citation Analysis with report_tab_citation: render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="citations", title="인용 출처 순위", description="AI 답변에서 가장 많이 인용되는 도메인과 출처", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="citation-trends", title="인용 추이", description="시간에 따른 인용 출처 변화 트렌드", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) render_feature_section( client=client, campaign_id=base_ctx["campaign_id"], feature_key="homepage-citations", title="홈페이지 인용률", description="자사 홈페이지가 AI 답변에 직접 인용되는 비율", start_date=start_date_str, end_date=end_date_str, api_key=base_ctx.get("api_key") or "", access_token=base_ctx.get("access_token") or "", ) # Tab 4: Full Report with report_tab_full: full_report.render(client, base_ctx, start_date_str, end_date_str) def _render_period_selector( first_date: datetime, last_date: datetime, first_date_str: str, last_date_str: str, total_days: int, ) -> tuple[str, str]: """기간 선택 UI 렌더링. (start_date, end_date) 반환.""" col_period, col_date1, col_date2 = st.columns([1.5, 1, 1]) with col_period: period_options = [label for label, _ in PERIOD_PRESETS] selected_period = st.selectbox( "분석 기간", options=period_options, index=5, # Default to "전체 기간" key="reports:period_select", help=f"캠페인 데이터: {first_date_str} ~ {last_date_str} (총 {total_days}일)", ) period_idx = period_options.index(selected_period) _, period_days = PERIOD_PRESETS[period_idx] if period_days == -1: # Custom selection with col_date1: report_start = st.date_input( "시작일", value=first_date, min_value=first_date, max_value=last_date, key="reports:start_date", ) with col_date2: report_end = st.date_input( "종료일", value=last_date, min_value=first_date, max_value=last_date, key="reports:end_date", ) return str(report_start), str(report_end) elif period_days is None: # All data with col_date1: st.text_input("시작일", value=first_date_str, disabled=True, key="reports:start_display") with col_date2: st.text_input("종료일", value=last_date_str, disabled=True, key="reports:end_display") return first_date_str, last_date_str else: # Preset days end_date = last_date start_date = max(first_date, end_date - timedelta(days=period_days - 1)) start_date_str = start_date.strftime("%Y-%m-%d") end_date_str = end_date.strftime("%Y-%m-%d") with col_date1: st.text_input("시작일", value=start_date_str, disabled=True, key="reports:start_display") with col_date2: st.text_input("종료일", value=end_date_str, disabled=True, key="reports:end_display") return start_date_str, end_date_str