Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import time | |
| import traceback | |
| import os | |
| import requests | |
| # 完全版のimportエラー対策(段階的フォールバック) | |
| LANGCHAIN_AVAILABLE = False | |
| FULL_VERSION = False | |
| try: | |
| from langchain_groq import ChatGroq | |
| from langchain_openai import ChatOpenAI | |
| LANGCHAIN_AVAILABLE = True | |
| print("✅ LangChain基本ライブラリが利用可能です") | |
| except ImportError as e: | |
| print(f"⚠️ LangChain基本ライブラリが利用できません: {e}") | |
| try: | |
| from OpenAITools.FetchTools import fetch_clinical_trials | |
| from OpenAITools.CrinicalTrialTools import SimpleClinicalTrialAgent, GraderAgent, LLMTranslator, generate_ex_question_English | |
| if LANGCHAIN_AVAILABLE: | |
| FULL_VERSION = True | |
| print("✅ 完全版モジュールが正常にロードされました") | |
| except ImportError as e: | |
| print(f"⚠️ 完全版モジュールのインポートに失敗: {e}") | |
| print("軽量版モードで動作します") | |
| # プログレスバーの安全な更新関数 | |
| def safe_progress_update(progress, value, desc): | |
| """プログレスバーを安全に更新する関数(テキスト表示のみ使用)""" | |
| # Gradioプログレスバーは使用せず、テキスト表示のみ | |
| progress_text = f"📊 進行状況 ({int(value*100)}%): {desc}" | |
| print(progress_text) | |
| return progress_text | |
| # 環境変数チェック | |
| def check_environment(): | |
| """環境変数をチェックし、不足している場合は警告""" | |
| missing_vars = [] | |
| # GROQ_API_KEYは必須 | |
| groq_key_available = bool(os.getenv("GROQ_API_KEY")) | |
| if not groq_key_available: | |
| missing_vars.append("GROQ_API_KEY") | |
| # OPENAI_API_KEYはオプション | |
| openai_key_available = bool(os.getenv("OPENAI_API_KEY")) | |
| if not openai_key_available: | |
| print("ℹ️ OPENAI_API_KEY が設定されていません(オプション機能)") | |
| if missing_vars: | |
| print(f"⚠️ 必須環境変数が設定されていません: {', '.join(missing_vars)}") | |
| print("AI評価機能が制限される可能性があります。") | |
| # GROQ_API_KEYがあれば完全版として動作可能 | |
| return groq_key_available | |
| # 環境変数チェック実行 | |
| env_ok = check_environment() | |
| # モデルとエージェントの安全な初期化 | |
| def safe_init_agents(): | |
| """エージェントを安全に初期化""" | |
| if not FULL_VERSION: | |
| return None, None, None | |
| try: | |
| groq = ChatGroq(model_name="llama3-70b-8192", temperature=0) | |
| translator = LLMTranslator(groq) | |
| criteria_agent = SimpleClinicalTrialAgent(groq) | |
| grader_agent = GraderAgent(groq) | |
| print("✅ AIエージェントが正常に初期化されました") | |
| return translator, criteria_agent, grader_agent | |
| except Exception as e: | |
| print(f"❌ エージェント初期化エラー: {e}") | |
| return None, None, None | |
| # エージェント初期化 | |
| translator, CriteriaCheckAgent, grader_agent = safe_init_agents() | |
| # エラーハンドリング付きでエージェント評価を実行する関数 | |
| def evaluate_with_retry(agent, criteria, question, max_retries=3): | |
| """エラーハンドリング付きでエージェント評価を実行""" | |
| if agent is None: | |
| return "評価エラー: エージェントが初期化されていません。API keyを確認してください。" | |
| for attempt in range(max_retries): | |
| try: | |
| return agent.evaluate_eligibility(criteria, question) | |
| except Exception as e: | |
| if "missing variables" in str(e): | |
| print(f"プロンプトテンプレートエラー (試行 {attempt + 1}/{max_retries}): {e}") | |
| return "評価エラー: プロンプトテンプレートの設定に問題があります" | |
| elif "no healthy upstream" in str(e) or "InternalServerError" in str(e): | |
| print(f"Groqサーバーエラー (試行 {attempt + 1}/{max_retries}): {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(2) | |
| continue | |
| else: | |
| return "評価エラー: サーバーに接続できませんでした" | |
| elif "API key" in str(e) or "authentication" in str(e).lower(): | |
| return "評価エラー: API keyが無効または設定されていません" | |
| else: | |
| print(f"予期しないエラー (試行 {attempt + 1}/{max_retries}): {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(1) | |
| continue | |
| else: | |
| return f"評価エラー: {str(e)}" | |
| return "評価エラー: 最大リトライ回数に達しました" | |
| def evaluate_grade_with_retry(agent, judgment, max_retries=3): | |
| """エラーハンドリング付きでグレード評価を実行""" | |
| if agent is None: | |
| return "unclear" | |
| for attempt in range(max_retries): | |
| try: | |
| return agent.evaluate_eligibility(judgment) | |
| except Exception as e: | |
| if "no healthy upstream" in str(e) or "InternalServerError" in str(e): | |
| print(f"Groqサーバーエラー (グレード評価 - 試行 {attempt + 1}/{max_retries}): {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(2) | |
| continue | |
| else: | |
| return "unclear" | |
| elif "API key" in str(e) or "authentication" in str(e).lower(): | |
| return "unclear" | |
| else: | |
| print(f"予期しないエラー (グレード評価 - 試行 {attempt + 1}/{max_retries}): {e}") | |
| if attempt < max_retries - 1: | |
| time.sleep(1) | |
| continue | |
| else: | |
| return "unclear" | |
| return "unclear" | |
| # 基本的なClinicalTrials.gov API呼び出し(軽量版) | |
| def fetch_clinical_trials_basic(cancer_name): | |
| """基本的な臨床試験データ取得(requestsのみ使用)""" | |
| try: | |
| search_expr = f"{cancer_name} SEARCH[Location](AREA[LocationCountry]Japan AND AREA[LocationStatus]Recruiting)" | |
| base_url = "https://clinicaltrials.gov/api/v2/studies" | |
| params = { | |
| "query.titles": search_expr, | |
| "pageSize": 20 # 軽量版では20件に制限 | |
| } | |
| print(f"基本API呼び出し: {cancer_name}") | |
| response = requests.get(base_url, params=params) | |
| if response.status_code == 200: | |
| data = response.json() | |
| studies = data.get('studies', []) | |
| data_list = [] | |
| for study in studies: | |
| nctId = study['protocolSection']['identificationModule'].get('nctId', 'Unknown') | |
| title = study['protocolSection']['identificationModule'].get('briefTitle', 'no title') | |
| conditions = ', '.join(study['protocolSection']['conditionsModule'].get('conditions', ['No conditions listed'])) | |
| summary = study['protocolSection']['descriptionModule'].get('briefSummary', 'no summary') | |
| # 場所情報の抽出 | |
| locations_list = study['protocolSection'].get('contactsLocationsModule', {}).get('locations', []) | |
| japan_locations = [] | |
| for location in locations_list: | |
| if location.get('country') == 'Japan': | |
| city = location.get('city', 'Unknown City') | |
| japan_locations.append(city) | |
| primaryCompletionDate = study['protocolSection']['statusModule'].get('primaryCompletionDateStruct', {}).get('date', 'Unknown Date') | |
| eligibilityCriteria = study['protocolSection']['eligibilityModule'].get('eligibilityCriteria', 'Unknown') | |
| data_list.append({ | |
| "NCTID": nctId, | |
| "Title": title, | |
| "Primary Completion Date": primaryCompletionDate, | |
| "Cancer": conditions, | |
| "Summary": summary, | |
| "Japanes Locations": ', '.join(set(japan_locations)) if japan_locations else "No Japan locations", | |
| "Eligibility Criteria": eligibilityCriteria | |
| }) | |
| return data_list | |
| else: | |
| print(f"API呼び出し失敗: {response.status_code}") | |
| return [] | |
| except Exception as e: | |
| print(f"基本API呼び出しエラー: {e}") | |
| return [] | |
| # 軽量版データ生成関数 | |
| def generate_sample_data(age, sex, tumor_type, GeneMutation, Meseable, Biopsiable): | |
| """サンプルデータを生成(辞書のリスト形式)""" | |
| try: | |
| if not all([age, sex, tumor_type]): | |
| return [] | |
| safe_progress_update(None, 0.3, "📋 サンプルデータを生成中...") | |
| sample_data = [ | |
| { | |
| "NCTID": "NCT12345678", | |
| "AgentGrade": "yes", | |
| "Title": f"Clinical Trial for {tumor_type} in {sex} patients", | |
| "AgentJudgment": f"{age}歳{sex}の{tumor_type}患者は参加可能です。詳細な検査結果により最終判断が必要です。", | |
| "Japanes Locations": "Tokyo, Osaka", | |
| "Primary Completion Date": "2025-12-31", | |
| "Cancer": tumor_type, | |
| "Summary": f"Phase II study evaluating new treatment for {tumor_type}", | |
| "Eligibility Criteria": f"Age 18-75, confirmed {tumor_type}, adequate organ function" | |
| }, | |
| { | |
| "NCTID": "NCT87654321", | |
| "AgentGrade": "no", | |
| "Title": f"Alternative treatment for {tumor_type}", | |
| "AgentJudgment": f"{age}歳{sex}の{tumor_type}患者は年齢制限により参加できません。", | |
| "Japanes Locations": "Kyoto, Fukuoka", | |
| "Primary Completion Date": "2026-06-30", | |
| "Cancer": tumor_type, | |
| "Summary": f"Comparative study of standard vs experimental therapy for {tumor_type}", | |
| "Eligibility Criteria": f"Age 20-65, {tumor_type} with specific mutations, ECOG 0-1" | |
| }, | |
| { | |
| "NCTID": "NCT11111111", | |
| "AgentGrade": "unclear", | |
| "Title": f"Experimental therapy for {tumor_type} with {GeneMutation}", | |
| "AgentJudgment": f"{age}歳{sex}の{tumor_type}患者の参加は追加情報が必要で不明確です。", | |
| "Japanes Locations": "Nagoya, Sendai", | |
| "Primary Completion Date": "2025-09-15", | |
| "Cancer": tumor_type, | |
| "Summary": f"Early phase trial testing combination therapy for {tumor_type}", | |
| "Eligibility Criteria": f"Age 18-80, advanced {tumor_type}, previous treatment failure" | |
| } | |
| ] | |
| safe_progress_update(None, 1.0, "✅ サンプルデータ生成完了") | |
| return sample_data | |
| except Exception as e: | |
| print(f"サンプルデータ生成エラー: {e}") | |
| return [] | |
| # 基本版データ生成関数(ClinicalTrials.gov API使用、AI評価なし) | |
| def generate_basic_data(age, sex, tumor_type, GeneMutation, Meseable, Biopsiable): | |
| """基本版のデータ生成(API使用、AI評価なし)""" | |
| try: | |
| if not all([age, sex, tumor_type]): | |
| return [] | |
| safe_progress_update(None, 0.2, "🔍 ClinicalTrials.govからデータを検索中...") | |
| # 実際のAPI呼び出し | |
| data_list = fetch_clinical_trials_basic(tumor_type) | |
| safe_progress_update(None, 0.7, "📊 データを処理中...") | |
| if not data_list: | |
| print("臨床試験データが見つかりませんでした") | |
| return [] | |
| # AI評価なしのプレースホルダーを追加 | |
| for item in data_list: | |
| item['AgentJudgment'] = f'基本版:{age}歳{sex}の{tumor_type}患者への詳細評価にはAI機能が必要です' | |
| item['AgentGrade'] = 'unclear' | |
| safe_progress_update(None, 1.0, f"✅ 完了: {len(data_list)}件の臨床試験を取得") | |
| print(f"基本版評価完了。結果: {len(data_list)} 件") | |
| return data_list | |
| except Exception as e: | |
| print(f"基本版データ生成中に予期しないエラー: {e}") | |
| traceback.print_exc() | |
| return [] | |
| # AI評価付きデータ生成関数(リアルタイムプログレス付き) | |
| def generate_full_data_with_progress(age, sex, tumor_type, GeneMutation, Meseable, Biopsiable): | |
| """完全版のデータ生成(リアルタイム進行状況付き)""" | |
| try: | |
| if not all([age, sex, tumor_type]): | |
| yield [] | |
| return | |
| yield "🔄 AI評価システムを初期化中..." | |
| # 日本語の腫瘍タイプを英語に翻訳 | |
| try: | |
| if translator is not None: | |
| yield "🌐 腫瘍タイプを英語に翻訳中..." | |
| TumorName = translator.translate(tumor_type) | |
| print(f"腫瘍タイプ翻訳: {tumor_type} → {TumorName}") | |
| else: | |
| print("翻訳エージェントが利用できません。元の値を使用します。") | |
| TumorName = tumor_type | |
| except Exception as e: | |
| print(f"翻訳エラー: {e}") | |
| TumorName = tumor_type | |
| # 質問文を生成 | |
| try: | |
| yield "❓ 患者情報から評価用質問を生成中..." | |
| ex_question = generate_ex_question_English(age, sex, TumorName, GeneMutation, Meseable, Biopsiable) | |
| print(f"生成された質問: {ex_question}") | |
| except Exception as e: | |
| print(f"質問生成エラー: {e}") | |
| yield [] | |
| return | |
| # 臨床試験データの取得 | |
| try: | |
| yield "🔍 ClinicalTrials.govから臨床試験データを検索中..." | |
| print(f"臨床試験データを検索中: {TumorName}") | |
| df = fetch_clinical_trials(TumorName) | |
| if df.empty: | |
| print("臨床試験データが見つかりませんでした") | |
| yield [] | |
| return | |
| print(f"取得した臨床試験数: {len(df)}") | |
| # DataFrameを辞書のリストに変換 | |
| data_list = df.to_dict('records') | |
| except Exception as e: | |
| print(f"臨床試験データ取得エラー: {e}") | |
| yield [] | |
| return | |
| # AI評価の実行(最大10件まで) | |
| evaluation_limit = min(len(data_list), 10) | |
| print(f"AI評価実行: {evaluation_limit} 件を処理します") | |
| yield f"🤖 AI評価開始: {evaluation_limit}件の臨床試験を分析中..." | |
| for i, item in enumerate(data_list[:evaluation_limit]): | |
| try: | |
| # リアルタイム進行状況表示 | |
| yield f"🧠 AI評価中: {i+1}/{evaluation_limit} - {item['NCTID']}" | |
| print(f"評価中 ({i+1}/{evaluation_limit}): {item['NCTID']}") | |
| target_criteria = item['Eligibility Criteria'] | |
| # エラーハンドリング付きで評価実行 | |
| agent_judgment = evaluate_with_retry(CriteriaCheckAgent, target_criteria, ex_question) | |
| agent_grade = evaluate_grade_with_retry(grader_agent, agent_judgment) | |
| # データの更新 | |
| item['AgentJudgment'] = agent_judgment | |
| item['AgentGrade'] = agent_grade | |
| except Exception as e: | |
| print(f"NCTID {item['NCTID']} の評価中にエラー: {e}") | |
| item['AgentJudgment'] = f"エラー: {str(e)}" | |
| item['AgentGrade'] = "unclear" | |
| # 評価されなかった残りのアイテムにはプレースホルダーを設定 | |
| yield "📊 結果を整理中..." | |
| for item in data_list[evaluation_limit:]: | |
| item['AgentJudgment'] = f"完全版:{age}歳{sex}の{tumor_type}患者(評価制限により未処理)" | |
| item['AgentGrade'] = "unclear" | |
| print(f"完全版評価完了。結果: {len(data_list)} 件(うち{evaluation_limit}件をAI評価)") | |
| # 最終結果を返す | |
| yield data_list | |
| except Exception as e: | |
| print(f"完全版データ生成中に予期しないエラー: {e}") | |
| yield [] | |
| # HTMLテーブル生成関数 | |
| def create_html_table(data, show_grade=True): | |
| """データをHTMLテーブルに変換""" | |
| if not data: | |
| return "<div style='text-align: center; padding: 20px; color: #666;'>📄 データがありません</div>" | |
| # CSS スタイル | |
| table_style = """ | |
| <style> | |
| .clinical-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 10px 0; | |
| font-family: Arial, sans-serif; | |
| font-size: 14px; | |
| } | |
| .clinical-table th { | |
| background-color: #f8f9fa; | |
| border: 1px solid #dee2e6; | |
| padding: 12px 8px; | |
| text-align: left; | |
| font-weight: bold; | |
| color: #495057; | |
| } | |
| .clinical-table td { | |
| border: 1px solid #dee2e6; | |
| padding: 10px 8px; | |
| vertical-align: top; | |
| } | |
| .grade-yes { background-color: #d4edda; } | |
| .grade-no { background-color: #f8d7da; } | |
| .grade-unclear { background-color: #fff3cd; } | |
| .clinical-table tr:hover { | |
| background-color: #f5f5f5; | |
| } | |
| .nctid-link { | |
| color: #007bff; | |
| text-decoration: none; | |
| font-weight: bold; | |
| } | |
| .nctid-link:hover { | |
| text-decoration: underline; | |
| } | |
| .title-cell { | |
| max-width: 300px; | |
| word-wrap: break-word; | |
| } | |
| .criteria-cell { | |
| max-width: 400px; | |
| word-wrap: break-word; | |
| font-size: 12px; | |
| } | |
| </style> | |
| """ | |
| # テーブルヘッダー | |
| html = table_style + '<table class="clinical-table">' | |
| html += '<tr>' | |
| html += '<th>NCTID</th>' | |
| if show_grade: | |
| html += '<th>Grade</th>' | |
| html += '<th>Title</th>' | |
| if show_grade: | |
| html += '<th>AI Judgment</th>' | |
| html += '<th>Japanese Locations</th>' | |
| html += '<th>Completion Date</th>' | |
| html += '<th>Cancer Type</th>' | |
| html += '</tr>' | |
| # データ行 | |
| for item in data: | |
| grade = item.get('AgentGrade', 'unclear') | |
| grade_class = f"grade-{grade}" if show_grade else "" | |
| html += f'<tr class="{grade_class}">' | |
| # NCTID(リンク付き) | |
| nctid = item.get('NCTID', '') | |
| html += f'<td><a href="https://clinicaltrials.gov/ct2/show/{nctid}" target="_blank" class="nctid-link">{nctid}</a></td>' | |
| # Grade | |
| if show_grade: | |
| grade_emoji = {'yes': '✅', 'no': '❌', 'unclear': '❓'}.get(grade, '❓') | |
| html += f'<td style="text-align: center;">{grade_emoji} {grade}</td>' | |
| # Title | |
| title = item.get('Title', '').strip() | |
| html += f'<td class="title-cell">{title}</td>' | |
| # AI Judgment | |
| if show_grade: | |
| judgment = item.get('AgentJudgment', '').strip() | |
| html += f'<td class="criteria-cell">{judgment}</td>' | |
| # Japanese Locations | |
| locations = item.get('Japanes Locations', '').strip() | |
| html += f'<td>{locations}</td>' | |
| # Completion Date | |
| completion_date = item.get('Primary Completion Date', '').strip() | |
| html += f'<td>{completion_date}</td>' | |
| # Cancer Type | |
| cancer = item.get('Cancer', '').strip() | |
| html += f'<td>{cancer}</td>' | |
| html += '</tr>' | |
| html += '</table>' | |
| # 統計情報 | |
| if show_grade: | |
| total = len(data) | |
| yes_count = len([item for item in data if item.get('AgentGrade') == 'yes']) | |
| no_count = len([item for item in data if item.get('AgentGrade') == 'no']) | |
| unclear_count = len([item for item in data if item.get('AgentGrade') == 'unclear']) | |
| stats_html = f""" | |
| <div style='margin: 10px 0; padding: 10px; background-color: #f8f9fa; border-radius: 5px; font-size: 14px;'> | |
| <strong>📊 統計:</strong> | |
| 合計 {total} 件 | | |
| ✅ 適格 {yes_count} 件 | | |
| ❌ 不適格 {no_count} 件 | | |
| ❓ 要検討 {unclear_count} 件 | |
| </div> | |
| """ | |
| html = stats_html + html | |
| return html | |
| # フィルタリング関数 | |
| def filter_data(data, grade): | |
| """データをフィルタリング""" | |
| if not data: | |
| return [] | |
| try: | |
| if grade == "all": | |
| return data | |
| return [item for item in data if item.get('AgentGrade') == grade] | |
| except Exception as e: | |
| print(f"フィルタリングエラー: {e}") | |
| return data | |
| # システム状態の確認 | |
| def get_system_status(): | |
| """システムの現在の状態を確認""" | |
| groq_available = bool(os.getenv("GROQ_API_KEY")) | |
| if FULL_VERSION and groq_available: | |
| return "🟢 完全版", "リアルタイム検索 + AI適格性評価が利用可能です" | |
| elif FULL_VERSION and not groq_available: | |
| return "🟡 完全版(制限)", "GROQ_API_KEYが必要です(Settings → Variables and secrets で設定)" | |
| elif LANGCHAIN_AVAILABLE: | |
| return "🟡 基本版", "ClinicalTrials.gov検索のみ利用可能(AI評価にはGROQ_API_KEY必要)" | |
| else: | |
| return "🔴 軽量版", "サンプルデータのみ表示" | |
| # CSV エクスポート関数 | |
| def export_to_csv(data): | |
| """データをCSVファイルとしてエクスポート""" | |
| try: | |
| if not data: | |
| return None | |
| # DataFrame に変換 | |
| df = pd.DataFrame(data) | |
| # ファイルパス | |
| file_path = "clinical_trials_data.csv" | |
| df.to_csv(file_path, index=False, encoding='utf-8-sig') | |
| return file_path | |
| except Exception as e: | |
| print(f"CSV エクスポートエラー: {e}") | |
| return None | |
| # Gradioインターフェースの作成 | |
| with gr.Blocks(title="臨床試験適格性評価", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("## 🏥 臨床試験適格性評価インターフェース(完全版)") | |
| # システム状態表示 | |
| status_level, status_message = get_system_status() | |
| gr.Markdown(f"**システム状態**: {status_level} - {status_message}") | |
| # 機能説明 | |
| groq_available = bool(os.getenv("GROQ_API_KEY")) | |
| if FULL_VERSION and groq_available: | |
| gr.Markdown("🚀 **利用可能機能**: ClinicalTrials.gov リアルタイム検索 + AI適格性評価 + データエクスポート") | |
| gr.Markdown("🤖 **AI機能**: Groq Llama3-70B による自動適格性判断 + 3段階グレード評価") | |
| elif FULL_VERSION: | |
| gr.Markdown("🔧 **利用可能機能**: リアルタイム検索 + 基本評価(AI機能にはGROQ_API_KEY必要)") | |
| gr.Markdown("⚠️ **API設定が必要**: Settings → Variables and secrets で GROQ_API_KEY を設定してください") | |
| elif LANGCHAIN_AVAILABLE: | |
| gr.Markdown("🔧 **利用可能機能**: ClinicalTrials.gov検索 + 基本評価 + データエクスポート") | |
| else: | |
| gr.Markdown("📋 **利用可能機能**: サンプルデータ表示 + フィルタリング") | |
| gr.Markdown("💡 **使用方法**: 患者情報を入力してボタンをクリックしてください。") | |
| # 各種入力フィールド | |
| with gr.Row(): | |
| with gr.Column(): | |
| age_input = gr.Textbox(label="Age", placeholder="例: 65", value="65") | |
| sex_input = gr.Dropdown(choices=["男性", "女性"], label="Sex", value="男性") | |
| tumor_type_input = gr.Textbox(label="Tumor Type", placeholder="例: gastric cancer", value="gastric cancer") | |
| with gr.Column(): | |
| gene_mutation_input = gr.Textbox(label="Gene Mutation", placeholder="例: HER2", value="HER2") | |
| measurable_input = gr.Dropdown(choices=["有り", "無し", "不明"], label="Measurable Tumor", value="有り") | |
| biopsiable_input = gr.Dropdown(choices=["有り", "無し", "不明"], label="Biopsiable Tumor", value="有り") | |
| # 結果表示エリア(HTMLテーブル) | |
| results_html = gr.HTML(label="Clinical Trials Results") | |
| # 内部状態用 | |
| data_state = gr.State(value=[]) | |
| # ボタン類 | |
| with gr.Row(): | |
| if FULL_VERSION and groq_available: | |
| generate_button = gr.Button("🤖 AI適格性評価付き検索(完全版)", variant="primary") | |
| elif FULL_VERSION: | |
| generate_button = gr.Button("🔍 リアルタイム検索(GROQ_API_KEY設定後にAI評価有効)", variant="primary") | |
| elif LANGCHAIN_AVAILABLE: | |
| generate_button = gr.Button("📡 ClinicalTrials.gov検索(基本版)", variant="primary") | |
| else: | |
| generate_button = gr.Button("📋 サンプルデータ表示", variant="primary") | |
| with gr.Row(): | |
| yes_button = gr.Button("✅ Show Eligible Trials", variant="secondary") | |
| no_button = gr.Button("❌ Show Ineligible Trials", variant="secondary") | |
| unclear_button = gr.Button("❓ Show Unclear Trials", variant="secondary") | |
| all_button = gr.Button("📊 Show All Trials", variant="secondary") | |
| with gr.Row(): | |
| download_button = gr.Button("💾 Download CSV") | |
| # ダウンロードファイル | |
| download_output = gr.File(label="Download CSV", visible=False) | |
| # プログレス表示 | |
| progress_text = gr.Textbox(label="Processing Status", value="Ready", interactive=False) | |
| # イベントハンドリング | |
| def update_data_and_display(age, sex, tumor_type, gene_mutation, measurable, biopsiable): | |
| """データ生成と表示更新(リアルタイムプログレス付き)""" | |
| try: | |
| groq_available = bool(os.getenv("GROQ_API_KEY")) | |
| # 初期進行状況表示 | |
| initial_progress = "🚀 処理を開始しています..." | |
| yield gr.update(), [], initial_progress | |
| if FULL_VERSION and groq_available: | |
| mode_progress = "🤖 AI適格性評価モードで実行中..." | |
| yield gr.update(), [], mode_progress | |
| # AI評価付きデータ生成(ユーザーに進行状況を返すジェネレーター) | |
| for progress_info in generate_full_data_with_progress(age, sex, tumor_type, gene_mutation, measurable, biopsiable): | |
| if isinstance(progress_info, str): # 進行状況メッセージ | |
| yield gr.update(), [], progress_info | |
| else: # 最終結果 | |
| data = progress_info | |
| break | |
| elif FULL_VERSION: | |
| mode_progress = "🔍 基本モードで実行中..." | |
| yield gr.update(), [], mode_progress | |
| data = generate_basic_data(age, sex, tumor_type, gene_mutation, measurable, biopsiable) | |
| elif LANGCHAIN_AVAILABLE: | |
| mode_progress = "📡 基本検索モードで実行中..." | |
| yield gr.update(), [], mode_progress | |
| data = generate_basic_data(age, sex, tumor_type, gene_mutation, measurable, biopsiable) | |
| else: | |
| mode_progress = "📋 サンプルモードで実行中..." | |
| yield gr.update(), [], mode_progress | |
| data = generate_sample_data(age, sex, tumor_type, gene_mutation, measurable, biopsiable) | |
| if data: | |
| html_table = create_html_table(data) | |
| final_progress = f"✅ 完了: {len(data)} 件の臨床試験が見つかりました" | |
| if FULL_VERSION and groq_available: | |
| ai_evaluated_count = len([item for item in data if 'エラー' not in item.get('AgentJudgment', '') and '未処理' not in item.get('AgentJudgment', '')]) | |
| final_progress += f"(うち{min(len(data), 10)}件をAI評価済み)" | |
| yield html_table, data, final_progress | |
| else: | |
| html_table = "<div style='text-align: center; padding: 20px; color: #666;'>⚠️ 該当する臨床試験が見つかりませんでした</div>" | |
| final_progress = "⚠️ 該当する臨床試験が見つかりませんでした" | |
| yield html_table, [], final_progress | |
| except Exception as e: | |
| error_msg = f"❌ エラー: {str(e)}" | |
| error_html = f"<div style='text-align: center; padding: 20px; color: #d32f2f;'>{error_msg}</div>" | |
| print(f"データ更新エラー: {e}") | |
| yield error_html, [], error_msg | |
| def filter_and_show(data, grade): | |
| """フィルタリングと表示更新""" | |
| try: | |
| filtered_data = filter_data(data, grade) | |
| html_table = create_html_table(filtered_data) | |
| return html_table | |
| except Exception as e: | |
| print(f"フィルタリングエラー: {e}") | |
| return create_html_table(data) | |
| def download_csv(data): | |
| """CSV ダウンロード処理""" | |
| try: | |
| if not data: | |
| return None | |
| return export_to_csv(data) | |
| except Exception as e: | |
| print(f"ダウンロードエラー: {e}") | |
| return None | |
| # ボタンイベント | |
| generate_button.click( | |
| fn=update_data_and_display, | |
| inputs=[age_input, sex_input, tumor_type_input, gene_mutation_input, measurable_input, biopsiable_input], | |
| outputs=[results_html, data_state, progress_text] | |
| ) | |
| yes_button.click( | |
| fn=lambda data: filter_and_show(data, "yes"), | |
| inputs=[data_state], | |
| outputs=[results_html] | |
| ) | |
| no_button.click( | |
| fn=lambda data: filter_and_show(data, "no"), | |
| inputs=[data_state], | |
| outputs=[results_html] | |
| ) | |
| unclear_button.click( | |
| fn=lambda data: filter_and_show(data, "unclear"), | |
| inputs=[data_state], | |
| outputs=[results_html] | |
| ) | |
| all_button.click( | |
| fn=lambda data: filter_and_show(data, "all"), | |
| inputs=[data_state], | |
| outputs=[results_html] | |
| ) | |
| download_button.click( | |
| fn=download_csv, | |
| inputs=[data_state], | |
| outputs=[download_output] | |
| ) | |
| # フッター情報 | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| groq_available_footer = bool(os.getenv("GROQ_API_KEY")) | |
| gr.Markdown("🔬 **技術情報**: ClinicalTrials.gov API + LangChain + Groq Llama3-70B") | |
| gr.Markdown("📝 **AI機能状況**: " + ("AI評価機能有効" if (FULL_VERSION and groq_available_footer) else "GROQ_API_KEY設定後にAI機能有効化")) | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| debug=False, | |
| show_error=True | |
| ) | |