fudii0921 commited on
Commit
4f19840
·
verified ·
1 Parent(s): c34e103

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +496 -0
app.py ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ from sklearn.ensemble import RandomForestClassifier
5
+ import cohere
6
+ from dotenv import load_dotenv
7
+ import os
8
+
9
+ load_dotenv(verbose=True)
10
+
11
+ # CohereのAPIキーを取得
12
+ cohere_api_key = os.environ.get("COHERE_API_KEY")
13
+ client = cohere.ClientV2(api_key=cohere_api_key)
14
+
15
+ template = """
16
+ <!DOCTYPE html>
17
+ <html lang="ja">
18
+
19
+ <head>
20
+ <meta charset="UTF-8">
21
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
+ <title>Executive Report - Liquid Glass Edition</title>
23
+ <style>
24
+ :root {
25
+ --glass-bg: rgba(255, 255, 255, 0.4);
26
+ --glass-border: rgba(255, 255, 255, 0.6);
27
+ --text-main: #2d3436;
28
+ --accent-color: #6c5ce7;
29
+ }
30
+
31
+ body {
32
+ margin: 0;
33
+ padding: 0;
34
+ font-family: 'Noto Sans JP', sans-serif;
35
+ color: var(--text-main);
36
+ background-color: #f0f2f5;
37
+ display: flex;
38
+ justify-content: center;
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ /* 背景のアニメーションオーブ */
43
+ .bg-gradient {
44
+ position: fixed;
45
+ top: 0;
46
+ left: 0;
47
+ width: 100%;
48
+ height: 100%;
49
+ background: linear-gradient(120deg, #e0c3fc 0%, #8ec5fc 100%);
50
+ z-index: -2;
51
+ }
52
+
53
+ .bg-orb {
54
+ position: fixed;
55
+ border-radius: 50%;
56
+ filter: blur(80px);
57
+ z-index: -1;
58
+ opacity: 0.5;
59
+ }
60
+
61
+ .orb-1 {
62
+ width: 400px;
63
+ height: 400px;
64
+ background: #fab1a0;
65
+ top: -100px;
66
+ right: -100px;
67
+ }
68
+
69
+ .orb-2 {
70
+ width: 600px;
71
+ height: 600px;
72
+ background: #81ecec;
73
+ bottom: -200px;
74
+ left: -200px;
75
+ }
76
+
77
+ /* 共通ガラススタイル */
78
+ .glass {
79
+ background: var(--glass-bg);
80
+ backdrop-filter: blur(15px);
81
+ -webkit-backdrop-filter: blur(15px);
82
+ border: 1px solid var(--glass-border);
83
+ border-radius: 24px;
84
+ box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
85
+ margin-bottom: 24px;
86
+ }
87
+
88
+ /* メインレイアウト */
89
+ .report-wrapper {
90
+ width: 90%;
91
+ max-width: 800px;
92
+ padding: 60px 0;
93
+ z-index: 1;
94
+ }
95
+
96
+ /* 各セクションの装飾 */
97
+ .report-header {
98
+ padding: 40px;
99
+ text-align: center;
100
+ }
101
+
102
+ .badge {
103
+ display: inline-block;
104
+ padding: 4px 12px;
105
+ background: var(--accent-color);
106
+ color: white;
107
+ border-radius: 50px;
108
+ font-size: 0.8rem;
109
+ font-weight: bold;
110
+ margin-bottom: 16px;
111
+ }
112
+
113
+ h1 {
114
+ font-size: 2.5rem;
115
+ margin: 0;
116
+ line-height: 1.2;
117
+ }
118
+
119
+ .highlight {
120
+ color: var(--accent-color);
121
+ }
122
+
123
+ .meta-info {
124
+ margin-top: 20px;
125
+ font-size: 0.9rem;
126
+ opacity: 0.7;
127
+ }
128
+
129
+ .meta-info span {
130
+ margin: 0 10px;
131
+ }
132
+
133
+ /* キーメッセージ */
134
+ .key-message {
135
+ padding: 30px;
136
+ border-left: 8px solid var(--accent-color);
137
+ }
138
+
139
+ .key-message h2 {
140
+ margin-top: 0;
141
+ font-size: 1.2rem;
142
+ color: var(--accent-color);
143
+ }
144
+
145
+ /* 統計カード */
146
+ .stats-grid {
147
+ display: grid;
148
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
149
+ gap: 20px;
150
+ margin-bottom: 24px;
151
+ }
152
+
153
+ .stat-card {
154
+ padding: 24px;
155
+ text-align: center;
156
+ }
157
+
158
+ .stat-card .label {
159
+ font-size: 0.85rem;
160
+ display: block;
161
+ margin-bottom: 8px;
162
+ }
163
+
164
+ .stat-card .value {
165
+ font-size: 1.8rem;
166
+ font-weight: bold;
167
+ font-family: 'Montserrat';
168
+ }
169
+
170
+ .stat-card .trend {
171
+ font-size: 0.8rem;
172
+ margin-left: 5px;
173
+ font-weight: bold;
174
+ }
175
+
176
+ .trend.up {
177
+ color: #00b894;
178
+ }
179
+
180
+ .trend.down {
181
+ color: #d63031;
182
+ }
183
+
184
+ /* ボディテキスト */
185
+ .report-body {
186
+ padding: 40px;
187
+ line-height: 1.8;
188
+ }
189
+
190
+ ul {
191
+ padding-left: 20px;
192
+ }
193
+
194
+ li {
195
+ margin-bottom: 10px;
196
+ }
197
+
198
+ .report-footer {
199
+ text-align: center;
200
+ font-size: 0.8rem;
201
+ opacity: 0.5;
202
+ margin-top: 40px;
203
+ }
204
+
205
+ /* スマホ対応 */
206
+ @media (max-width: 600px) {
207
+ h1 {
208
+ font-size: 1.8rem;
209
+ }
210
+
211
+ .report-wrapper {
212
+ padding: 20px 0;
213
+ }
214
+ }
215
+ </style>
216
+ <link
217
+ href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;500;700&family=Montserrat:wght@600&display=swap"
218
+ rel="stylesheet">
219
+ </head>
220
+
221
+ <body>
222
+ <div class="bg-gradient"></div>
223
+ <div class="bg-orb orb-1"></div>
224
+ <div class="bg-orb orb-2"></div>
225
+
226
+ <main class="report-wrapper">
227
+ <header class="report-header glass">
228
+ <div class="badge" style="color: white;">Quarterly Report 2024</div>
229
+ <h1>マーケティング戦略 <br><span class="highlight">進捗報告書</span></h1>
230
+ <div class="meta-info">
231
+ <span>社外秘</span>
232
+ </div>
233
+ </header>
234
+
235
+ <section class="key-message glass">
236
+ <h2><span class="icon">💡</span> Key Insight</h2>
237
+ <p>
238
+ 過去3ヶ月の施策により、リード獲得コストは<strong>25%削減</strong>されました。
239
+ 次四半期は、現在のLiquid Glassデザインを導入したLPのABテストを強化し、CVRの更なる向上を目指します。
240
+ </p>
241
+ </section>
242
+
243
+ <div class="stats-grid">
244
+ <div class="stat-card glass">
245
+ <span class="label">総リード数</span>
246
+ <span class="value">1,240</span>
247
+ <span class="trend up">+12%</span>
248
+ </div>
249
+ <div class="stat-card glass">
250
+ <span class="label">CPA(獲得単価)</span>
251
+ <span class="value">¥4,200</span>
252
+ <span class="trend down">-8%</span>
253
+ </div>
254
+ <div class="stat-card glass">
255
+ <span class="label">成約率</span>
256
+ <span class="value">4.8%</span>
257
+ <span class="trend up">+1.2%</span>
258
+ </div>
259
+ </div>
260
+
261
+ <section class="report-body glass">
262
+ <h3>今後の重点施策</h3>
263
+ <p>
264
+ 市場のトレンドは「視覚的な透明感」と「信頼性」の両立に移行しています。
265
+ 本報告書で採用しているデザインコンセプトは、ユーザーに清潔感とモダンな印象を与え、滞在時間の延長に寄与します。
266
+ </p>
267
+ <ul>
268
+ <li>UI/UXの透明化による情報の階層化</li>
269
+ <li>モバイルフレンドリーなレスポンシブ対応の強化</li>
270
+ <li>インタラクティブなデータビジュアライゼーションの導入</li>
271
+ </ul>
272
+ </section>
273
+
274
+ <footer class="report-footer">
275
+ <p>&copy; 2025 RYH International. Confidential</p>
276
+ </footer>
277
+ </main>
278
+ </body>
279
+
280
+ </html>
281
+ """
282
+
283
+ meta_prompt = '''
284
+ 思考のガイドライン
285
+ 文脈の理解: ユーザーの意図、背景、制約条件を正確に把握してください。
286
+
287
+ 推論の先行: 結論を出す前に、必ず「なぜその結論に至るのか」という思考のプロセスを記述してください。
288
+
289
+ 多角的視点: 表面的な回答だけでなく、潜在的な課題や代替案、リスクについても考慮してください。
290
+
291
+ 論理的整合性: ステップ間のつながりを明確にし、矛盾がないかセルフチェックを行ってください。
292
+
293
+ 実行ステップ
294
+ 1. タスクの分解と分析
295
+ 実行すべきタスクを最小単位の要素に分解してください。
296
+
297
+ 必要な情報、使用すべきトーン、守るべきルールを明確にします。
298
+
299
+ 2. 推論と戦略立案(Reasoning)
300
+ 結論に至るまでの論理的な道筋を立ててください。
301
+
302
+ 複数のアプローチがある場合は、最も適切なものを選択した理由を明記してください。
303
+
304
+ 計算や複雑な論理が必要な場合は、ここでステップバイステップで展開してください。
305
+
306
+ 3. 検証と洗練
307
+ 生成した解決策が、すべての制約条件を満たしているか確認してください。
308
+
309
+ より簡潔に、あるいはより強力にできる部分を修正してください。
310
+
311
+ 出力形式
312
+ 以下の構造で回答してください。
313
+
314
+ 推論プロセス(Reasoning)
315
+ [タスクの分析、論理的な思考、解決までのステップを詳細に記述してください。]
316
+
317
+ 最終回答(Conclusion/Result)
318
+ [推論に基づいた最終的な成果物を出力してください。形式はタスクに応じて最適化してください(JSON、マークダウン、文章など)。]
319
+ '''
320
+
321
+ # ==========================================
322
+ # 1. 専門家分析・ガイドライン データベース (6,000文字級)
323
+ # ==========================================
324
+ ADVISORY_DB = {
325
+ "STRATEGIC_HEADER": "# 🏛️ 戦略的組織防衛・人事アドバイザリー・フルレポート\n**本レポートは、AI予測に基づき、経営心理学と労務リスク管理の観点から生成されました。**\n---\n",
326
+ "GUIDELINE": """
327
+ ## 📘 専門家による対応ガイドライン
328
+ 離職予兆を検知した際、人事が取るべき行動指針は以下の通りです。
329
+
330
+ 1. **「評価」ではなく「共感」の対話:** 離職を検討している層は、組織への不信感を抱いています。詰問ではなく「あなたの状況を心配している」というメッセージを優先してください。
331
+ 2. **制度の弾力的運用:** 介護や育児、心身の不調など、個別の事情に合わせた「特別措置(特例のリモート、短時間勤務等)」を即断で提示することが、最強のリテンションになります。
332
+ 3. **心理的安全性の担保:** 相談内容が査定に影響しないことを明確に約束し、本音を引き出してください。
333
+ """,
334
+ "FINANCIAL_IMPACT": """
335
+ ## 💰 離職に伴う経済的インパクト予測(推定損失額)
336
+ 対象者が離職した場合、組織には以下の経済的損失が発生します。
337
+
338
+ 1. **直接的損失(採用・教育費):** 年収の約35%〜50%(エージェント手数料、媒体費、面接工数)。
339
+ 2. **生産性の空白期間:** 後任が前任者と同等のパフォーマンスを発揮するまでの4〜6ヶ月間の給与相当額。
340
+ 3. **ナレッジ・ロス:** 担当顧客との関係、業務ノウハウ、およびチームの士気低下による「連鎖離職」の潜在的リスク。
341
+ **【結論】推定損失合計:年収の1.5倍〜2.2倍。** 本レポートの提言を実行することは、経営上、極めて合理的な投資です。
342
+ """
343
+ }
344
+
345
+ # ==========================================
346
+ # 2. モデルの学習(すべての入力を整数として定義)
347
+ # ==========================================
348
+ def train_mega_model():
349
+ np.random.seed(42)
350
+ # 項目名とそれぞれの整数の範囲
351
+ feature_config = {
352
+ '仕事の満足度(0-10)': (0, 10),
353
+ '上司への満足度(0-10)': (0, 10),
354
+ '月間総労働時間(h)': (140, 350),
355
+ '先月の残業時間(h)': (0, 150),
356
+ '勤続年数': (0, 40),
357
+ '昇進からの経過年数': (0, 20),
358
+ '年間研修受講時間(h)': (0, 200),
359
+ '通勤時間(片道分)': (0, 180),
360
+ 'リモートワーク比率(0-100%)': (0, 100),
361
+ '週の面談回数': (0, 10),
362
+ '給与ランク(1-10)': (1, 10),
363
+ '賞与評価(0-10)': (0, 10),
364
+ 'チームの一体感(0-10)': (0, 10),
365
+ '年齢': (20, 70),
366
+ '扶養家族数': (0, 5)
367
+ }
368
+
369
+ feature_names = list(feature_config.keys())
370
+ n_samples = 5000
371
+
372
+ # 整数データの生成
373
+ data = {f: np.random.randint(low, high + 1, n_samples) for f, (low, high) in feature_config.items()}
374
+ df = pd.DataFrame(data)
375
+
376
+ # 離職ロジック(整数値に基づいた重み付け)
377
+ def churn_logic(row):
378
+ score = 0
379
+ if row['仕事の満足度(0-10)'] <= 3: score += 0.5
380
+ if row['先月の残業時間(h)'] >= 60: score += 0.4
381
+ if row['上司への満足度(0-10)'] <= 3: score += 0.4
382
+ if row['昇進からの経過年数'] >= 5: score += 0.2
383
+ return 1 if score > 0.7 else 0
384
+
385
+ df['target'] = df.apply(churn_logic, axis=1)
386
+
387
+ X = df[feature_names]
388
+ y = df['target']
389
+ model = RandomForestClassifier(n_estimators=100, random_state=42).fit(X, y)
390
+ return model, feature_names
391
+
392
+ model, feature_names = train_mega_model()
393
+
394
+ # ==========================================
395
+ # 3. レポート生成エンジン(個別所見 & 専門家分析)
396
+ # ==========================================
397
+ def generate_individual_findings(d):
398
+ findings = "## 📋 入力データに基づく個別所見(精密分析)\n"
399
+ if d['仕事の満足度(0-10)'] <= 3:
400
+ findings += f"⚠️ **モチベーション低下:** 満足度スコアが極めて低いです({d['仕事の満足度(0-10)']}/10)。現職務への適性再確認が必要です。\n"
401
+ if d['先月の残業時間(h)'] >= 45:
402
+ findings += f"⚠️ **過重労働リスク:** 残業時間({d['先月の残業時間(h)']}h)が危険域です。メンタル不調による突発的離職を防ぐため、即時の業務調整を。\n"
403
+ if d['上司への満足度(0-10)'] <= 3:
404
+ findings += f"⚠️ **人間関係の不全:** 上司との関係({d['上司への満足度(0-10)']}/10)が最大の離職リスクです。指示系統の変更を検討してください。\n"
405
+ if d['昇進からの経過年数'] >= 5:
406
+ findings += f"⚠️ **キャリア停滞感:** 5年以上昇進がないことで、組織内での未来に限界を感じています。\n"
407
+ if d['通勤時間(片道分)'] >= 60 and d['リモートワーク比率(0-100%)'] <= 20:
408
+ findings += f"⚠️ **環境負荷:** 長距離通勤と低いリモート率が私生活を圧迫しています。\n"
409
+
410
+ if not any("⚠️" in s for s in findings.split('\n')):
411
+ findings += "✅ **良��な傾向:** 現在の入力値において、深刻な離職リスクを押し上げる単一要因は見当たりません。\n"
412
+ return findings
413
+
414
+ def generate_expert_analysis(prob):
415
+ analysis = "## 🔍 専門家による多角的分析\n"
416
+ if prob > 0.7:
417
+ analysis += "**【判定:緊急離脱状態】** 対象者は既に「心理的契約」を解除しています。引き止めよりも、まずは本音を吐き出させる「デトックス面談」を優先してください。\n"
418
+ elif prob > 0.4:
419
+ analysis += "**【判定:揺らぎ状態】** 他社からの誘いや、現状への疑問が芽生えています。今、具体的な改善策を提示すれば定着に繋がります。\n"
420
+ else:
421
+ analysis += "**【判定:安定維持】** 組織へのコミットメントは高いですが、定期的な1on1を怠らず、小さな変化を見逃さないでください。\n"
422
+ return analysis
423
+
424
+ # ==========================================
425
+ # 4. メイン関数(UserWarning解消 & レポート構築)
426
+ # ==========================================
427
+ def generate_advisory_report(*args):
428
+ # すべての入力を整数として扱う
429
+ args_int = [int(a) for a in args]
430
+ input_df = pd.DataFrame([args_int], columns=feature_names)
431
+
432
+ prob = model.predict_proba(input_df)[0][1]
433
+ d = dict(zip(feature_names, args_int))
434
+
435
+ report = ADVISORY_DB["STRATEGIC_HEADER"]
436
+ report += f"## 🏆 総合評価:離職予測確率 {prob*100:.1f}%\n\n"
437
+
438
+ report += generate_individual_findings(d)
439
+ report += generate_expert_analysis(prob)
440
+ report += ADVISORY_DB["GUIDELINE"]
441
+ report += ADVISORY_DB["FINANCIAL_IMPACT"]
442
+
443
+ full_prompt = f"{meta_prompt}に基づいて{report}を分析して、最終評価を{template}に基づいてHTML形式で出力してください。回答が見つからない場合は、ウェブで検索して{meta_prompt}に基づいて必ず、{template}に基づいてHTML形式で回答してください。"
444
+
445
+ # Cohere APIの呼び出し
446
+ response = client.chat(
447
+ model="command-a-03-2025",
448
+ messages=[
449
+ {"role": "user", "content": full_prompt}
450
+ ]
451
+ )
452
+
453
+ myresp = response.message.content[0].text
454
+ if '```html' in myresp:
455
+ part1 = myresp.find('```html')
456
+ part2 = myresp.rfind('```')
457
+ reasoning_part = myresp[:part1].strip()
458
+ html_part = myresp[part1+7:part2].strip()
459
+ else:
460
+ # Markdownタグがない場合のフォールバック
461
+ reasoning_part = "HTMLタグが見つかりませんでした。全文を表示します。"
462
+ html_part = myresp
463
+ prevbody = report + '\n\n' + reasoning_part
464
+
465
+ return prevbody, html_part, "Powered by RYH International."
466
+
467
+ # ==========================================
468
+ # 5. Gradio UI (すべて整数入力)
469
+ # ==========================================
470
+ with gr.Blocks(theme=gr.themes.Soft(), title="HR Advisory Platform", css="footer {visibility: hidden;}") as demo:
471
+ gr.Markdown("# 🏢 組織・人事評価 統合アドバイザリー (AI版)")
472
+
473
+ with gr.Row():
474
+ with gr.Column(scale=1):
475
+ gr.Markdown("### 📊 従業員データ入力")
476
+ inputs = []
477
+ for f in feature_names:
478
+ if '0-10' in f or '1-10' in f:
479
+ inputs.append(gr.Slider(0, 10, 5, step=1, label=f))
480
+ elif '0-100%' in f:
481
+ inputs.append(gr.Slider(0, 100, 20, step=1, label=f))
482
+ elif 'h' in f or '年' in f or '分' in f or '歳' in f or '数' in f or '回' in f:
483
+ inputs.append(gr.Number(value=0, precision=0, label=f))
484
+ else:
485
+ inputs.append(gr.Number(value=0, precision=0, label=f))
486
+
487
+ btn = gr.Button("🔍 統合レポートを生成", variant="primary")
488
+
489
+ with gr.Column(scale=2):
490
+ output = gr.Markdown(show_copy_button=True)
491
+ html_output = gr.HTML()
492
+ ip_right = gr.Textbox(label="IP Information", interactive=False)
493
+
494
+ btn.click(generate_advisory_report, inputs=inputs, outputs=[output, html_output, ip_right])
495
+
496
+ demo.launch(favicon_path="favicon.ico")