Spaces:
Sleeping
Sleeping
File size: 8,336 Bytes
8036226 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import gradio as gr
import pandas as pd
import numpy as np
import os
from openai import OpenAI
# --- 設定 ---
api_key = os.getenv("OPENAI_API_KEY")
# 固定ファイル名
MASTER_FILE = "master.csv"
PLM_FILE = "plm.csv"
def robust_read_csv(path, target_columns):
"""
タイトル行や空行をスキップし、特定のカラム名が含まれる行をヘッダーとして自動認識して読み込む。
"""
if not os.path.exists(path):
return None, f"ファイル '{path}' が見つかりません。"
encodings = ['cp932', 'utf-8', 'shift_jis']
for enc in encodings:
try:
# まずはヘッダーなしで全行読み込む
raw_df = pd.read_csv(path, encoding=enc, header=None, on_bad_lines='skip', engine='python')
# ターゲットとなるカラム名が含まれる行を探す
header_idx = None
for i, row in raw_df.iterrows():
row_values = [str(x) for x in row.values]
# target_columnsのいずれかが含まれている行をヘッダーとみなす
if any(col in row_values for col in target_columns):
header_idx = i
break
if header_idx is not None:
# 特定した行をヘッダーとして再読み込み
df = pd.read_csv(path, encoding=enc, skiprows=header_idx)
# カラム名に含まれる改行コードなどを除去
df.columns = [str(c).replace('\n', '').strip() for c in df.columns]
return df, None
else:
return None, f"ヘッダー行({target_columns})が見つかりませんでした。"
except UnicodeDecodeError:
continue
except Exception as e:
return None, str(e)
return None, "文字コードの判定に失敗しました。"
# データの読み込み(起動時に実行)
# 原紙マスタは「品番」、PLMは「代表銘柄」をキーにヘッダーを探索
M_DF, M_ERR = robust_read_csv(MASTER_FILE, ["品番", "親品番", "銘柄名"])
P_DF, P_ERR = robust_read_csv(PLM_FILE, ["代表銘柄", "品名", "材料名"])
def analyze_cd_logic(target_brand):
# エラーチェック
if M_DF is None: return f"❌ マスタ読み込みエラー: {M_ERR}"
if P_DF is None: return f"❌ PLMデータ読み込みエラー: {P_ERR}"
if not api_key: return "❌ OpenAI APIキーが未設定です。"
if not target_brand: return "⚠️ 銘柄名または親品番を入力してください。"
client = OpenAI(api_key=api_key)
try:
# 1. 銘柄検索ロジック (親品番、銘柄名、一般名称銘柄)
# カラム名の揺れに対応(マスタ側)
brand_mask = (M_DF['親品番'].astype(str) == target_brand) | \
(M_DF['銘柄名'].astype(str) == target_brand)
# 3列目(一般名称銘柄)も検索対象に
if M_DF.shape[1] > 2:
brand_mask |= (M_DF.iloc[:, 2].astype(str) == target_brand)
brand_data = M_DF[brand_mask]
if brand_data.empty:
return f"❌ 銘柄「{target_brand}」はマスタに見つかりません。"
spec = brand_data.iloc[0]
# 2. 製品紐付け(PLM側)
related_prods = P_DF[P_DF['代表銘柄'].astype(str) == target_brand]
prod_names = related_prods['品名'].unique()[:5] if not related_prods.empty else ["紐付けなし"]
# 3. CDロジック判定(資材1G戦略に基づく)
findings = []
# A. 共用銘柄化(メーカー集約)
all_variants = M_DF[M_DF['親品番'] == spec['親品番']].copy()
all_variants['price'] = pd.to_numeric(all_variants['仕入単価(新)'], errors='coerce')
if all_variants['製紙会社'].nunique() > 1:
p_max = all_variants['price'].max()
p_min = all_variants['price'].min()
if p_max > p_min:
findings.append(f"【共用銘柄化】同一品番内で複数メーカー混在。単価差 {p_max - p_min:.1f}円/kg。安値メーカーへの全量集約。")
# B. 環境対応(1Gナレッジ)
if str(spec.get('古紙使用フラグ')) == '1':
findings.append("【環境対応見直し】古紙配合品からFSC認証紙へのスペック変更。1G事例(▲18M/年)に基づくコスト構造の改善。")
# C. 価値の再定義(一般品化)
if '1' in [str(spec.get('特抄フラグ')), str(spec.get('特寸フラグ'))]:
findings.append("【価値の再定義】特注仕様(特抄・特寸)を廃止し、一般流通品へ集約。供給ソース固定化の解消と相見積の実施。")
# 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 "💡 明確な自動判定ロジックに該当する施策は見つかりませんでした。市場市況に基づく価格交渉、または物流商流の効率化を検討してください。"
# 4. AIによる具体的施策の肉付け
prompt = f"""
あなたは製造業の戦略調達プロフェッショナルです。以下のデータ分析結果に基づき、実務に即した「CD施策シート」を作成してください。
【対象銘柄情報】
銘柄: {spec['銘柄名']}
製紙会社: {spec['製紙会社']} / 現行単価: {spec['仕入単価(新)']}円
主要使用製品: {', '.join(prod_names)}
検出された論理: {" / ".join(findings)}
【出力ルール】
- 交渉メール案、反論対策、導入文、挨拶は一切含めないでください。
- 以下の4項目を1セットとして、各切り口ごとに鋭く、論理的なアクションを出力してください。
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(theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🛡️ CDG AI Strategist - 論理分析特化版")
gr.Markdown("不規則なCSV形式にも対応した強化版読み込みエンジンを搭載しています。")
with gr.Row():
with gr.Column(scale=1):
brand_input = gr.Textbox(label="銘柄名 / 親品番 を入力", placeholder="例: Jウメ70AC")
btn = gr.Button("CD戦略を生成", variant="primary")
# ステータス表示
if M_DF is not None:
gr.Markdown(f"✅ 原紙マスタ: {len(M_DF)}行 読み込み完了")
else:
gr.Markdown(f"❌ 原紙マスタ: {M_ERR}")
if P_DF is not None:
gr.Markdown(f"✅ PLMデータ: {len(P_DF)}行 読み込み完了")
else:
gr.Markdown(f"❌ PLMデータ: {P_ERR}")
with gr.Column(scale=2):
output = gr.Markdown(label="CD施策詳細レポート")
btn.click(fn=analyze_cd_logic, inputs=brand_input, outputs=output)
if __name__ == "__main__":
demo.launch() |