churn_predict / app.py
fudii0921's picture
Create app.py
4f19840 verified
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>&copy; 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")