Corin1998 commited on
Commit
4142cdd
·
verified ·
1 Parent(s): aa72477

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +12 -116
app.py CHANGED
@@ -1,121 +1,17 @@
1
- from __future__ import annotations
2
- import os, io, base64, json, tempfile
3
- from typing import List, Dict, Any
4
- from fastapi import FastAPI, UploadFile, File, Form
5
- from fastapi.middleware.cors import CORSMiddleware
6
- from pdf2image import convert_from_path
7
- import pdfplumber
8
- from openai import OpenAI
9
- from scorer import score_company
10
 
11
- OPENAI_MODEL_VISION = os.getenv("OPENAI_VISION_MODEL", "gpt-4o-mini")
12
- OPENAI_MODEL_TEXT = os.getenv("OPENAI_TEXT_MODEL", "gpt-4o-mini")
13
 
14
- def _b64(b: bytes) -> str: return base64.b64encode(b).decode("utf-8")
 
15
 
16
- def _client() -> OpenAI:
17
- key = os.getenv("OPENAI_API_KEY")
18
- if not key: raise RuntimeError("OPENAI_API_KEY not set")
19
- return OpenAI(api_key=key, timeout=60)
20
 
21
- SYSTEM_JSON = """あなたは有能な財務アナリストです。
22
- 与えられた決算書(画像またはテキスト)から、次の厳密な JSON 構造のみを日本語の単位なし・半角数値で返してください。分からない項目は null。
23
- {
24
- "company": {"name": null},
25
- "period": {"start_date": null, "end_date": null},
26
- "balance_sheet": {
27
- "total_assets": null, "total_liabilities": null, "total_equity": null,
28
- "current_assets": null, "fixed_assets": null,
29
- "current_liabilities": null, "long_term_liabilities": null
30
- },
31
- "income_statement": {
32
- "sales": null, "cost_of_sales": null, "gross_profit": null,
33
- "operating_expenses": null, "operating_income": null,
34
- "ordinary_income": null, "net_income": null
35
- },
36
- "cash_flows": {
37
- "operating_cash_flow": null, "investing_cash_flow": null, "financing_cash_flow": null
38
- }
39
- }
40
- """
41
 
42
- def pdf_to_images(path: str, dpi=220, max_pages=6) -> List[bytes]:
43
- pages = convert_from_path(path, dpi=dpi, fmt="png")
44
- out = []
45
- for i, p in enumerate(pages):
46
- if i >= max_pages: break
47
- buf = io.BytesIO(); p.save(buf, format="PNG"); out.append(buf.getvalue())
48
- return out
49
-
50
- def pdf_to_text(path: str, max_chars=15000) -> str:
51
- chunks = []
52
- with pdfplumber.open(path) as pdf:
53
- for i, page in enumerate(pdf.pages):
54
- t = (page.extract_text() or "").strip()
55
- if t: chunks.append(f"[page {i+1}]\n{t}")
56
- if sum(len(c) for c in chunks) > max_chars: break
57
- return "\n\n".join(chunks)[:max_chars]
58
-
59
- def extract(images: List[bytes] | None, text_blob: str | None, company_hint: str) -> Dict[str, Any]:
60
- client = _client()
61
- if images:
62
- content = [{"type":"text","text":SYSTEM_JSON}]
63
- if company_hint: content.append({"type":"text","text":f"会社名の候補: {company_hint}"})
64
- for im in images:
65
- content.append({"type":"input_image","image_url":f"data:image/png;base64,{_b64(im)}"})
66
- res = client.chat.completions.create(
67
- model=OPENAI_MODEL_VISION,
68
- messages=[{"role":"system","content":"返答は有効なJSONのみ"},
69
- {"role":"user","content":content}],
70
- response_format={"type":"json_object"}, temperature=0.1,
71
- )
72
- return json.loads(res.choices[0].message.content)
73
- else:
74
- prompt = f"{SYSTEM_JSON}\n\n{text_blob or ''}"
75
- res = client.chat.completions.create(
76
- model=OPENAI_MODEL_TEXT,
77
- messages=[{"role":"system","content":"返答は有効なJSONのみ"},
78
- {"role":"user","content":prompt}],
79
- response_format={"type":"json_object"}, temperature=0.1,
80
- )
81
- return json.loads(res.choices[0].message.content)
82
-
83
- app = FastAPI()
84
- app.add_middleware(
85
- CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
86
- )
87
-
88
- @app.get("/health")
89
- def health():
90
- return {"ok": True, "vision_model": OPENAI_MODEL_VISION, "text_model": OPENAI_MODEL_TEXT}
91
-
92
- @app.post("/analyze")
93
- async def analyze(
94
- company: str = Form(""),
95
- use_vision: bool = Form(True),
96
- files: List[UploadFile] = File(...),
97
- ):
98
- # 受け取った PDF を一時保存
99
- tmps = []
100
- for f in files:
101
- suf = ".pdf" if not f.filename.lower().endswith(".pdf") else ""
102
- fd, path = tempfile.mkstemp(suffix=suf); os.close(fd)
103
- with open(path, "wb") as w: w.write(await f.read())
104
- tmps.append(path)
105
-
106
- try:
107
- if use_vision:
108
- imgs = []
109
- for p in tmps: imgs += pdf_to_images(p)
110
- fin = extract(imgs, None, company)
111
- else:
112
- text = ""
113
- for p in tmps: text += pdf_to_text(p) + "\n\n"
114
- fin = extract(None, text, company)
115
-
116
- score = score_company(fin)
117
- return {"financials": fin, "score": score}
118
- finally:
119
- for p in tmps:
120
- try: os.remove(p)
121
- except: pass
 
1
+ from fastapi import FastAPI
2
+ import gradio as gr
 
 
 
 
 
 
 
3
 
4
+ # API(FastAPI) 本体
5
+ from api.app import api # /api/app.py 内で FastAPI() を作成
6
 
7
+ # UI(Gradio Blocks)
8
+ from ui.ui_app import build_ui # /ui/ui_app.py が Blocks を返す
9
 
10
+ demo = build_ui()
 
 
 
11
 
12
+ # ルート("/")にGradio、APIは /api/ 以下に残す
13
+ app = gr.mount_gradio_app(api, demo, path="/")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ if __name__ == "__main__":
16
+ import uvicorn
17
+ uvicorn.run(app, host="0.0.0.0", port=7860)