Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| from sklearn.ensemble import RandomForestClassifier | |
| import cohere | |
| from dotenv import load_dotenv | |
| import os | |
| load_dotenv(verbose=True) | |
| # CohereのAPIキーを取得 | |
| cohere_api_key = os.environ.get("COHERE_API_KEY") | |
| client = cohere.ClientV2(api_key=cohere_api_key) | |
| template = """ | |
| <!DOCTYPE html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Executive Report - Liquid Glass Edition</title> | |
| <style> | |
| :root { | |
| --glass-bg: rgba(255, 255, 255, 0.4); | |
| --glass-border: rgba(255, 255, 255, 0.6); | |
| --text-main: #2d3436; | |
| --accent-color: #6c5ce7; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Noto Sans JP', sans-serif; | |
| color: var(--text-main); | |
| background-color: #f0f2f5; | |
| display: flex; | |
| justify-content: center; | |
| overflow-x: hidden; | |
| } | |
| /* 背景のアニメーションオーブ */ | |
| .bg-gradient { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(120deg, #e0c3fc 0%, #8ec5fc 100%); | |
| z-index: -2; | |
| } | |
| .bg-orb { | |
| position: fixed; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| z-index: -1; | |
| opacity: 0.5; | |
| } | |
| .orb-1 { | |
| width: 400px; | |
| height: 400px; | |
| background: #fab1a0; | |
| top: -100px; | |
| right: -100px; | |
| } | |
| .orb-2 { | |
| width: 600px; | |
| height: 600px; | |
| background: #81ecec; | |
| bottom: -200px; | |
| left: -200px; | |
| } | |
| /* 共通ガラススタイル */ | |
| .glass { | |
| background: var(--glass-bg); | |
| backdrop-filter: blur(15px); | |
| -webkit-backdrop-filter: blur(15px); | |
| border: 1px solid var(--glass-border); | |
| border-radius: 24px; | |
| box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); | |
| margin-bottom: 24px; | |
| } | |
| /* メインレイアウト */ | |
| .report-wrapper { | |
| width: 90%; | |
| max-width: 800px; | |
| padding: 60px 0; | |
| z-index: 1; | |
| } | |
| /* 各セクションの装飾 */ | |
| .report-header { | |
| padding: 40px; | |
| text-align: center; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 4px 12px; | |
| background: var(--accent-color); | |
| color: white; | |
| border-radius: 50px; | |
| font-size: 0.8rem; | |
| font-weight: bold; | |
| margin-bottom: 16px; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| margin: 0; | |
| line-height: 1.2; | |
| } | |
| .highlight { | |
| color: var(--accent-color); | |
| } | |
| .meta-info { | |
| margin-top: 20px; | |
| font-size: 0.9rem; | |
| opacity: 0.7; | |
| } | |
| .meta-info span { | |
| margin: 0 10px; | |
| } | |
| /* キーメッセージ */ | |
| .key-message { | |
| padding: 30px; | |
| border-left: 8px solid var(--accent-color); | |
| } | |
| .key-message h2 { | |
| margin-top: 0; | |
| font-size: 1.2rem; | |
| color: var(--accent-color); | |
| } | |
| /* 統計カード */ | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 24px; | |
| } | |
| .stat-card { | |
| padding: 24px; | |
| text-align: center; | |
| } | |
| .stat-card .label { | |
| font-size: 0.85rem; | |
| display: block; | |
| margin-bottom: 8px; | |
| } | |
| .stat-card .value { | |
| font-size: 1.8rem; | |
| font-weight: bold; | |
| font-family: 'Montserrat'; | |
| } | |
| .stat-card .trend { | |
| font-size: 0.8rem; | |
| margin-left: 5px; | |
| font-weight: bold; | |
| } | |
| .trend.up { | |
| color: #00b894; | |
| } | |
| .trend.down { | |
| color: #d63031; | |
| } | |
| /* ボディテキスト */ | |
| .report-body { | |
| padding: 40px; | |
| line-height: 1.8; | |
| } | |
| ul { | |
| padding-left: 20px; | |
| } | |
| li { | |
| margin-bottom: 10px; | |
| } | |
| .report-footer { | |
| text-align: center; | |
| font-size: 0.8rem; | |
| opacity: 0.5; | |
| margin-top: 40px; | |
| } | |
| /* スマホ対応 */ | |
| @media (max-width: 600px) { | |
| h1 { | |
| font-size: 1.8rem; | |
| } | |
| .report-wrapper { | |
| padding: 20px 0; | |
| } | |
| } | |
| </style> | |
| <link | |
| href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;500;700&family=Montserrat:wght@600&display=swap" | |
| rel="stylesheet"> | |
| </head> | |
| <body> | |
| <div class="bg-gradient"></div> | |
| <div class="bg-orb orb-1"></div> | |
| <div class="bg-orb orb-2"></div> | |
| <main class="report-wrapper"> | |
| <header class="report-header glass"> | |
| <div class="badge" style="color: white;">Quarterly Report 2024</div> | |
| <h1>マーケティング戦略 <br><span class="highlight">進捗報告書</span></h1> | |
| <div class="meta-info"> | |
| <span>社外秘</span> | |
| </div> | |
| </header> | |
| <section class="key-message glass"> | |
| <h2><span class="icon">💡</span> Key Insight</h2> | |
| <p> | |
| 過去3ヶ月の施策により、リード獲得コストは<strong>25%削減</strong>されました。 | |
| 次四半期は、現在のLiquid Glassデザインを導入したLPのABテストを強化し、CVRの更なる向上を目指します。 | |
| </p> | |
| </section> | |
| <div class="stats-grid"> | |
| <div class="stat-card glass"> | |
| <span class="label">総リード数</span> | |
| <span class="value">1,240</span> | |
| <span class="trend up">+12%</span> | |
| </div> | |
| <div class="stat-card glass"> | |
| <span class="label">CPA(獲得単価)</span> | |
| <span class="value">¥4,200</span> | |
| <span class="trend down">-8%</span> | |
| </div> | |
| <div class="stat-card glass"> | |
| <span class="label">成約率</span> | |
| <span class="value">4.8%</span> | |
| <span class="trend up">+1.2%</span> | |
| </div> | |
| </div> | |
| <section class="report-body glass"> | |
| <h3>今後の重点施策</h3> | |
| <p> | |
| 市場のトレンドは「視覚的な透明感」と「信頼性」の両立に移行しています。 | |
| 本報告書で採用しているデザインコンセプトは、ユーザーに清潔感とモダンな印象を与え、滞在時間の延長に寄与します。 | |
| </p> | |
| <ul> | |
| <li>UI/UXの透明化による情報の階層化</li> | |
| <li>モバイルフレンドリーなレスポンシブ対応の強化</li> | |
| <li>インタラクティブなデータビジュアライゼーションの導入</li> | |
| </ul> | |
| </section> | |
| <footer class="report-footer"> | |
| <p>© 2025 RYH International. Confidential</p> | |
| </footer> | |
| </main> | |
| </body> | |
| </html> | |
| """ | |
| meta_prompt = ''' | |
| 思考のガイドライン | |
| 文脈の理解: ユーザーの意図、背景、制約条件を正確に把握してください。 | |
| 推論の先行: 結論を出す前に、必ず「なぜその結論に至るのか」という思考のプロセスを記述してください。 | |
| 多角的視点: 表面的な回答だけでなく、潜在的な課題や代替案、リスクについても考慮してください。 | |
| 論理的整合性: ステップ間のつながりを明確にし、矛盾がないかセルフチェックを行ってください。 | |
| 実行ステップ | |
| 1. タスクの分解と分析 | |
| 実行すべきタスクを最小単位の要素に分解してください。 | |
| 必要な情報、使用すべきトーン、守るべきルールを明確にします。 | |
| 2. 推論と戦略立案(Reasoning) | |
| 結論に至るまでの論理的な道筋を立ててください。 | |
| 複数のアプローチがある場合は、最も適切なものを選択した理由を明記してください。 | |
| 計算や複雑な論理が必要な場合は、ここでステップバイステップで展開してください。 | |
| 3. 検証と洗練 | |
| 生成した解決策が、すべての制約条件を満たしているか確認してください。 | |
| より簡潔に、あるいはより強力にできる部分を修正してください。 | |
| 出力形式 | |
| 以下の構造で回答してください。 | |
| 推論プロセス(Reasoning) | |
| [タスクの分析、論理的な思考、解決までのステップを詳細に記述してください。] | |
| 最終回答(Conclusion/Result) | |
| [推論に基づいた最終的な成果物を出力してください。形式はタスクに応じて最適化してください(JSON、マークダウン、文章など)。] | |
| ''' | |
| # ========================================== | |
| # 1. 専門家分析・ガイドライン データベース (6,000文字級) | |
| # ========================================== | |
| ADVISORY_DB = { | |
| "STRATEGIC_HEADER": "# 🏛️ 戦略的組織防衛・人事アドバイザリー・フルレポート\n**本レポートは、AI予測に基づき、経営心理学と労務リスク管理の観点から生成されました。**\n---\n", | |
| "GUIDELINE": """ | |
| ## 📘 専門家による対応ガイドライン | |
| 離職予兆を検知した際、人事が取るべき行動指針は以下の通りです。 | |
| 1. **「評価」ではなく「共感」の対話:** 離職を検討している層は、組織への不信感を抱いています。詰問ではなく「あなたの状況を心配している」というメッセージを優先してください。 | |
| 2. **制度の弾力的運用:** 介護や育児、心身の不調など、個別の事情に合わせた「特別措置(特例のリモート、短時間勤務等)」を即断で提示することが、最強のリテンションになります。 | |
| 3. **心理的安全性の担保:** 相談内容が査定に影響しないことを明確に約束し、本音を引き出してください。 | |
| """, | |
| "FINANCIAL_IMPACT": """ | |
| ## 💰 離職に伴う経済的インパクト予測(推定損失額) | |
| 対象者が離職した場合、組織には以下の経済的損失が発生します。 | |
| 1. **直接的損失(採用・教育費):** 年収の約35%〜50%(エージェント手数料、媒体費、面接工数)。 | |
| 2. **生産性の空白期間:** 後任が前任者と同等のパフォーマンスを発揮するまでの4〜6ヶ月間の給与相当額。 | |
| 3. **ナレッジ・ロス:** 担当顧客との関係、業務ノウハウ、およびチームの士気低下による「連鎖離職」の潜在的リスク。 | |
| **【結論】推定損失合計:年収の1.5倍〜2.2倍。** 本レポートの提言を実行することは、経営上、極めて合理的な投資です。 | |
| """ | |
| } | |
| # ========================================== | |
| # 2. モデルの学習(すべての入力を整数として定義) | |
| # ========================================== | |
| def train_mega_model(): | |
| np.random.seed(42) | |
| # 項目名とそれぞれの整数の範囲 | |
| feature_config = { | |
| '仕事の満足度(0-10)': (0, 10), | |
| '上司への満足度(0-10)': (0, 10), | |
| '月間総労働時間(h)': (140, 350), | |
| '先月の残業時間(h)': (0, 150), | |
| '勤続年数': (0, 40), | |
| '昇進からの経過年数': (0, 20), | |
| '年間研修受講時間(h)': (0, 200), | |
| '通勤時間(片道分)': (0, 180), | |
| 'リモートワーク比率(0-100%)': (0, 100), | |
| '週の面談回数': (0, 10), | |
| '給与ランク(1-10)': (1, 10), | |
| '賞与評価(0-10)': (0, 10), | |
| 'チームの一体感(0-10)': (0, 10), | |
| '年齢': (20, 70), | |
| '扶養家族数': (0, 5) | |
| } | |
| feature_names = list(feature_config.keys()) | |
| n_samples = 5000 | |
| # 整数データの生成 | |
| data = {f: np.random.randint(low, high + 1, n_samples) for f, (low, high) in feature_config.items()} | |
| df = pd.DataFrame(data) | |
| # 離職ロジック(整数値に基づいた重み付け) | |
| def churn_logic(row): | |
| score = 0 | |
| if row['仕事の満足度(0-10)'] <= 3: score += 0.5 | |
| if row['先月の残業時間(h)'] >= 60: score += 0.4 | |
| if row['上司への満足度(0-10)'] <= 3: score += 0.4 | |
| if row['昇進からの経過年数'] >= 5: score += 0.2 | |
| return 1 if score > 0.7 else 0 | |
| df['target'] = df.apply(churn_logic, axis=1) | |
| X = df[feature_names] | |
| y = df['target'] | |
| model = RandomForestClassifier(n_estimators=100, random_state=42).fit(X, y) | |
| return model, feature_names | |
| model, feature_names = train_mega_model() | |
| # ========================================== | |
| # 3. レポート生成エンジン(個別所見 & 専門家分析) | |
| # ========================================== | |
| def generate_individual_findings(d): | |
| findings = "## 📋 入力データに基づく個別所見(精密分析)\n" | |
| if d['仕事の満足度(0-10)'] <= 3: | |
| findings += f"⚠️ **モチベーション低下:** 満足度スコアが極めて低いです({d['仕事の満足度(0-10)']}/10)。現職務への適性再確認が必要です。\n" | |
| if d['先月の残業時間(h)'] >= 45: | |
| findings += f"⚠️ **過重労働リスク:** 残業時間({d['先月の残業時間(h)']}h)が危険域です。メンタル不調による突発的離職を防ぐため、即時の業務調整を。\n" | |
| if d['上司への満足度(0-10)'] <= 3: | |
| findings += f"⚠️ **人間関係の不全:** 上司との関係({d['上司への満足度(0-10)']}/10)が最大の離職リスクです。指示系統の変更を検討してください。\n" | |
| if d['昇進からの経過年数'] >= 5: | |
| findings += f"⚠️ **キャリア停滞感:** 5年以上昇進がないことで、組織内での未来に限界を感じています。\n" | |
| if d['通勤時間(片道分)'] >= 60 and d['リモートワーク比率(0-100%)'] <= 20: | |
| findings += f"⚠️ **環境負荷:** 長距離通勤と低いリモート率が私生活を圧迫しています。\n" | |
| if not any("⚠️" in s for s in findings.split('\n')): | |
| findings += "✅ **良好な傾向:** 現在の入力値において、深刻な離職リスクを押し上げる単一要因は見当たりません。\n" | |
| return findings | |
| def generate_expert_analysis(prob): | |
| analysis = "## 🔍 専門家による多角的分析\n" | |
| if prob > 0.7: | |
| analysis += "**【判定:緊急離脱状態】** 対象者は既に「心理的契約」を解除しています。引き止めよりも、まずは本音を吐き出させる「デトックス面談」を優先してください。\n" | |
| elif prob > 0.4: | |
| analysis += "**【判定:揺らぎ状態】** 他社からの誘いや、現状への疑問が芽生えています。今、具体的な改善策を提示すれば定着に繋がります。\n" | |
| else: | |
| analysis += "**【判定:安定維持】** 組織へのコミットメントは高いですが、定期的な1on1を怠らず、小さな変化を見逃さないでください。\n" | |
| return analysis | |
| # ========================================== | |
| # 4. メイン関数(UserWarning解消 & レポート構築) | |
| # ========================================== | |
| def generate_advisory_report(*args): | |
| # すべての入力を整数として扱う | |
| args_int = [int(a) for a in args] | |
| input_df = pd.DataFrame([args_int], columns=feature_names) | |
| prob = model.predict_proba(input_df)[0][1] | |
| d = dict(zip(feature_names, args_int)) | |
| report = ADVISORY_DB["STRATEGIC_HEADER"] | |
| report += f"## 🏆 総合評価:離職予測確率 {prob*100:.1f}%\n\n" | |
| report += generate_individual_findings(d) | |
| report += generate_expert_analysis(prob) | |
| report += ADVISORY_DB["GUIDELINE"] | |
| report += ADVISORY_DB["FINANCIAL_IMPACT"] | |
| full_prompt = f"{meta_prompt}に基づいて{report}を分析して、最終評価を{template}に基づいてHTML形式で出力してください。回答が見つからない場合は、ウェブで検索して{meta_prompt}に基づいて必ず、{template}に基づいてHTML形式で回答してください。" | |
| # Cohere APIの呼び出し | |
| response = client.chat( | |
| model="command-a-03-2025", | |
| messages=[ | |
| {"role": "user", "content": full_prompt} | |
| ] | |
| ) | |
| myresp = response.message.content[0].text | |
| if '```html' in myresp: | |
| part1 = myresp.find('```html') | |
| part2 = myresp.rfind('```') | |
| reasoning_part = myresp[:part1].strip() | |
| html_part = myresp[part1+7:part2].strip() | |
| else: | |
| # Markdownタグがない場合のフォールバック | |
| reasoning_part = "HTMLタグが見つかりませんでした。全文を表示します。" | |
| html_part = myresp | |
| prevbody = report + '\n\n' + reasoning_part | |
| return prevbody, html_part, "Powered by RYH International." | |
| # ========================================== | |
| # 5. Gradio UI (すべて整数入力) | |
| # ========================================== | |
| with gr.Blocks(theme=gr.themes.Soft(), title="HR Advisory Platform", css="footer {visibility: hidden;}") as demo: | |
| gr.Markdown("# 🏢 組織・人事評価 統合アドバイザリー (AI版)") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 📊 従業員データ入力") | |
| inputs = [] | |
| for f in feature_names: | |
| if '0-10' in f or '1-10' in f: | |
| inputs.append(gr.Slider(0, 10, 5, step=1, label=f)) | |
| elif '0-100%' in f: | |
| inputs.append(gr.Slider(0, 100, 20, step=1, label=f)) | |
| elif 'h' in f or '年' in f or '分' in f or '歳' in f or '数' in f or '回' in f: | |
| inputs.append(gr.Number(value=0, precision=0, label=f)) | |
| else: | |
| inputs.append(gr.Number(value=0, precision=0, label=f)) | |
| btn = gr.Button("🔍 統合レポートを生成", variant="primary") | |
| with gr.Column(scale=2): | |
| output = gr.Markdown(show_copy_button=True) | |
| html_output = gr.HTML() | |
| ip_right = gr.Textbox(label="IP Information", interactive=False) | |
| btn.click(generate_advisory_report, inputs=inputs, outputs=[output, html_output, ip_right]) | |
| demo.launch(favicon_path="favicon.ico") |