2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

KCCS デジタルプラットフォーム部の林です。

弊社では定期的に社内で勉強会やハンズオン学習を実施しています。
実施後にアンケートを回答してもらい集計を行うのですが、Genieを使えばもっと簡単に集計や分析ができるのではと考えました。

ついでにDatabricks上でアンケートの回答システムも作成してしまえばデータの収集もできて一石二鳥ではないかと思い、今回はDatabricks上でアンケートシステムの構築からGenieでの分析まで実施してみました。

作成ポイント

今回作成にあたりポイントとした点は以下の通りです。

  • アンケート自体はアプリのコードを変更することなく、設定ファイルの定義だけで作成できるようにする
  • 事前にテーブルを作成しておかなくてもいいように、アンケート作成に合わせて自動で作成されるようにする

本記事では、Databricks Appsを活用し、JSON設定ファイルから動的にStreamlitフォームを生成し、Delta Tableに直接保存するアンケートシステムを構築します。

システム概要

アーキテクチャ

アンケート設定ファイル → ConfigLoader → FormGenerator
                                         ↓
       Delta Table ← DataManager ← Streamlit UI

アンケート設定ファイル:JSON形式でアンケートの内容を定義する
ConfigLoader:JSONファイルの設定読み込み
FormGenerator:JSON設定情報からStreamlitコンポーネントを動的生成
Streamlit UI:Webでのアンケートページ表示
DataManager:動的テーブル作成、SQLウェアハウス接続
Delta Table:データ保存

アプリの仕様を全て記載すると長くなってしまうため、ポイントに絞って設定内容を解説したいと思います。

1. アンケート設定ファイル

JSON形式でアンケートの内容を定義:

{
  "survey_id": "customer_satisfaction",
  "title": "顧客満足度調査",
  "survey_date": "20241112",
  "questions": [
    {
      "id": "overall_satisfaction",
      "type": "radio",
      "label": "総合的な満足度",
      "required": true,
      "options": ["非常に満足", "満足", "普通", "不満", "非常に不満"]
    },
    {
      "id": "service_rating",
      "type": "slider",
      "label": "サービス評価(1-10点)",
      "required": true,
      "min_value": 1,
      "max_value": 10,
      "default_value": 5
    }
  ]
}

アンケートの内容ではラジオボックスやスライダーなど9種類の質問タイプを利用することができます。
一覧は以下の通りです。

質問タイプ 用途例 Streamlitコンポーネント
text 名前、メールアドレス st.text_input()
text_area コメント、意見 st.text_area()
number 年齢、金額 st.number_input()
date 生年月日、予定日 st.date_input()
selectbox 所属部門、職種 st.selectbox()
radio 性別、満足度 st.radio()
checkbox 同意確認、希望有無 st.checkbox()
multiselect 興味分野、改善希望項目 st.multiselect()
slider 評価点数、重要度 st.slider()

2. ConfigLoader

JSON設定の読み込み・検証を行う:

def validate_survey_config(self, config: Dict[str, Any]) -> List[str]:
    """アンケート設定をチェック"""
    errors = []
    
    # 必須フィールドチェック
    required_fields = ['survey_id', 'title', 'survey_date', 'questions']
    for field in required_fields:
        if field not in config:
            errors.append(f"必須フィールド '{field}' が見つかりません")
    
    return errors

3. FormGenerator:

JSON設定から動的にStreamlitコンポーネントを生成する:

def _render_question(self, question: Dict[str, Any]) -> Any:
    """個別の質問を描画"""
    question_type = question['type']
    question_id = question['id']
    label = self._format_label(question)
    help_text = question.get('help_text', None)
        
    # 質問タイプに応じて適切なコンポーネントを選択
    if question_type == 'text':
        return self._render_text_input(question_id, label, question, help_text)
    # 他の質問タイプも同様に実装

動的フォーム生成プロセス:

def render_survey_form(self, survey_config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    """アンケートフォームを描画し、回答データを返す"""
    
    st.title(survey_config['title'])
    if 'description' in survey_config:
        st.markdown(survey_config['description'])
    
    with st.form(key=f"survey_form_{survey_config['survey_id']}"):
        form_data = {}
        
        # 各質問を動的に描画
        for question in survey_config['questions']:
            value = self._render_question(question)
            if value is not None:
                form_data[question['id']] = value
        
        submitted = st.form_submit_button("回答を送信")
        
        if submitted:
            validation_errors = self._validate_form_data(survey_config['questions'], form_data)
            if validation_errors:
                for error in validation_errors:
                    st.error(error)
                return None
            return form_data

4. DataManager

Delta Tableへの操作を担当する:

class DataManager:
    """Delta TableをSQL実行で操作するデータ管理クラス"""
    
    def __init__(self, catalog: str = None, schema: str = None):
        self.catalog = catalog or os.getenv('DEFAULT_CATALOG', 'main')
        self.schema = schema or os.getenv('DEFAULT_SCHEMA', 'survey_data')
        self.warehouse_id = os.getenv('DATABRICKS_WAREHOUSE_ID')
        self._connection = None
        self._ensure_schema_exists()
    
    def _get_connection(self):
        """Databricks SQL接続を取得"""
        if self._connection is None:
            try:
                # Databricks Apps環境では環境変数から自動取得
                self._connection = sql.connect(
                    server_hostname=os.getenv('DATABRICKS_HOST', '').replace('https://', ''),
                    http_path=f"/sql/1.0/warehouses/{self.warehouse_id}",
                    access_token=os.getenv('DATABRICKS_TOKEN')
                )
            except Exception as e:
                st.error(f"Databricks SQL接続に失敗しました: {e}")
                raise
        return self._connection

アンケート設定に基づいて自動的にDelta Tableを作成する。テーブル名はJSON設定から取得した{survey_id}_{survey_date}形式で命名:

def create_table_if_not_exists(self, table_name: str, questions: List[Dict[str, Any]]) -> bool:
    """テーブルが存在しない場合作成"""
    full_table_name = f"`{self.catalog}`.`{self.schema}`.`{table_name}`"
        
    try:
        # テーブル存在チェック
        if self._table_exists(full_table_name):
            return True
            
        # CREATE TABLE SQL文を生成
        columns_sql = []
            
        # システム列を追加
        columns_sql.extend([
            "`response_id` STRING NOT NULL",
            "`submitted_at` TIMESTAMP NOT NULL",
            "`survey_version` STRING"
        ])
            
        # 質問からカラムを生成
        for question in questions:
            field_name = question['id']
            sql_type = self._get_sql_data_type(question)
            nullable = "NOT NULL" if question.get('required', False) else ""
            columns_sql.append(f"`{field_name}` {sql_type} {nullable}".strip())
            
        create_table_sql = f"""
        CREATE TABLE {full_table_name} (
            {', '.join(columns_sql)}
        ) USING DELTA
        TBLPROPERTIES (
            'delta.autoOptimize.optimizeWrite' = 'true',
            'delta.autoOptimize.autoCompact' = 'true'
        )
        """
            
        cursor = self._execute_sql(create_table_sql)
        cursor.close()
            
        st.success(f"テーブル {full_table_name} を作成しました")
        return True
            
    except Exception as e:
        st.error(f"テーブル作成に失敗しました: {e}")
        return False

質問タイプからSQL型を自動判定:

def _get_sql_data_type(self, question: Dict[str, Any]) -> str:
    """質問タイプからSQLデータ型を決定"""
    question_type = question.get('type', 'text')
        
    type_mapping = {
        'text': 'STRING',
        'text_area': 'STRING',
        'number': 'BIGINT' if question.get('min_value', 0) >= 0 else 'DOUBLE',
        'date': 'STRING',  # 日付は文字列として保存
        'selectbox': 'STRING',
        'radio': 'STRING',
        'checkbox': 'BOOLEAN',
        'multiselect': 'STRING',  # カンマ区切り文字列として保存
        'slider': 'BIGINT' if isinstance(question.get('min_value'), int) else 'DOUBLE'
    }
        
    return type_mapping.get(question_type, 'STRING')

実用例:Databricksハンズオン研修アンケート

テストケースとして、Databricksハンズオン研修の受講者アンケートを作成してみます。

プロジェクト構成

databricks-survey-app/
├── app.yml                    # Databricks Apps設定
├── app.py                     # メインアプリケーション
├── requirements.txt           # Python依存関係
├── config/
│   ├── app_config.json       # アプリケーション設定
│   └── surveys/              # アンケート設定ディレクトリ
│       ├── databricks_handson_training.json
│       └── template_survey.json  # アンケートテンプレート(非表示)
├── src/
│   ├── data_manager.py       # Delta Table操作
│   └── form_generator.py     # 動的フォーム生成
├── utils/
│   └── config_loader.py      # 設定ファイル読み込み
└── docs/
    └── setup.md              # セットアップガイド

1.アンケート設計

研修評価アンケート項目:

  • 基本情報: 参加者名、所属部署、Databricks / Python経験レベル
  • 研修評価: 研修の理解度、難易度、講師評価(スライダー使用)
  • 内容分析: 有用だった・興味のあるコンテンツ(複数選択)、改善提案
  • 今後の展望: 今後の学習希望分野
  • 満足度指標: 全体満足度、他のメンバーへの推奨度

設定例(抜粋)

{
  "survey_id": "databricks_handson_training",
  "title": "Databricksハンズオン研修 受講者アンケート",
  "survey_date": "20241211",
  "questions": [
    {
      "id": "training_content_understanding",
      "type": "slider",
      "label": "研修内容の理解度(1-10)",
      "required": true,
      "min_value": 1,
      "max_value": 10,
      "default_value": 5,
      "help_text": "研修内容をどの程度理解できましたか?"
    },
    {
      "id": "most_valuable_content",
      "type": "multiselect",
      "label": "最も有用だった研修内容",
      "required": false,
      "options": [
        "Databricks基本概念・アーキテクチャ",
        "ノートブックの使い方",
        "Spark DataFrame操作",
        "Delta Lake概要・操作",
        "MLflow機械学習パイプライン"
      ]
    }
  ]
}

生成されるテーブル

CREATE TABLE main.survey_data.databricks_handson_training_20241211 (
  response_id STRING NOT NULL,
  submitted_at TIMESTAMP NOT NULL,
  survey_version STRING,
  participant_name STRING NOT NULL,
  department STRING NOT NULL,
  experience_level STRING NOT NULL,
  training_content_understanding BIGINT NOT NULL,
  most_valuable_content STRING,
  overall_satisfaction BIGINT NOT NULL,
  recommendation_score BIGINT NOT NULL
) USING DELTA

2. Databricks Apps設定

# app.yml
command: ["streamlit", "run", "app.py"]

env:
  - name: STREAMLIT_BROWSER_GATHER_USAGE_STATS
    value: "false"
  - name: STREAMLIT_SERVER_HEADLESS
    value: "true"
  - name: "SURVEY_CONFIG_PATH"
    value: "./config/surveys"
  - name: "APP_CONFIG_PATH"
    value: "./config/app_config.json"
  - name: DEFAULT_CATALOG
    value: "your_catalog_name"
  - name: DEFAULT_SCHEMA
    value: "your_schema_name"
  - name: DATABRICKS_WAREHOUSE_ID
    value: "your-warehouse-id"
  - name: DATABRICKS_TOKEN
    value: "your-access-token"
  - name: DATABRICKS_HOST
    value: "https://your-workspace.cloud.databricks.com/"

3. 必要な権限設定

  • Unity Catalogへのアクセス権限
  • スキーマ作成・テーブル作成権限
  • SQL Warehouseの使用権限

4. アンケート画面

スクリーンショット 2025-12-11 11.48.33.png

アンケート分析

アンケートの集計ができたらGenieを使って分析を行います。
作成されたテーブルをデータソースとして設定することで、簡単にアンケートの集計を行うことができます。

スクリーンショット 2025-11-19 9.12.58.png

また複数回実施したデータを登録すれば、回を跨いだ分析も簡単に行うことができます。

まとめ

Databricks AppsとGenieを活用することで簡単にアンケートを集計するシステムが構築できました。
一方、Appsで作成したアプリケーションへの接続にはDatabricksアカウントが必要となるため、アンケートの取得対象者によっては利用するハードルが高いといった課題もあります。
また、アンケートの作成は設定ファイルから簡単にできるものの、JSONファイルでの記載となっているのでこの点もハードルが高い部分であり、AIによる自動作成やGUIから設定可能にするなど、JSONがわからない人でも簡単に利用できるような改善は必要だと感じています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?