Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| from openai import OpenAI | |
| # --- OpenAI設定 --- | |
| api_key = os.getenv("OPENAI_API_KEY") | |
| def analyze_cd_logic(m_file, p_file, target_brand): | |
| if not api_key: | |
| return "❌ OpenAI APIキーが設定されていません。Settings > Secretsを確認してください。" | |
| if m_file is None or p_file is None or not target_brand: | |
| return "⚠️ 原紙マスタ、製品マップの両方をアップロードし、銘柄名を入力してください。" | |
| client = OpenAI(api_key=api_key) | |
| try: | |
| # データ読み込み | |
| m_df = pd.read_csv(m_file.name) | |
| if '1' in m_df.columns: | |
| m_df.columns = m_df.iloc[0] | |
| m_df = m_df[1:].reset_index(drop=True) | |
| p_df = pd.read_csv(p_file.name) | |
| if '基礎情報' in p_df.columns: | |
| p_df.columns = p_df.iloc[0] | |
| p_df = p_df[1:].reset_index(drop=True) | |
| # 1. 銘柄特定ロジック | |
| mask = (m_df['親品番'].astype(str) == target_brand) | \ | |
| (m_df['銘柄名'].astype(str) == target_brand) | \ | |
| (m_df.iloc[:, 2].astype(str) == target_brand) | |
| brand_data = m_df[mask] | |
| if brand_data.empty: | |
| return f"❌ 銘柄「{target_brand}」は見つかりませんでした。" | |
| spec = brand_data.iloc[0] | |
| findings = [] | |
| # --- ロジック抽出 --- | |
| # A. 共用銘柄化(単価差) | |
| all_same_parent = m_df[m_df['親品番'] == spec['親品番']].copy() | |
| all_same_parent['price'] = pd.to_numeric(all_same_parent['仕入単価(新)'], errors='coerce') | |
| if all_same_parent['製紙会社'].nunique() > 1: | |
| findings.append(f"【共用銘柄化】同一品番内で単価乖離あり(最大差:{all_same_parent['price'].max() - all_same_parent['price'].min()}円)。安値メーカーへの集約。") | |
| # B. 環境対応(1Gナレッジ) | |
| if str(spec.get('古紙使用フラグ')) == '1': | |
| findings.append("【環境対応見直し】古紙配合品からFSC認証紙へのスペック変更。1G成功事例(▲18M)に基づくコストダウン。") | |
| # C. 一般品化 | |
| if '1' in [str(spec.get('特抄フラグ')), str(spec.get('特寸フラグ'))]: | |
| findings.append("【価値の再定義】特抄・特寸仕様を廃止し、汎用JIS規格品へ切り替え。競争環境の再構築。") | |
| # D. 坪量適正化 | |
| gram = pd.to_numeric(spec['坪量 g/㎡'], errors='coerce') | |
| if not np.isnan(gram) and gram > 70: | |
| findings.append(f"【仕様変更】現行{gram}g/㎡。製品要件を再定義し薄物化による面積単価低減。") | |
| if not findings: | |
| return "💡 明確な自動判定ロジックに該当する施策が見つかりませんでした。個別交渉を検討してください。" | |
| # OpenAIによる詳細化 | |
| prompt = f""" | |
| あなたは調達戦略スペシャリストです。以下の分析結果を、実務的な「CD施策シート」に変換してください。 | |
| 交渉メール、挨拶、反論対策は一切不要です。論理性と具体的アクションのみを出力してください。 | |
| 【対象銘柄】: {target_brand} ({spec['製紙会社']} / {spec['仕入単価(新)']}円) | |
| 【抽出された事実】: | |
| {"/".join(findings)} | |
| 【出力項目】 | |
| 各項目について以下を記載: | |
| 1. 切り口 | |
| 2. 具体的な方法 | |
| 3. 現状の課題(データ根拠) | |
| 4. 提案内容と期待効果(具体的アクション) | |
| """ | |
| response = client.chat.completions.create( | |
| model="gpt-4o", | |
| messages=[{"role": "system", "content": "論理的な調達分析官として振る舞ってください。"}, | |
| {"role": "user", "content": prompt}], | |
| temperature=0.3 | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| return f"解析エラー: {str(e)}" | |
| # --- Gradio UI --- | |
| with gr.Blocks(title="CDG AI Strategist") as demo: | |
| gr.Markdown("# 🛡️ CDG AI Strategist - 資材1G論理分析") | |
| gr.Markdown("原紙マスタと製品マップから、利益貢献の切り口に基づいた具体的CD案を生成します。") | |
| with gr.Row(): | |
| with gr.Column(): | |
| m_input = gr.File(label="1. 原紙マスタ(CSV)をアップロード") | |
| p_input = gr.File(label="2. 製品マップ(CSV)をアップロード") | |
| brand_input = gr.Textbox(label="3. 分析対象の銘柄名を入力", placeholder="例: Jウメ70AC") | |
| btn = gr.Button("分析実行", variant="primary") | |
| with gr.Column(): | |
| output = gr.Markdown(label="CD施策詳細レポート") | |
| btn.click(fn=analyze_cd_logic, inputs=[m_input, p_input, brand_input], outputs=output) | |
| # HuggingFace用の起動設定 | |
| if __name__ == "__main__": | |
| demo.launch() |