Spaces:
Sleeping
Sleeping
| """Attention vs Density 4-Quadrant Scatter. | |
| X-axis: attention_score | |
| Y-axis: citation_density | |
| Quadrant lines at median values. | |
| Point size: fanout_count, hover: cluster_label. | |
| """ | |
| import statistics | |
| import streamlit as st | |
| import plotly.graph_objects as go | |
| # Quadrant colors | |
| QUAD_COLORS = { | |
| "high_attn_low_density": "#10B981", # Green - Opportunity | |
| "high_attn_high_density": "#3B82F6", # Blue - Competitive | |
| "low_attn_low_density": "#9CA3AF", # Gray - Niche | |
| "low_attn_high_density": "#EF4444", # Red - Crowded | |
| } | |
| def render_distribution(clusters: list[dict], frame: str = "all"): | |
| """Render attention vs density quadrant scatter chart.""" | |
| scored = [ | |
| c for c in clusters | |
| if c.get("attention_score") is not None | |
| and c.get("citation_density") is not None | |
| ] | |
| if not scored: | |
| st.info("์ค์ฝ์ด๊ฐ ๊ณ์ฐ๋ ํด๋ฌ์คํฐ๊ฐ ์์ต๋๋ค.") | |
| return | |
| # 4-Quadrant business interpretation (frame-specific) | |
| if frame == "demand": | |
| st.markdown(""" | |
| ChatGPT Demand ํ ํฝ์ **๊ฒ์ ๋น๋**(๊ฐ๋ก์ถ)์ **์ถ์ฒ ๊ฒฝ์**(์ธ๋ก์ถ)๋ก ๋ถ๋ฅํฉ๋๋ค: | |
| - **Opportunity** (์ฐํ๋จ): ๊ฒ์ ๋น๋ ๋์ + ๊ฒฝ์ ๋ฎ์ โ ์ฝํ ์ธ ์ ์ ๊ธฐํ | |
| - **Competitive** (์ฐ์๋จ): ๊ฒ์ ๋น๋ ๋์ + ๊ฒฝ์ ๋์ โ ์ฐจ๋ณํ ํ์ | |
| - **Niche** (์ขํ๋จ): ๊ฒ์ ๋น๋ ๋ฎ์ + ๊ฒฝ์ ๋ฎ์ โ ํ์ ์์ญ | |
| - **Crowded** (์ข์๋จ): ๊ฒ์ ๋น๋ ๋ฎ์ + ๊ฒฝ์ ๋์ โ ํฌํ ์์ญ | |
| """) | |
| elif frame == "supply": | |
| st.markdown(""" | |
| Gemini Supply ํ ํฝ์ **์ธ์ฉ ๋น๋**(๊ฐ๋ก์ถ)์ **์ถ์ฒ ์ง์ค๋**(์ธ๋ก์ถ)๋ก ๋ถ๋ฅํฉ๋๋ค: | |
| - **Opportunity** (์ฐํ๋จ): ์ธ์ฉ ๋น๋ ๋์ + ์ถ์ฒ ๋ถ์ฐ โ ์ ์ถ์ฒ ์ง์ ๊ธฐํ | |
| - **Competitive** (์ฐ์๋จ): ์ธ์ฉ ๋น๋ ๋์ + ์ถ์ฒ ์ง์ค โ ๊ธฐ์กด ๊ถ์์ ์ง๋ฐฐ | |
| - **Niche** (์ขํ๋จ): ์ธ์ฉ ๋น๋ ๋ฎ์ + ์ถ์ฒ ๋ถ์ฐ โ ํ์ ์์ญ | |
| - **Crowded** (์ข์๋จ): ์ธ์ฉ ๋น๋ ๋ฎ์ + ์ถ์ฒ ์ง์ค โ ํฌํ ์์ญ | |
| """) | |
| else: | |
| st.markdown(""" | |
| ํ ํฝ์ **AI ๊ด์ฌ๋**(๊ฐ๋ก์ถ)์ **๊ฒฝ์ ๋ฐ๋**(์ธ๋ก์ถ)๋ก ๋ถ๋ฅํฉ๋๋ค: | |
| - **Opportunity** (์ฐํ๋จ): AI ๊ด์ฌ ๋์ + ๊ฒฝ์ ๋ฎ์ โ ์ฝํ ์ธ ์ ์ ๊ธฐํ | |
| - **Competitive** (์ฐ์๋จ): AI ๊ด์ฌ ๋์ + ๊ฒฝ์ ๋์ โ ์ฐจ๋ณํ ํ์ | |
| - **Niche** (์ขํ๋จ): AI ๊ด์ฌ ๋ฎ์ + ๊ฒฝ์ ๋ฎ์ โ ํ์ ์์ญ | |
| - **Crowded** (์ข์๋จ): AI ๊ด์ฌ ๋ฎ์ + ๊ฒฝ์ ๋์ โ ํฌํ ์์ญ | |
| """) | |
| attns = [float(c["attention_score"]) for c in scored] | |
| densities = [float(c["citation_density"]) for c in scored] | |
| median_attn = statistics.median(attns) | |
| median_density = statistics.median(densities) | |
| # Classify each point into quadrant | |
| xs, ys, sizes, colors, hover_texts = [], [], [], [], [] | |
| quadrant_counts = {"opportunity": 0, "competitive": 0, "niche": 0, "crowded": 0} | |
| for c in scored: | |
| attn = float(c["attention_score"]) | |
| density = float(c["citation_density"]) | |
| fanout_count = c.get("fanout_count", 10) | |
| label = c.get("cluster_label") or f"Cluster-{c['id'][:8]}" | |
| xs.append(attn) | |
| ys.append(density) | |
| sizes.append(max(5, min(35, fanout_count / 5))) | |
| if attn >= median_attn and density < median_density: | |
| color = QUAD_COLORS["high_attn_low_density"] | |
| quad = "Opportunity" | |
| quadrant_counts["opportunity"] += 1 | |
| elif attn >= median_attn and density >= median_density: | |
| color = QUAD_COLORS["high_attn_high_density"] | |
| quad = "Competitive" | |
| quadrant_counts["competitive"] += 1 | |
| elif attn < median_attn and density < median_density: | |
| color = QUAD_COLORS["low_attn_low_density"] | |
| quad = "Niche" | |
| quadrant_counts["niche"] += 1 | |
| else: | |
| color = QUAD_COLORS["low_attn_high_density"] | |
| quad = "Crowded" | |
| quadrant_counts["crowded"] += 1 | |
| colors.append(color) | |
| opp = float(c.get("opportunity_score", 0) or 0) | |
| hover_texts.append( | |
| f"<b>{label}</b><br>" | |
| f"Attention: {attn:.4f}<br>" | |
| f"Density: {density:.4f}<br>" | |
| f"Opportunity: {opp:.4f}<br>" | |
| f"Fanouts: {fanout_count}<br>" | |
| f"Quadrant: {quad}" | |
| ) | |
| fig = go.Figure() | |
| # Data points | |
| fig.add_trace(go.Scatter( | |
| x=xs, | |
| y=ys, | |
| mode="markers", | |
| marker=dict( | |
| size=sizes, | |
| color=colors, | |
| opacity=0.7, | |
| line=dict(width=0.5, color="#333"), | |
| ), | |
| text=hover_texts, | |
| hoverinfo="text", | |
| showlegend=False, | |
| )) | |
| # Quadrant lines (add padding to avoid collapse when all values are identical) | |
| attn_span = max(attns) - min(attns) or 0.001 | |
| density_span = max(densities) - min(densities) or 0.1 | |
| x_range = [min(attns) - attn_span * 0.1, max(attns) + attn_span * 0.1] | |
| y_range = [min(densities) - density_span * 0.1, max(densities) + density_span * 0.1] | |
| fig.add_hline(y=median_density, line_dash="dash", line_color="#9CA3AF", opacity=0.5) | |
| fig.add_vline(x=median_attn, line_dash="dash", line_color="#9CA3AF", opacity=0.5) | |
| # Quadrant labels | |
| fig.add_annotation(x=x_range[1], y=y_range[0], text="๐ข Opportunity", | |
| showarrow=False, font=dict(size=11, color=QUAD_COLORS["high_attn_low_density"])) | |
| fig.add_annotation(x=x_range[1], y=y_range[1], text="๐ต Competitive", | |
| showarrow=False, font=dict(size=11, color=QUAD_COLORS["high_attn_high_density"])) | |
| fig.add_annotation(x=x_range[0], y=y_range[0], text="โช Niche", | |
| showarrow=False, font=dict(size=11, color=QUAD_COLORS["low_attn_low_density"])) | |
| fig.add_annotation(x=x_range[0], y=y_range[1], text="๐ด Crowded", | |
| showarrow=False, font=dict(size=11, color=QUAD_COLORS["low_attn_high_density"])) | |
| fig.update_layout( | |
| title="Attention vs Citation Density (4-Quadrant)", | |
| xaxis_title="Attention Score", | |
| yaxis_title="Citation Density", | |
| height=600, | |
| template="plotly_white", | |
| hoverlabel=dict(bgcolor="white", font_size=12), | |
| ) | |
| st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False}) | |
| # Quadrant summary with action guides | |
| st.markdown("#### Quadrant ์์ฝ") | |
| q1, q2, q3, q4 = st.columns(4) | |
| with q1: | |
| st.metric( | |
| "๐ข Opportunity", f"{quadrant_counts['opportunity']}๊ฐ", | |
| help="AI ๊ด์ฌ๋ ๋์ + ๊ฒฝ์ ๋ฎ์ โ ์ฝํ ์ธ ์ ์ ๊ธฐํ. " | |
| "์ด ํ ํฝ์ ๋ํ ์ ๋ฌธ ์ฝํ ์ธ ๋ฅผ ์ ์ํ๋ฉด AI ๋ต๋ณ์ ์ธ์ฉ๋ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.", | |
| ) | |
| with q2: | |
| st.metric( | |
| "๐ต Competitive", f"{quadrant_counts['competitive']}๊ฐ", | |
| help="AI ๊ด์ฌ๋ ๋์ + ๊ฒฝ์ ๋์ โ ๊ฒฝ์ ์น์ด. " | |
| "์ฐจ๋ณํ๋ ์ฝํ ์ธ ๋ ์ ๋ฌธ์ฑ์ด ํ์ํฉ๋๋ค.", | |
| ) | |
| with q3: | |
| st.metric( | |
| "โช Niche", f"{quadrant_counts['niche']}๊ฐ", | |
| help="AI ๊ด์ฌ๋ ๋ฎ์ + ๊ฒฝ์ ๋ฎ์ โ ํ์ ์์ญ. " | |
| "์์ฅ์ด ์ฑ์ฅํ๋ฉด ์ ์ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.", | |
| ) | |
| with q4: | |
| st.metric( | |
| "๐ด Crowded", f"{quadrant_counts['crowded']}๊ฐ", | |
| help="AI ๊ด์ฌ๋ ๋ฎ์ + ๊ฒฝ์ ๋์ โ ํฌํ ์์ญ. " | |
| "์๋ก์ด ์ง์ถ๋ณด๋ค ๊ธฐ์กด ์ฝํ ์ธ ์ ์ง์ ์ง์คํ์ธ์.", | |
| ) | |
| st.caption(f"Median Attention: {median_attn:.4f} | Median Density: {median_density:.4f}") | |