Corin1998 commited on
Commit
635b170
·
verified ·
1 Parent(s): dab75f1

Update ui/ui_app.py

Browse files
Files changed (1) hide show
  1. ui/ui_app.py +172 -143
ui/ui_app.py CHANGED
@@ -1,231 +1,260 @@
1
  # ui/ui_app.py
2
  from __future__ import annotations
3
- import json, math
4
- from typing import Dict, Any, List, Tuple
5
 
6
  import gradio as gr
7
  import plotly.graph_objects as go
8
  import pandas as pd
9
 
10
- from core.extract import parse_pdf # 既存:PDF→(fin, df, meta, log)
 
11
  from core.external_scoring import (
12
  get_external_template_df,
13
  fill_missing_with_external,
 
14
  score_external_from_df,
15
  )
16
  from core.ai_judgement import suggest_external_with_llm, ai_evaluate
17
 
18
- # ---------- 可視化ヘルパ ----------
19
- def _radar_from_categories(title: str, cat_scores: Dict[str, float]) -> go.Figure:
20
  if not cat_scores:
21
  cat_scores = {"N/A": 0.0}
22
  labels = list(cat_scores.keys())
23
- values = [cat_scores[k] for k in labels]
24
  fig = go.Figure()
25
- fig.add_trace(go.Scatterpolar(r=values+[values[0]], theta=labels+[labels[0]],
26
- fill="toself", name=title))
27
- fig.update_layout(
28
- polar=dict(radialaxis=dict(visible=True, range=[0,100])),
29
- showlegend=False, margin=dict(l=30,r=30,t=40,b=30), height=380,
30
- title=title
31
- )
32
  return fig
33
 
34
  def _diff_bar(ext: Dict[str,float], ai: Dict[str,float]) -> go.Figure:
35
- keys = sorted(set(ext.keys()) | set(ai.keys()))
36
- diffs = [(ai.get(k,0)-ext.get(k,0)) for k in keys]
37
- fig = go.Figure(data=[go.Bar(x=keys, y=diffs)])
38
- fig.update_layout(title="AI評点 - 外部評価(カテゴリ差分)", height=360,
39
- margin=dict(l=30,r=30,t=40,b=30))
40
  return fig
41
 
42
- def _format_num(x):
43
- if x is None: return "—"
44
  try:
45
- x = float(x)
46
- if abs(x)>=1e12: return f"{x/1e12:.2f}"
47
- if abs(x)>=1e8: return f"{x/1e8:.2f}"
48
- if abs(x)>=1e6: return f"{x/1e6:.2f}百万円"
49
- if abs(x)>=1e3: return f"{x/1e3:.2f}千円"
50
- return f"{x:.0f}"
51
  except Exception:
52
- return str(x)
53
 
54
- def _cards_html(company: str, meta: Dict[str,Any], fin: Dict[str,Any], ext_total: float, ai_total: float) -> str:
55
- bs = fin.get("balance_sheet", {}) or {}
56
- is_ = fin.get("income_statement", {}) or {}
57
- equity_ratio = None
58
  try:
59
- ta = float(bs.get("total_assets") or 0)
60
- te = float(bs.get("total_equity") or 0)
61
- if ta>0: equity_ratio = te/ta*100
62
  except Exception:
63
- equity_ratio = None
64
-
65
  period = ""
66
- if meta and meta.get("period"):
67
- p = meta["period"]
68
- if isinstance(p, dict):
69
- period = f"{p.get('start_date','')} ~ {p.get('end_date','')}"
70
- else:
71
- period = str(p)
72
  unit = (meta.get("unit") or "円").replace("JPY","円")
73
-
74
- html = f"""
75
  <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
76
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
77
  <div style="font-size:12px;color:#6B7280;">企業名</div>
78
  <div style="font-size:18px;font-weight:700;">{company or fin.get('company',{}).get('name','—')}</div>
79
  <div style="font-size:12px;color:#6B7280;margin-top:4px;">期間: {period or '—'} / 単位: {unit}</div>
80
  </div>
81
-
82
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
83
  <div style="font-size:12px;color:#6B7280;">売上高</div>
84
- <div style="font-size:18px;font-weight:700;">{_format_num(is_.get('sales'))}</div>
85
  </div>
86
-
87
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
88
  <div style="font-size:12px;color:#6B7280;">営業利益</div>
89
- <div style="font-size:18px;font-weight:700;">{_format_num(is_.get('operating_income'))}</div>
90
  </div>
91
-
92
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
93
  <div style="font-size:12px;color:#6B7280;">自己資本比率</div>
94
- <div style="font-size:18px;font-weight:700;">{ '—' if equity_ratio is None else f'{equity_ratio:.1f}%' }</div>
95
  </div>
96
-
97
  <div style="background:#F0FDF4;border:1px solid #DCFCE7;border-radius:12px;padding:12px;">
98
  <div style="font-size:12px;color:#047857;">外部評価(定量)</div>
99
  <div style="font-size:22px;font-weight:800;color:#065F46;">{ext_total:.1f}</div>
100
  </div>
101
-
102
  <div style="background:#EFF6FF;border:1px solid #DBEAFE;border-radius:12px;padding:12px;">
103
  <div style="font-size:12px;color:#1D4ED8;">AI評点</div>
104
  <div style="font-size:22px;font-weight:800;color:#1E40AF;">{ai_total:.1f}</div>
105
  </div>
106
  </div>
107
  """
108
- return html
109
 
110
- # ---------- 解析フロー ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  def on_analyze(company: str, use_vision: bool, files: List[str]):
112
  if not files:
113
  raise gr.Error("PDF をアップロードしてください。")
114
-
115
- # PDF→財務JSON/抽出DF/メタ/ログ
116
  fin, df_fin, meta, log = parse_pdf(files, company, use_vision)
117
 
118
- # 外部入力テンプレ & LLMで不足を提案
119
  ext_df = get_external_template_df()
120
- suggestions = suggest_external_with_llm(fin, company)
121
- ext_df = fill_missing_with_external(ext_df, suggestions)
122
 
123
- # 外部スコア
 
 
124
  ext_res = score_external_from_df(ext_df)
125
-
126
- # AI評点(基準分離)
127
- # ext_dfから必要要素だけピック(市場成長率/商品系)
128
- ext_like = {}
129
- for k in ["市場の年成長率(%)", "主力商品数", "成長中主力商品数"]:
130
- try:
131
- v = float(ext_df.loc[ext_df["入力項目"].eq(k), "値"].values[0])
132
- ext_like[k] = v
133
- except Exception:
134
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  ai_res = ai_evaluate(fin, ext_like)
136
 
137
- # 表示物
138
- cards_html = _cards_html(company, meta, fin, ext_res["external_total"], ai_res["ai_total"])
139
- ext_fig = _radar_from_categories("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
140
- ai_fig = _radar_from_categories("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
141
- diff_fig = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
142
 
143
- # JSON(詳細で見る用)
144
- fin_json = json.dumps(fin, ensure_ascii=False, indent=2)
145
- ext_json = json.dumps(ext_res, ensure_ascii=False, indent=2)
146
- ai_json = json.dumps(ai_res, ensure_ascii=False, indent=2)
 
 
147
 
148
- return cards_html, df_fin, ext_df, ext_fig, ai_fig, diff_fig, ai_res.get("memo",""), fin_json, ext_json, ai_json, log
149
-
150
- def on_rescore_external(ext_df: pd.DataFrame, fin_json: str, company: str):
151
  fin = json.loads(fin_json)
152
- ext_res = score_external_from_df(ext_df)
153
- ext_fig = _radar_from_categories("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
154
- return ext_res["external_total"], ext_fig, json.dumps(ext_res, ensure_ascii=False, indent=2)
155
-
156
- def on_rescore_ai(ext_df: pd.DataFrame, fin_json: str):
157
- fin = json.loads(fin_json)
158
- # ext_dfから市場成長率/商品系を再取得
159
- ext_like = {}
160
- for k in ["市場の年成長率(%)", "主力商品数", "成長中主力商品数"]:
161
- try:
162
- v = float(ext_df.loc[ext_df["入力項目"].eq(k), "値"].values[0])
163
- ext_like[k] = v
164
- except Exception:
165
- pass
166
  ai_res = ai_evaluate(fin, ext_like)
167
- ai_fig = _radar_from_categories("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
168
- return ai_res["ai_total"], ai_fig, json.dumps(ai_res, ensure_ascii=False, indent=2), ai_res.get("memo","")
169
 
170
- # ---------- UI ----------
 
 
 
 
 
 
 
 
 
 
171
  def build_ui():
172
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo"), analytics_enabled=False) as demo:
173
- gr.Markdown("## 🧮 企業スコアリング(PDF解析 × 外部定量 × AI評点)")
174
 
175
  with gr.Row():
176
  with gr.Column(scale=1):
177
- company = gr.Textbox(label="企業名(任意)", placeholder="例:株式会社OO")
178
- use_vision = gr.Checkbox(value=True, label="OpenAI Visionで表・数値を補完")
179
  files = gr.File(label="決算書PDF(複数可)", file_count="multiple", type="filepath")
180
- run_btn = gr.Button("📄 解析する", variant="primary")
181
- gr.Markdown("※ JSONは下部の「詳細」に折りたたみ表示します。")
182
-
183
  with gr.Column(scale=2):
184
  cards = gr.HTML(label="サマリー")
185
 
186
- with gr.Tabs():
187
- with gr.Tab("財務・外部入力"):
188
- with gr.Row():
189
- df_fin = gr.Dataframe(label="抽出テーブル(編集可)", interactive=True, wrap=True)
190
- with gr.Row():
191
- ext_df = gr.Dataframe(label="外部入力(編集可)", interactive=True, wrap=True)
192
-
193
- with gr.Tab("スコア"):
194
- with gr.Row():
195
- ext_plot = gr.Plot(label="外部評価(レーダー)")
196
- ai_plot = gr.Plot(label="AI評点(レーダー)")
197
- diff_plot = gr.Plot(label="差分(棒)")
198
- with gr.Row():
199
- ext_total = gr.Number(label="外部評価 合計(0-100)", value=0, precision=1, interactive=False)
200
- ai_total = gr.Number(label="AI評点 合計(0-100)", value=0, precision=1, interactive=False)
201
- ai_memo = gr.Markdown(label="AI短評")
202
-
203
- with gr.Tab("詳細"):
204
- fin_json = gr.Code(label="抽出JSON", language="json")
205
- ext_json = gr.Code(label="外部評価JSON", language="json")
206
- ai_json = gr.Code(label="AI評点JSON", language="json")
207
- debug_out = gr.Textbox(label="ログ", lines=8)
208
-
209
- # 動作
 
 
 
 
 
 
 
 
 
 
 
210
  run_btn.click(
211
  on_analyze,
212
  inputs=[company, use_vision, files],
213
- outputs=[cards, df_fin, ext_df, ext_plot, ai_plot, diff_plot, ai_memo, fin_json, ext_json, ai_json, debug_out],
 
 
 
 
 
 
 
214
  )
215
 
216
- # 外部再スコア / AI再スコア(外部入力編集後)
217
- rescore_ext = gr.Button("🔁 外部評価を再計算")
218
- rescore_ai = gr.Button("🔁 AI評点を再計算")
219
-
220
- rescore_ext.click(
221
- on_rescore_external,
222
- inputs=[ext_df, fin_json, company],
223
- outputs=[ext_total, ext_plot, ext_json],
224
- )
225
- rescore_ai.click(
226
- on_rescore_ai,
227
- inputs=[ext_df, fin_json],
228
- outputs=[ai_total, ai_plot, ai_json, ai_memo],
229
  )
230
 
231
  return demo
 
1
  # ui/ui_app.py
2
  from __future__ import annotations
3
+ import json
4
+ from typing import Dict, Any, List
5
 
6
  import gradio as gr
7
  import plotly.graph_objects as go
8
  import pandas as pd
9
 
10
+ from core.extract import parse_pdf
11
+ from core.market_infer import infer_market_metrics
12
  from core.external_scoring import (
13
  get_external_template_df,
14
  fill_missing_with_external,
15
+ merge_market_into_external_df,
16
  score_external_from_df,
17
  )
18
  from core.ai_judgement import suggest_external_with_llm, ai_evaluate
19
 
20
+ # ---------- chart helpers ----------
21
+ def _radar(title: str, cat_scores: Dict[str, float]) -> go.Figure:
22
  if not cat_scores:
23
  cat_scores = {"N/A": 0.0}
24
  labels = list(cat_scores.keys())
25
+ vals = [cat_scores[k] for k in labels]
26
  fig = go.Figure()
27
+ fig.add_trace(go.Scatterpolar(r=vals+[vals[0]], theta=labels+[labels[0]], fill="toself", name=title))
28
+ fig.update_layout(polar=dict(radialaxis=dict(visible=True, range=[0,100])),
29
+ showlegend=False, height=360, margin=dict(l=30,r=30,t=40,b=30), title=title)
 
 
 
 
30
  return fig
31
 
32
  def _diff_bar(ext: Dict[str,float], ai: Dict[str,float]) -> go.Figure:
33
+ ks = sorted(set(ext.keys()) | set(ai.keys()))
34
+ diffs = [(ai.get(k,0)-ext.get(k,0)) for k in ks]
35
+ fig = go.Figure(data=[go.Bar(x=ks, y=diffs)])
36
+ fig.update_layout(title="AI評点 - 外部評価(カテゴリ差分)", height=320, margin=dict(l=30,r=30,t=40,b=30))
 
37
  return fig
38
 
39
+ def _fmt(x):
 
40
  try:
41
+ f = float(x)
42
+ if abs(f) >= 1e8: return f"{f/1e8:.2f}"
43
+ if abs(f) >= 1e6: return f"{f/1e6:.2f}百万円"
44
+ if abs(f) >= 1e3: return f"{f/1e3:.1f}"
45
+ return f"{f:.0f}"
 
46
  except Exception:
47
+ return str(x) if x not in (None,"") else "—"
48
 
49
+ def _cards(company, meta, fin, ext_total, ai_total) -> str:
50
+ bs = fin.get("balance_sheet", {}) or {}; is_ = fin.get("income_statement", {}) or {}
51
+ ta = bs.get("total_assets") or 0; te = bs.get("total_equity") or 0
52
+ er = ""
53
  try:
54
+ ta = float(ta); te = float(te); er = f"{(te/ta*100):.1f}%" if ta>0 else "—"
 
 
55
  except Exception:
56
+ er = "—"
 
57
  period = ""
58
+ if meta and isinstance(meta.get("period"), dict):
59
+ period = f"{meta['period'].get('start_date','')} ~ {meta['period'].get('end_date','')}"
 
 
 
 
60
  unit = (meta.get("unit") or "円").replace("JPY","円")
61
+ return f"""
 
62
  <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;">
63
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
64
  <div style="font-size:12px;color:#6B7280;">企業名</div>
65
  <div style="font-size:18px;font-weight:700;">{company or fin.get('company',{}).get('name','—')}</div>
66
  <div style="font-size:12px;color:#6B7280;margin-top:4px;">期間: {period or '—'} / 単位: {unit}</div>
67
  </div>
 
68
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
69
  <div style="font-size:12px;color:#6B7280;">売上高</div>
70
+ <div style="font-size:18px;font-weight:700;">{_fmt(is_.get('sales'))}</div>
71
  </div>
 
72
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
73
  <div style="font-size:12px;color:#6B7280;">営業利益</div>
74
+ <div style="font-size:18px;font-weight:700;">{_fmt(is_.get('operating_income'))}</div>
75
  </div>
 
76
  <div style="background:#F8FAFF;border:1px solid #E5E7EB;border-radius:12px;padding:12px;">
77
  <div style="font-size:12px;color:#6B7280;">自己資本比率</div>
78
+ <div style="font-size:18px;font-weight:700;">{er}</div>
79
  </div>
 
80
  <div style="background:#F0FDF4;border:1px solid #DCFCE7;border-radius:12px;padding:12px;">
81
  <div style="font-size:12px;color:#047857;">外部評価(定量)</div>
82
  <div style="font-size:22px;font-weight:800;color:#065F46;">{ext_total:.1f}</div>
83
  </div>
 
84
  <div style="background:#EFF6FF;border:1px solid #DBEAFE;border-radius:12px;padding:12px;">
85
  <div style="font-size:12px;color:#1D4ED8;">AI評点</div>
86
  <div style="font-size:22px;font-weight:800;color:#1E40AF;">{ai_total:.1f}</div>
87
  </div>
88
  </div>
89
  """
 
90
 
91
+ # ---------- core flows ----------
92
+ def _market_df_from_dict(d: Dict[str, Any]) -> pd.DataFrame:
93
+ rows = []
94
+ order = [
95
+ "市場の年成長率(%)","市場成熟度(0-1)","競争強度(0-10)","参入障壁(0-10)","価格決定力(0-10)",
96
+ "サイクル感応度(0-10)","規制リスク(0-10)","技術破壊リスク(0-10)","TAM_億円","SAM_億円","SOM_億円"
97
+ ]
98
+ for k in order:
99
+ rows.append([k, d.get(k,"")])
100
+ return pd.DataFrame(rows, columns=["指標","値"])
101
+
102
+ def _dict_from_market_df(df: pd.DataFrame) -> Dict[str, Any]:
103
+ out = {}
104
+ for _, r in df.iterrows():
105
+ k = str(r["指標"]); v = r["値"]
106
+ try:
107
+ out[k] = float(v)
108
+ except Exception:
109
+ out[k] = None
110
+ return out
111
+
112
  def on_analyze(company: str, use_vision: bool, files: List[str]):
113
  if not files:
114
  raise gr.Error("PDF をアップロードしてください。")
 
 
115
  fin, df_fin, meta, log = parse_pdf(files, company, use_vision)
116
 
117
+ # 外部入力テンプレ+不足項目のLLMサジェスト(従来どおり)
118
  ext_df = get_external_template_df()
119
+ ext_df = fill_missing_with_external(ext_df, suggest_external_with_llm(fin, company))
 
120
 
121
+ # 初期(空の市場DF)
122
+ market_df = _market_df_from_dict({})
123
+ # 外部/AIは市場未反映のまま仮計算(UI起動のため)
124
  ext_res = score_external_from_df(ext_df)
125
+ ai_res = ai_evaluate(fin, {})
126
+
127
+ cards = _cards(company, meta, fin, ext_res["external_total"], ai_res["ai_total"])
128
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
129
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
130
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
131
+
132
+ return (cards, df_fin, ext_df, market_df,
133
+ ext_res["external_total"], ai_res["ai_total"],
134
+ ext_fig, ai_fig, diff,
135
+ json.dumps(fin, ensure_ascii=False, indent=2),
136
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
137
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
138
+ "\n".join([str(x) for x in (log if isinstance(log,list) else [log])]))
139
+
140
+ def on_market_infer(industry: str, products_text: str, country: str, horizon: int,
141
+ ext_df: pd.DataFrame, fin_json: str):
142
+ prods = [p.strip() for p in (products_text or "").splitlines() if p.strip()]
143
+ market = infer_market_metrics(industry, prods, country, horizon)
144
+ market_df = _market_df_from_dict(market)
145
+
146
+ # ext_dfに市場推定を統合
147
+ ext_df2 = merge_market_into_external_df(ext_df, market, prods)
148
+
149
+ # スコア更新
150
+ fin = json.loads(fin_json)
151
+ ext_res = score_external_from_df(ext_df2)
152
+ # AIは市場の“別軸”影響を軽めに(成長余地構成で反映)
153
+ ext_like = {"市場の年成長率(%)": market.get("市場の年成長率(%)"),
154
+ "主力商品数": len(prods),
155
+ "成長中主力商品数": sum(1 for p in prods if (market.get("製品別年成長率(%)",{}).get(p,0) or 0)>10)}
156
  ai_res = ai_evaluate(fin, ext_like)
157
 
158
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
159
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
160
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
 
 
161
 
162
+ return (market_df, ext_df2,
163
+ ext_res["external_total"], ai_res["ai_total"],
164
+ ext_fig, ai_fig, diff,
165
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
166
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
167
+ "市場推定OK: " + "; ".join(market.get("注記", [])[:3]))
168
 
169
+ def on_rescore_all(ext_df: pd.DataFrame, market_df: pd.DataFrame, fin_json: str, products_text: str):
 
 
170
  fin = json.loads(fin_json)
171
+ prods = [p.strip() for p in (products_text or "").splitlines() if p.strip()]
172
+ market = _dict_from_market_df(market_df)
173
+ # ext_dfへ反映(手で編集されたmarket_dfも取り込む)
174
+ ext_df2 = merge_market_into_external_df(ext_df, market, prods)
175
+
176
+ ext_res = score_external_from_df(ext_df2)
177
+ ext_like = {"市場の年成長率(%)": market.get("市場の年成長率(%)"),
178
+ "主力商品数": len(prods),
179
+ "成長中主力商品数": sum(1 for p in prods if (market.get("製品別年成長率(%)",{}).get(p,0) or 0)>10)}
 
 
 
 
 
180
  ai_res = ai_evaluate(fin, ext_like)
 
 
181
 
182
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
183
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
184
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
185
+
186
+ return (ext_df2,
187
+ ext_res["external_total"], ai_res["ai_total"],
188
+ ext_fig, ai_fig, diff,
189
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
190
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
191
+ "再計算完了")
192
+
193
  def build_ui():
194
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo"), analytics_enabled=False) as demo:
195
+ gr.Markdown("## 🧮 企業スコアリング:PDF抽出 × 市場推定(LLM)× 外部定量 × AI評点")
196
 
197
  with gr.Row():
198
  with gr.Column(scale=1):
199
+ company = gr.Textbox(label="企業名(任意)")
200
+ use_vision = gr.Checkbox(value=True, label="OpenAI VisionでPDF表を補完")
201
  files = gr.File(label="決算書PDF(複数可)", file_count="multiple", type="filepath")
202
+ run_btn = gr.Button("📄 PDFを解析", variant="primary")
 
 
203
  with gr.Column(scale=2):
204
  cards = gr.HTML(label="サマリー")
205
 
206
+ with gr.Tab("入力/市場推定"):
207
+ with gr.Row():
208
+ industry = gr.Textbox(label="事業領域(業界・カテゴリ)", placeholder="例)ヘルスケアIT / 産業ロボット 等")
209
+ products = gr.Textbox(label="主力商品(1行1件)", lines=4, placeholder="製品A\n製品B\n…")
210
+ with gr.Row():
211
+ country = gr.Dropdown(choices=["JP","US","EU","APAC","GLOBAL"], value="JP", label="対象地域")
212
+ horizon = gr.Slider(1, 7, value=3, step=1, label="予測年数")
213
+ infer_btn = gr.Button("🔎 市場を推定(LLM)", variant="secondary")
214
+
215
+ market_df = gr.Dataframe(label="市場メトリクス(編集可)", interactive=True, wrap=True)
216
+
217
+ with gr.Tab("外部入力/財務"):
218
+ df_fin = gr.Dataframe(label="抽出テーブル(編集可)", interactive=True, wrap=True)
219
+ ext_df = gr.Dataframe(label="外部入力(編集可)", interactive=True, wrap=True)
220
+
221
+ with gr.Tab("スコア"):
222
+ with gr.Row():
223
+ ext_total = gr.Number(label="外部評価 合計(0-100)", value=0, precision=1, interactive=False)
224
+ ai_total = gr.Number(label="AI評点 合計(0-100)", value=0, precision=1, interactive=False)
225
+ with gr.Row():
226
+ ext_plot = gr.Plot(label="外部評価(レーダー)")
227
+ ai_plot = gr.Plot(label="AI評点(レーダー)")
228
+ diff_plot = gr.Plot(label="差分(棒)")
229
+ rescore_btn = gr.Button("🔁 すべて再計算", variant="secondary")
230
+
231
+ with gr.Tab("詳細"):
232
+ fin_json = gr.Code(label="抽出JSON", language="json")
233
+ ext_json = gr.Code(label="外部評価JSON", language="json")
234
+ ai_json = gr.Code(label="AI評点JSON", language="json")
235
+ debug = gr.Textbox(label="ログ", lines=8)
236
+
237
+ # state
238
+ fin_state = gr.State("")
239
+
240
+ # wire
241
  run_btn.click(
242
  on_analyze,
243
  inputs=[company, use_vision, files],
244
+ outputs=[cards, df_fin, ext_df, market_df, ext_total, ai_total, ext_plot, ai_plot, diff_plot,
245
+ fin_json, ext_json, ai_json, debug],
246
+ ).then(lambda x: x, inputs=[fin_json], outputs=[fin_state])
247
+
248
+ infer_btn.click(
249
+ on_market_infer,
250
+ inputs=[industry, products, country, horizon, ext_df, fin_state],
251
+ outputs=[market_df, ext_df, ext_total, ai_total, ext_plot, ai_plot, diff_plot, ext_json, ai_json, debug],
252
  )
253
 
254
+ rescore_btn.click(
255
+ on_rescore_all,
256
+ inputs=[ext_df, market_df, fin_state, products],
257
+ outputs=[ext_df, ext_total, ai_total, ext_plot, ai_plot, diff_plot, ext_json, ai_json, debug],
 
 
 
 
 
 
 
 
 
258
  )
259
 
260
  return demo