Trung cấpHướng dẫnClaude APINguồn: Anthropic

BI Dashboard với Streamlit và Claude — Từ data đến visualization

Nghe bài viết
00:00

Điểm nổi bật

Nhấn để đến mục tương ứng

  1. 1 Kết quả là chỉ 20-30% nhân viên trong tổ chức thực sự sử dụng BI tools.
  2. 2 Cung cấp sample data trong system prompt — Claude sinh SQL chính xác hơn nhiều khi thấy dữ liệu mẫu.
  3. 3 Claude thay đổi điều này — khi tích hợp vào Streamlit dashboard, người dùng có thể hỏi bằng tiếng Việt tự nhiên như "Doanh thu tháng này so với tháng trước tăng hay giảm?" và nhận ngay biểu đồ cùng phân tích.
  4. 4 Khi được hỏi về dữ liệu, tạo SQL query PostgreSQL chính xác 2.
  5. 5 ...""" response = analyst.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=2000, messages=[{"role": "user", "content": report_prompt}] ) return response.content[0].text Security và best practices Dashboard BI truy cập dữ liệu nhạy cảm nên security là ưu tiên hàng đầu.
a robotic arm is connected to a computer mouse

BI dashboard truyền thống yêu cầu người dùng biết SQL, biết chọn đúng chart type, và biết diễn giải dữ liệu. Kết quả là chỉ 20-30% nhân viên trong tổ chức thực sự sử dụng BI tools. Claude thay đổi điều này — khi tích hợp vào Streamlit dashboard, người dùng có thể hỏi bằng tiếng Việt tự nhiên như "Doanh thu tháng này so với tháng trước tăng hay giảm?" và nhận ngay biểu đồ cùng phân tích. Bài viết này hướng dẫn bạn xây dựng BI dashboard thông minh từ đầu.

Tại sao chọn Streamlit + Claude?

Streamlit là framework Python để tạo data apps cực nhanh — viết 50 dòng code đã có dashboard hoạt động. Claude API thêm khả năng hiểu ngôn ngữ tự nhiên và tự động phân tích dữ liệu. Sự kết hợp này có 4 lợi thế. Thứ nhất, development speed — prototype dashboard trong vài giờ thay vì vài tuần. Thứ hai, natural language interface — mọi người đều dùng được, không cần biết SQL. Thứ ba, auto-insights — Claude tự phát hiện patterns và anomalies trong data. Thứ tư, Python ecosystem — tận dụng pandas, plotly, matplotlib cho visualization mạnh mẽ.

Kiến trúc dashboard

Dashboard gồm 4 layers. Layer 1 là Data layer — kết nối database (PostgreSQL, MySQL), CSV, hoặc API. Layer 2 là Processing layer — pandas xử lý data, Claude sinh SQL queries và phân tích. Layer 3 là Visualization layer — Plotly/Altair tạo interactive charts. Layer 4 là Interface layer — Streamlit UI cho end users.

# Cấu trúc dự án
#
# bi-dashboard/
# ├── app.py              # Main Streamlit app
# ├── data_connector.py   # Database connections
# ├── claude_analyst.py   # Claude API integration
# ├── visualizer.py       # Chart generation
# ├── config.py           # Configuration
# ├── requirements.txt    # Dependencies
# └── .env                # API keys (KHÔNG commit)
#
# requirements.txt:
# streamlit==1.31.0
# anthropic==0.18.0
# pandas==2.2.0
# plotly==5.18.0
# sqlalchemy==2.0.25
# python-dotenv==1.0.0

Bước 1: Data Connector

Đầu tiên, tạo module kết nối data source. Module này abstract hóa việc truy cập data để phần còn lại không cần biết data đến từ đâu.

# data_connector.py
import pandas as pd
from sqlalchemy import create_engine, text
import streamlit as st

class DataConnector:
    def __init__(self, connection_string):
        self.engine = create_engine(connection_string)

    @st.cache_data(ttl=300)  # Cache 5 phút
    def query(_self, sql_query):
        """Execute SQL query and return DataFrame"""
        with _self.engine.connect() as conn:
            return pd.read_sql(text(sql_query), conn)

    def get_schema(_self):
        """Get database schema for Claude context"""
        schema_query = """
        SELECT table_name, column_name, data_type
        FROM information_schema.columns
        WHERE table_schema = 'public'
        ORDER BY table_name, ordinal_position
        """
        df = _self.query(schema_query)
        schema_text = ""
        for table in df['table_name'].unique():
            cols = df[df['table_name'] == table]
            schema_text += f"
Table: {table}
"
            for _, col in cols.iterrows():
                schema_text += f"  - {col['column_name']} ({col['data_type']})
"
        return schema_text

    def get_sample_data(_self, table_name, limit=5):
        """Get sample rows for Claude context"""
        df = _self.query(f"SELECT * FROM {table_name} LIMIT {limit}")
        return df.to_string()

Bước 2: Claude Analyst Module

Module này là trung tâm — nhận câu hỏi tiếng Việt từ người dùng, sinh SQL query, thực thi, và tạo phân tích. Claude đóng vai trò data analyst có kiến thức về database schema.

# claude_analyst.py
import anthropic
import json
import re

class ClaudeAnalyst:
    def __init__(self, db_schema, sample_data=None):
        self.client = anthropic.Anthropic()
        self.db_schema = db_schema
        self.sample_data = sample_data or ""
        self.system_prompt = f"""Bạn là data analyst chuyên nghiệp.
Database schema:
{self.db_schema}

Sample data:
{self.sample_data}

Quy tắc:
1. Khi được hỏi về dữ liệu, tạo SQL query PostgreSQL chính xác
2. Giải thích kết quả bằng tiếng Việt dễ hiểu
3. Đề xuất chart type phù hợp nhất
4. Phát hiện trends, anomalies, và insights
5. Nếu câu hỏi không rõ, hỏi lại trước khi query
6. KHÔNG tạo destructive queries (DELETE, DROP, UPDATE)
7. Luôn dùng LIMIT để tránh query quá lớn"""

    def generate_sql(self, question):
        """Generate SQL from natural language question"""
        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1000,
            system=self.system_prompt,
            messages=[{
                "role": "user",
                "content": f"""Câu hỏi: {question}

Trả về JSON format:
{{
  "sql": "SELECT ...",
  "explanation": "Query này sẽ...",
  "chart_type": "bar|line|pie|scatter|table|metric",
  "chart_config": {{
    "x": "column_name",
    "y": "column_name",
    "title": "Tiêu đề biểu đồ"
  }}
}}"""
            }]
        )
        text = response.content[0].text
        # Extract JSON from response
        json_match = re.search(r'{[sS]*}', text)
        if json_match:
            return json.loads(json_match.group())
        return None

    def analyze_results(self, question, data_summary, chart_description):
        """Analyze query results and provide insights"""
        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1500,
            system=self.system_prompt,
            messages=[{
                "role": "user",
                "content": f"""Câu hỏi ban đầu: {question}

Dữ liệu trả về:
{data_summary}

Biểu đồ: {chart_description}

Hãy phân tích:
1. Trả lời câu hỏi ngắn gọn (2-3 câu)
2. Insights chính: 3-5 bullet points
3. Anomalies hoặc điểm bất thường (nếu có)
4. Recommendations: 2-3 hành động đề xuất
5. Câu hỏi follow-up gợi ý (2-3 câu)

Trả lời bằng tiếng Việt, ngắn gọn, actionable."""
            }]
        )
        return response.content[0].text

Bước 3: Visualization Module

Module tạo biểu đồ tự động dựa trên chart type mà Claude đề xuất. Dùng Plotly cho interactive charts.

# visualizer.py
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st

class Visualizer:
    @staticmethod
    def create_chart(df, chart_type, config):
        """Create Plotly chart based on type and config"""
        title = config.get('title', 'Biểu đồ')
        x = config.get('x')
        y = config.get('y')
        color = config.get('color')

        if chart_type == 'bar':
            fig = px.bar(df, x=x, y=y, color=color, title=title)
        elif chart_type == 'line':
            fig = px.line(df, x=x, y=y, color=color, title=title,
                          markers=True)
        elif chart_type == 'pie':
            fig = px.pie(df, names=x, values=y, title=title)
        elif chart_type == 'scatter':
            fig = px.scatter(df, x=x, y=y, color=color, title=title)
        elif chart_type == 'metric':
            # Single number display
            value = df.iloc[0, 0] if len(df) > 0 else 0
            st.metric(label=title, value=f"{value:,.0f}")
            return None
        elif chart_type == 'table':
            st.dataframe(df, use_container_width=True)
            return None
        else:
            fig = px.bar(df, x=x, y=y, title=title)

        fig.update_layout(
            template='plotly_white',
            font=dict(family='Inter, sans-serif'),
            height=450
        )
        return fig

    @staticmethod
    def create_kpi_cards(metrics_dict):
        """Create KPI metric cards"""
        cols = st.columns(len(metrics_dict))
        for col, (label, value, delta) in zip(cols, metrics_dict.items()):
            col.metric(label=label, value=value, delta=delta)

Bước 4: Main Streamlit App

Ghép tất cả lại thành ứng dụng hoàn chỉnh với giao diện thân thiện.

# app.py
import streamlit as st
from data_connector import DataConnector
from claude_analyst import ClaudeAnalyst
from visualizer import Visualizer
from dotenv import load_dotenv
import os

load_dotenv()

st.set_page_config(
    page_title="BI Dashboard - Claude AI",
    page_icon="📊",
    layout="wide"
)

st.title("BI Dashboard")
st.caption("Hỏi bất kỳ câu hỏi nào về dữ liệu bằng tiếng Việt")

# Initialize
@st.cache_resource
def init_system():
    db = DataConnector(os.getenv('DATABASE_URL'))
    schema = db.get_schema()
    analyst = ClaudeAnalyst(schema)
    return db, analyst

db, analyst = init_system()
viz = Visualizer()

# Sidebar: Predefined questions
with st.sidebar:
    st.subheader("Hoi nhanh")
    quick_questions = [
        "Doanh thu 7 ngay gan nhat?",
        "Top 10 san pham ban chay nhat thang nay?",
        "So sanh doanh thu thang nay va thang truoc",
        "Ty le huy don theo ly do",
        "Khach hang moi vs khach cu theo tuan"
    ]
    selected_q = st.selectbox("Chon cau hoi:", [""] + quick_questions)

# Main chat interface
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display chat history
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
        if "chart" in msg:
            st.plotly_chart(msg["chart"], use_container_width=True)

# Input
question = st.chat_input("Hoi ve du lieu...") or selected_q
if question:
    # Show user message
    st.session_state.messages.append({"role": "user", "content": question})
    with st.chat_message("user"):
        st.markdown(question)

    # Process with Claude
    with st.chat_message("assistant"):
        with st.spinner("Dang phan tich..."):
            # Step 1: Generate SQL
            result = analyst.generate_sql(question)

            if result and result.get('sql'):
                # Show SQL (collapsible)
                with st.expander("Xem SQL query"):
                    st.code(result['sql'], language='sql')

                # Step 2: Execute query
                try:
                    df = db.query(result['sql'])

                    if len(df) > 0:
                        # Step 3: Create chart
                        chart = viz.create_chart(
                            df,
                            result.get('chart_type', 'table'),
                            result.get('chart_config', {})
                        )
                        if chart:
                            st.plotly_chart(chart, use_container_width=True)

                        # Step 4: AI Analysis
                        analysis = analyst.analyze_results(
                            question,
                            df.describe().to_string(),
                            result.get('chart_config', {}).get('title', '')
                        )
                        st.markdown(analysis)

                        # Save to history
                        msg_data = {"role": "assistant", "content": analysis}
                        if chart:
                            msg_data["chart"] = chart
                        st.session_state.messages.append(msg_data)
                    else:
                        st.info("Khong co du lieu phu hop voi cau hoi.")
                except Exception as e:
                    st.error(f"Loi khi thuc thi query: {str(e)}")
            else:
                st.warning("Khong the tao query tu cau hoi. Vui long thu lai.")

Tính năng nâng cao: Auto-report

Ngoài hỏi-đáp interactive, dashboard có thể tự tạo báo cáo định kỳ. Claude phân tích data tổng thể và tạo executive summary.

def generate_auto_report(db, analyst):
    """Generate weekly executive report"""
    # Collect key metrics
    queries = {
        "revenue": "SELECT SUM(amount) as total FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'",
        "orders": "SELECT COUNT(*) as total FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'",
        "new_customers": "SELECT COUNT(DISTINCT customer_id) FROM customers WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'",
        "top_products": "SELECT product_name, SUM(quantity) as qty FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.created_at >= CURRENT_DATE - INTERVAL '7 days' GROUP BY product_name ORDER BY qty DESC LIMIT 5",
        "revenue_by_day": "SELECT DATE(created_at) as date, SUM(amount) as revenue FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '7 days' GROUP BY date ORDER BY date"
    }

    data = {}
    for name, sql in queries.items():
        data[name] = db.query(sql).to_string()

    report_prompt = f"""Tao bao cao tuan cho CEO (tieng Viet).

Du lieu:
{json.dumps(data, indent=2)}

Format:
## Bao cao tuan [ngay]

### Tom tat
- Doanh thu: X (tang/giam Y% so voi tuan truoc)
- Don hang: X
- Khach moi: X

### Diem noi bat
1. ...
2. ...
3. ...

### Canh bao
- ...

### De xuat hanh dong
1. ...
2. ..."""

    response = analyst.client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2000,
        messages=[{"role": "user", "content": report_prompt}]
    )
    return response.content[0].text

Security và best practices

Dashboard BI truy cập dữ liệu nhạy cảm nên security là ưu tiên hàng đầu. Có 5 nguyên tắc bảo mật cần tuân thủ.

SQL injection prevention: Dù Claude sinh SQL, bạn vẫn cần validate. Chỉ cho phép SELECT statements, block DROP/DELETE/UPDATE/INSERT. Dùng read-only database user cho dashboard.

Data access control: Giới hạn tables mà Claude có thể query. Không expose tables chứa PII (password, CCCD) trong schema context.

API key security: Lưu API key trong environment variables hoặc secret manager, không hardcode trong code. Rotate key định kỳ.

Rate limiting: Giới hạn số queries mỗi user mỗi phút để tránh abuse và kiểm soát chi phí API.

Audit logging: Log mọi query được sinh và thực thi, bao gồm user, timestamp, query, và kết quả. Cần thiết cho compliance và debugging.

# SQL validation
import re

def validate_sql(sql):
    """Validate generated SQL is read-only"""
    sql_upper = sql.upper().strip()

    # Block destructive operations
    dangerous = ['DROP', 'DELETE', 'UPDATE', 'INSERT', 'ALTER',
                 'TRUNCATE', 'GRANT', 'REVOKE', 'CREATE']
    for keyword in dangerous:
        if re.search(rf'{keyword}', sql_upper):
            raise ValueError(f"SQL chua tu khoa bi cam: {keyword}")

    # Must start with SELECT or WITH
    if not (sql_upper.startswith('SELECT') or sql_upper.startswith('WITH')):
        raise ValueError("Chi cho phep SELECT queries")

    # Limit rows
    if 'LIMIT' not in sql_upper:
        sql = sql.rstrip(';') + ' LIMIT 1000;'

    return sql

Deploy và chia sẻ dashboard

Streamlit app có thể deploy lên nhiều nền tảng. Cách đơn giản nhất là dùng Streamlit Community Cloud (miễn phí cho public repos) hoặc deploy trên server riêng bằng Docker.

# Dockerfile
FROM python:3.11-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health

ENTRYPOINT ["streamlit", "run", "app.py",
  "--server.port=8501",
  "--server.address=0.0.0.0",
  "--server.headless=true"]

# docker-compose.yml
# version: '3.8'
# services:
#   dashboard:
#     build: .
#     ports:
#       - "8501:8501"
#     environment:
#       - DATABASE_URL=${DATABASE_URL}
#       - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
#     restart: unless-stopped

Chi phí vận hành

Chi phí dashboard BI với Claude gồm 3 khoản chính. Claude API: trung bình mỗi query tốn 1000-2000 tokens, khoảng $0.01-0.03 mỗi query. Với 100 queries/ngày (team 10 người, mỗi người 10 queries), chi phí khoảng $1-3/ngày hay $30-90/tháng. Server hosting: VPS nhỏ đủ chạy Streamlit, khoảng $5-20/tháng. Database: phụ thuộc vào hạ tầng hiện có, thường $0 nếu dùng DB sẵn có. Tổng chi phí: khoảng $50-120/tháng cho team 10 người — rẻ hơn nhiều so với các BI tools thương mại như Tableau ($70/user/tháng) hay Power BI ($10-20/user/tháng kèm hạ tầng).

Tính năng nâng cao: Natural Language Alerts

Ngoài hỏi-đáp, dashboard có thể chủ động thông báo khi phát hiện bất thường trong dữ liệu. Claude phân tích data định kỳ và gửi alert bằng ngôn ngữ tự nhiên thay vì chỉ gửi số liệu khô khan.

# alert_system.py
import schedule

def check_anomalies(db, analyst):
    """Check for data anomalies every hour"""
    # Get current metrics vs. historical average
    current = db.query("""
      SELECT
        SUM(amount) as revenue_today,
        COUNT(*) as orders_today,
        AVG(amount) as avg_order_value
      FROM orders
      WHERE created_at >= CURRENT_DATE
    """)

    historical = db.query("""
      SELECT
        AVG(daily_revenue) as avg_revenue,
        STDDEV(daily_revenue) as stddev_revenue
      FROM (
        SELECT DATE(created_at) as date, SUM(amount) as daily_revenue
        FROM orders
        WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
        GROUP BY date
      ) daily
    """)

    # Claude analyzes if current metrics are abnormal
    analysis = analyst.client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": f"""Hom nay den bay gio:
Revenue: {current.iloc[0]['revenue_today']}
Orders: {current.iloc[0]['orders_today']}
AOV: {current.iloc[0]['avg_order_value']}

30-day average:
Avg daily revenue: {historical.iloc[0]['avg_revenue']}
Std dev: {historical.iloc[0]['stddev_revenue']}

Co bat thuong khong? Chi bao cao neu co van de THUC SU,
khong bao cao neu moi thu binh thuong.
Neu bat thuong: Mo ta van de + nguyen nhan co the + hanh dong de xuat."""
        }]
    )

    result = analysis.content[0].text
    if "binh thuong" not in result.lower():
        send_slack_alert(result)
        send_email_alert(result)

# Run every hour during business hours
schedule.every().hour.at(":00").do(check_anomalies, db, analyst)

Multi-user và permission control

Trong tổ chức thực tế, không phải ai cũng nên xem được mọi dữ liệu. Streamlit hỗ trợ authentication cơ bản, kết hợp với Claude có thể tạo permission-aware queries.

Cách triển khai đơn giản: dùng streamlit-authenticator cho login, mỗi user có role (admin, manager, analyst), role quyết định tables nào được truy cập, và Claude nhận thêm context về user role trong system prompt để chỉ sinh queries phù hợp quyền hạn. Ví dụ, analyst chỉ xem được dữ liệu department mình, manager xem được toàn bộ nhưng không xem PII, admin xem được tất cả.

Mẹo xây dựng BI dashboard hiệu quả

  • Bắt đầu với 5-10 câu hỏi quan trọng nhất của business — đừng cố build dashboard "hỏi gì cũng trả lời được" ngay từ đầu.
  • Cache kết quả query phổ biến — giảm latency và chi phí API khi nhiều người hỏi cùng câu.
  • Cung cấp sample data trong system prompt — Claude sinh SQL chính xác hơn nhiều khi thấy dữ liệu mẫu.
  • Thêm predefined quick questions — giúp người dùng mới biết dashboard làm được gì.
  • Validate SQL trước khi execute — không bao giờ chạy raw SQL từ LLM mà không validate.
  • Monitor chi phí API hàng ngày — set alert khi vượt budget để tránh bill surprise.

Bước tiếp theo

Bạn đã nắm được cách xây dựng BI dashboard thông minh với Streamlit và Claude API. Dashboard cho phép mọi người trong team hỏi data bằng tiếng Việt tự nhiên và nhận insights tự động — democratize data access cho toàn tổ chức. Khám phá thêm tại Thư viện Ứng dụng Claude.

Tính năng liên quan:Streamlit DashboardData VisualizationNatural Language QueryAuto Insights

Bai viet co huu ich khong?

Bản quyền thuộc về tác giả. Vui lòng dẫn nguồn khi chia sẻ.

Bình luận (0)
Ảnh đại diện
Đăng nhập để bình luận...
Đăng nhập để bình luận
  • Đang tải bình luận...

Đăng ký nhận bản tin

Nhận bài viết hay nhất về sản phẩm và vận hành, gửi thẳng vào hộp thư của bạn.

Bảo mật thông tin. Hủy đăng ký bất cứ lúc nào. Chính sách bảo mật.