Create extract.py
Browse files- core/extract.py +57 -0
core/extract.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
import json
|
| 3 |
+
from typing import Any, Dict, List, Optional
|
| 4 |
+
from .openai_client import get_client, VISION_MODEL, TEXT_MODEL
|
| 5 |
+
from .pdf_io import b64
|
| 6 |
+
|
| 7 |
+
SYSTEM_JSON = """あなたは有能な財務アナリストです。
|
| 8 |
+
与えられた決算書(画像またはテキスト)から、次の厳密な JSON 構造のみを日本語の単位なし・半角数値で返してください。分からない項目は null。
|
| 9 |
+
{
|
| 10 |
+
"company": {"name": null},
|
| 11 |
+
"period": {"start_date": null, "end_date": null},
|
| 12 |
+
"balance_sheet": {
|
| 13 |
+
"total_assets": null, "total_liabilities": null, "total_equity": null,
|
| 14 |
+
"current_assets": null, "fixed_assets": null,
|
| 15 |
+
"current_liabilities": null, "long_term_liabilities": null
|
| 16 |
+
},
|
| 17 |
+
"income_statement": {
|
| 18 |
+
"sales": null, "cost_of_sales": null, "gross_profit": null,
|
| 19 |
+
"operating_expenses": null, "operating_income": null,
|
| 20 |
+
"ordinary_income": null, "net_income": null
|
| 21 |
+
},
|
| 22 |
+
"cash_flows": {
|
| 23 |
+
"operating_cash_flow": null, "investing_cash_flow": null, "financing_cash_flow": null
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
def extract_financials(images: Optional[List[bytes]], text_blob: Optional[str], company_hint: str="") -> Dict[str, Any]:
|
| 29 |
+
client = get_client()
|
| 30 |
+
if images and len(images) > 0:
|
| 31 |
+
content = [{"type": "text", "text": SYSTEM_JSON}]
|
| 32 |
+
if company_hint:
|
| 33 |
+
content.append({"type": "text", "text": f"会社名の候補: {company_hint}"})
|
| 34 |
+
for im in images:
|
| 35 |
+
content.append({"type": "input_image", "image_url": f"data:image/png;base64,{b64(im)}"})
|
| 36 |
+
resp = client.chat.completions.create(
|
| 37 |
+
model=VISION_MODEL,
|
| 38 |
+
messages=[
|
| 39 |
+
{"role": "system", "content": "返答は必ず有効な JSON オブジェクトのみ。説明は不要。"},
|
| 40 |
+
{"role": "user", "content": content},
|
| 41 |
+
],
|
| 42 |
+
response_format={"type": "json_object"},
|
| 43 |
+
temperature=0.1,
|
| 44 |
+
)
|
| 45 |
+
return json.loads(resp.choices[0].message.content)
|
| 46 |
+
else:
|
| 47 |
+
prompt = f"{SYSTEM_JSON}\n\n以下は決算書のテキストです。上記の JSON だけを返してください。\n\n{text_blob or ''}"
|
| 48 |
+
resp = client.chat.completions.create(
|
| 49 |
+
model=TEXT_MODEL,
|
| 50 |
+
messages=[
|
| 51 |
+
{"role": "system", "content": "返答は必ず有効な JSON オブジェクトのみ。説明は不要。"},
|
| 52 |
+
{"role": "user", "content": prompt},
|
| 53 |
+
],
|
| 54 |
+
response_format={"type": "json_object"},
|
| 55 |
+
temperature=0.1,
|
| 56 |
+
)
|
| 57 |
+
return json.loads(resp.choices[0].message.content)
|