Corin1998 commited on
Commit
6ce15c9
·
verified ·
1 Parent(s): 412434a

Update ui/ui_app.py

Browse files
Files changed (1) hide show
  1. ui/ui_app.py +142 -100
ui/ui_app.py CHANGED
@@ -1,7 +1,7 @@
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
@@ -17,23 +17,24 @@ from core.external_scoring import (
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):
@@ -44,16 +45,17 @@ def _fmt(x):
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','')}"
@@ -88,7 +90,7 @@ def _cards(company, meta, fin, ext_total, ai_total) -> str:
88
  </div>
89
  """
90
 
91
- # ---------- core flows ----------
92
  def _market_df_from_dict(d: Dict[str, Any]) -> pd.DataFrame:
93
  rows = []
94
  order = [
@@ -101,94 +103,136 @@ def _market_df_from_dict(d: Dict[str, Any]) -> pd.DataFrame:
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:
@@ -211,7 +255,6 @@ def build_ui():
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("外部入力/財務"):
@@ -220,8 +263,8 @@ def build_ui():
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評点(レーダー)")
@@ -241,8 +284,8 @@ def build_ui():
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(
@@ -256,5 +299,4 @@ def build_ui():
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
 
1
  # ui/ui_app.py
2
  from __future__ import annotations
3
  import json
4
+ from typing import Dict, Any, List, Tuple
5
 
6
  import gradio as gr
7
  import plotly.graph_objects as go
 
17
  )
18
  from core.ai_judgement import suggest_external_with_llm, ai_evaluate
19
 
20
+ # ---------- charts ----------
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 = [float(cat_scores[k] or 0.0) 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 = [(float(ai.get(k, 0.0)) - float(ext.get(k, 0.0))) for k in ks]
35
  fig = go.Figure(data=[go.Bar(x=ks, y=diffs)])
36
+ fig.update_layout(title="AI評点 - 外部評価(カテゴリ差分)",
37
+ height=320, margin=dict(l=30, r=30, t=40, b=30))
38
  return fig
39
 
40
  def _fmt(x):
 
45
  if abs(f) >= 1e3: return f"{f/1e3:.1f}千"
46
  return f"{f:.0f}"
47
  except Exception:
48
+ return str(x) if x not in (None, "") else "—"
49
 
50
  def _cards(company, meta, fin, ext_total, ai_total) -> str:
51
  bs = fin.get("balance_sheet", {}) or {}; is_ = fin.get("income_statement", {}) or {}
52
  ta = bs.get("total_assets") or 0; te = bs.get("total_equity") or 0
53
+ er = ""
54
  try:
55
+ ta = float(ta); te = float(te)
56
+ er = f"{(te/ta*100):.1f}%" if ta>0 else "—"
57
  except Exception:
58
+ pass
59
  period = ""
60
  if meta and isinstance(meta.get("period"), dict):
61
  period = f"{meta['period'].get('start_date','')} ~ {meta['period'].get('end_date','')}"
 
90
  </div>
91
  """
92
 
93
+ # ---------- market df helpers ----------
94
  def _market_df_from_dict(d: Dict[str, Any]) -> pd.DataFrame:
95
  rows = []
96
  order = [
 
103
 
104
  def _dict_from_market_df(df: pd.DataFrame) -> Dict[str, Any]:
105
  out = {}
106
+ try:
107
+ for _, r in df.iterrows():
108
+ k = str(r["指標"]); v = r["値"]
109
+ try:
110
+ out[k] = float(v)
111
+ except Exception:
112
+ out[k] = None
113
+ except Exception:
114
+ # 列崩れ対策:空dictで返す
115
+ return {}
116
  return out
117
 
118
+ # ---------- flows ----------
119
  def on_analyze(company: str, use_vision: bool, files: List[str]):
120
+ """
121
+ 例外はcatchしてUIに出す常に所定の型で返す。
122
+ """
123
+ try:
124
+ if not files:
125
+ raise RuntimeError("PDF をアップロードしてください。")
126
+ fin, df_fin, meta, log = parse_pdf(files, company, use_vision)
127
+
128
+ # 外部テンプレ & 埋め候補
129
+ ext_df = get_external_template_df()
130
+ ext_df = fill_missing_with_external(ext_df, suggest_external_with_llm(fin, company))
131
+
132
+ # 市場(空の表)/ 仮スコア
133
+ market_df = _market_df_from_dict({})
134
+ ext_res = score_external_from_df(ext_df)
135
+ ai_res = ai_evaluate(fin, {})
136
+
137
+ cards = _cards(company, meta, fin, ext_res["external_total"], ai_res["ai_total"])
138
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
139
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
140
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
141
+
142
+ return (cards, df_fin, ext_df, market_df,
143
+ float(ext_res["external_total"]), float(ai_res["ai_total"]),
144
+ ext_fig, ai_fig, diff,
145
+ json.dumps(fin, ensure_ascii=False, indent=2),
146
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
147
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
148
+ "\n".join([str(x) for x in (log if isinstance(log,list) else [log])]))
149
+ except Exception as e:
150
+ # 失敗してもUIが壊れないように空プレースホルダで返す
151
+ empty_df = pd.DataFrame(columns=["カテゴリー","入力項目","値"])
152
+ return (
153
+ f"<div style='color:#b91c1c'>解析に失敗: {e}</div>",
154
+ pd.DataFrame(columns=["category","item","value"]),
155
+ empty_df,
156
+ _market_df_from_dict({}),
157
+ 0.0, 0.0,
158
+ _radar("外部評価(カテゴリ)", {}),
159
+ _radar("AI評点(カテゴリ)", {}),
160
+ _diff_bar({}, {}),
161
+ "{}", "{}","{}",
162
+ f"TRACE: {type(e).__name__}: {e}"
163
+ )
164
 
165
  def on_market_infer(industry: str, products_text: str, country: str, horizon: int,
166
  ext_df: pd.DataFrame, fin_json: str):
167
+ try:
168
+ prods = [p.strip() for p in (products_text or "").splitlines() if p.strip()]
169
+ market = infer_market_metrics(industry or "", prods, country or "JP", int(horizon or 3))
170
+ market_df = _market_df_from_dict(market)
171
+
172
+ # ext_dfに市場推定を統合
173
+ ext_df2 = merge_market_into_external_df(ext_df, market, prods)
174
+
175
+ # スコア更新
176
+ fin = json.loads(fin_json or "{}")
177
+ ext_res = score_external_from_df(ext_df2)
178
+
179
+ ext_like = {
180
+ "市場の年成長率(%)": market.get("市場の年成長率(%)"),
181
+ "主力商品数": len(prods),
182
+ "成長中主力商品数": sum(1 for p in prods if (market.get("製品別年成長率(%)",{}).get(p,0) or 0)>10)
183
+ }
184
+ ai_res = ai_evaluate(fin, ext_like)
185
+
186
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
187
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
188
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
189
+
190
+ return (market_df, ext_df2,
191
+ float(ext_res["external_total"]), float(ai_res["ai_total"]),
192
+ ext_fig, ai_fig, diff,
193
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
194
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
195
+ "市場推定OK: " + "; ".join(market.get("注記", [])[:3]))
196
+ except Exception as e:
197
+ return (_market_df_from_dict({}),
198
+ ext_df,
199
+ 0.0, 0.0,
200
+ _radar("外部評価(カテゴリ)", {}),
201
+ _radar("AI評点(カテゴリ)", {}),
202
+ _diff_bar({}, {}),
203
+ "{}", "{}", f"市場推定に失敗: {e}")
204
 
205
  def on_rescore_all(ext_df: pd.DataFrame, market_df: pd.DataFrame, fin_json: str, products_text: str):
206
+ try:
207
+ fin = json.loads(fin_json or "{}")
208
+ prods = [p.strip() for p in (products_text or "").splitlines() if p.strip()]
209
+ market = _dict_from_market_df(market_df)
210
+ ext_df2 = merge_market_into_external_df(ext_df, market, prods)
211
+
212
+ ext_res = score_external_from_df(ext_df2)
213
+ ext_like = {
214
+ "市場の年成長率(%)": market.get("市場の年成長率(%)"),
215
+ "主力商品数": len(prods),
216
+ "成長中主力商品数": sum(1 for p in prods if (market.get("製品別年成長率(%)",{}).get(p,0) or 0)>10)
217
+ }
218
+ ai_res = ai_evaluate(fin, ext_like)
219
+
220
+ ext_fig = _radar("外部評価(カテゴリ)", ext_res.get("category_scores", {}))
221
+ ai_fig = _radar("AI評点(カテゴリ)", ai_res.get("category_scores", {}))
222
+ diff = _diff_bar(ext_res.get("category_scores", {}), ai_res.get("category_scores", {}))
223
+
224
+ return (ext_df2,
225
+ float(ext_res["external_total"]), float(ai_res["ai_total"]),
226
+ ext_fig, ai_fig, diff,
227
+ json.dumps(ext_res, ensure_ascii=False, indent=2),
228
+ json.dumps(ai_res, ensure_ascii=False, indent=2),
229
+ "再計算完了")
230
+ except Exception as e:
231
+ return (ext_df, 0.0, 0.0,
232
+ _radar("外部評価(カテゴリ)", {}),
233
+ _radar("AI評点(カテゴリ)", {}),
234
+ _diff_bar({}, {}),
235
+ "{}", "{}", f"再計算に失敗: {e}")
236
 
237
  def build_ui():
238
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="indigo"), analytics_enabled=False) as demo:
 
255
  country = gr.Dropdown(choices=["JP","US","EU","APAC","GLOBAL"], value="JP", label="対象地域")
256
  horizon = gr.Slider(1, 7, value=3, step=1, label="予測年数")
257
  infer_btn = gr.Button("🔎 市場を推定(LLM)", variant="secondary")
 
258
  market_df = gr.Dataframe(label="市場メトリクス(編集可)", interactive=True, wrap=True)
259
 
260
  with gr.Tab("外部入力/財務"):
 
263
 
264
  with gr.Tab("スコア"):
265
  with gr.Row():
266
+ ext_total = gr.Number(label="外部評価 合計(0-100)", value=0.0, precision=1, interactive=False)
267
+ ai_total = gr.Number(label="AI評点 合計(0-100)", value=0.0, precision=1, interactive=False)
268
  with gr.Row():
269
  ext_plot = gr.Plot(label="外部評価(レーダー)")
270
  ai_plot = gr.Plot(label="AI評点(レーダー)")
 
284
  run_btn.click(
285
  on_analyze,
286
  inputs=[company, use_vision, files],
287
+ outputs=[cards, df_fin, ext_df, market_df, ext_total, ai_total,
288
+ ext_plot, ai_plot, diff_plot, fin_json, ext_json, ai_json, debug],
289
  ).then(lambda x: x, inputs=[fin_json], outputs=[fin_state])
290
 
291
  infer_btn.click(
 
299
  inputs=[ext_df, market_df, fin_state, products],
300
  outputs=[ext_df, ext_total, ai_total, ext_plot, ai_plot, diff_plot, ext_json, ai_json, debug],
301
  )
 
302
  return demo