portfolio / server.py
eric2digit's picture
Upload folder using huggingface_hub
bf3714e verified
import argparse
import os
import tempfile
from datetime import datetime
from typing import List, Optional, Dict, Any
from fastapi import FastAPI, HTTPException, File, UploadFile
from pydantic import BaseModel
from industry_info import industry_info
from investment_company import investment_company
from similar_investors import SimilarInvestors
from stock_news import StockNews
from stock_price import StockPrice
from theme_info import theme_info
from portfolio import build_final_prompt, chatgpt, read_file_text, load_json
app = FastAPI(title="Portfolio Analysis")
# ๋ฐ์ดํ„ฐ ํŒŒ์ผ ๊ฒฝ๋กœ ์„ค์ •
DATA_PATHS = {
"similar_investors_us": "/work/portfolio/data/SEC_Filing_Manager.csv",
"similar_investors_ko": "/work/portfolio/data/ETF.csv",
"investment_company": "/work/portfolio/data/investment_company.jsonl",
"industry_info": "/work/portfolio/data/industry_info.csv",
"theme_info":"/work/portfolio/data/theme_info.csv"
}
# Request Models
class IndustryInfoRequest(BaseModel):
stock: List[str] = ["Meta"]
class InvestmentCompanyRequest(BaseModel):
name: str = "GEODE CAPITAL MANAGEMENT, LLC"
class SimilarInvestorsRequest(BaseModel):
csv_text: str = """์ข…๋ชฉ์ฝ”๋“œ,์ข…๋ชฉ๋ช…,๋งค์ž…์ฃผ๊ฐ€,๋ณด์œ ์ฃผ์‹์ˆ˜\nNVDA,NVIDIA Corporation,159.69,167\nAMZN,"Amazon.com, Inc.",211.58,140"""
region: str
top: int = 1
class StockNewsRequest(BaseModel):
stock: str = "META"
period: int = 1
class StockPriceRequest(BaseModel):
stock: str = "META"
class ThemeInfoRequest(BaseModel):
stock: List[str] = ["Meta"]
class PortfolioRequest(BaseModel):
date:str = datetime.now().strftime("%Y๋…„ %m์›” %d์ผ")
user_name: str = "์ด์„œ์ค€"
csv_text: str = "์ข…๋ชฉ์ฝ”๋“œ,์ข…๋ชฉ๋ช…,๋งค์ž…์ฃผ๊ฐ€,๋ณด์œ ์ฃผ์‹์ˆ˜\nAAPL,Apple Inc.,150,10"
json_text: Dict[str, Any] = {"similar_investors":[{"ID":"37833100","NAME":"LAZARI CAPITAL MANAGEMENT, INC.","COMPANY":"APPLE INC","VALUE":33972687.0,"AMOUNT":165583,"PERCENTAGE":12.8}],"stock_price":{"META":[{"Date":"2025-10-20","Name":"Meta Platforms","Open":721.2,"High":733.8,"Low":720.2,"Close":732.2,"Volume":8900200},{"Date":"2025-10-21","Name":"Meta Platforms","Open":736.0,"High":738.5,"Low":728.8,"Close":733.3,"Volume":7647300},{"Date":"2025-10-22","Name":"Meta Platforms","Open":733.8,"High":740.6,"Low":724.0,"Close":733.4,"Volume":8734500}]},"stock_news":{"META":[{"Name":"Meta Platforms","date":"2025-11-20","time":"08:37","news":"๋”ฅ๋Ÿฌ๋‹ ๊ฑฐ์žฅ ์–€ ๋ฅด์ฟค, Meta Platforms ๋– ๋‚˜ AI ์Šคํƒ€ํŠธ์—… ์„ค๋ฆฝ ์„ ์–ธ\nโ€œ์˜ค๋Š˜๋‚  AI๋Š” ์ธ๊ฐ„ ์ˆ˜์ค€์˜ ์ง€๋Šฅ์„ ํ–ฅํ•ด ๊ฐ€๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ์ƒˆ๋กœ์šด ์ ‘๊ทผ์ด ํ•„์š”ํ•˜๋‹ค.โ€ ๋”ฅ๋Ÿฌ๋‹์˜ ๊ฐœ์ฒ™์ž ์–€ ๋ฅด์ฟค(Yann LeCun)์ด Meta๋ฅผ ๋– ๋‚˜๋ฉฐ ๋‚จ๊ธด ์ด ์ฝ”๋ฉ˜ํŠธ๋Š” AI ์—…๊ณ„..."}]},"industry_info":[{"stock":"Meta","description":"Industry: Internet Content & Information | Sector: Communication Services"}],"theme_info":[{"stock":"Meta","desc":"Theme: Artificial Intelligence | Theme 2: \"Social Media\" | Theme 3: \"Virtual Reality\""}]}
# 1. Industry Info API
@app.post("/industry_info")
def get_industry_info(request: IndustryInfoRequest):
"""์ข…๋ชฉ์˜ ์‚ฐ์—…๊ตฐ/์„นํ„ฐ ๊ฒ€์ƒ‰"""
try:
print(f"[INFO] Processing stocks: {request.stock}")
# industry_info ๊ฐ์ฒด ์ƒ์„ฑ
ic = industry_info(DATA_PATHS["industry_info"])
all_records = []
for stock in request.stock:
print(f"[INFO] ์ฒ˜๋ฆฌ ์ค‘: {stock}")
result = ic.make(stock)
if not result:
print(f"[WARN] ์ข…๋ชฉ ์ฒ˜๋ฆฌ ์‹คํŒจ: {stock}")
continue
full_name = result["stock"]
desc = ic.get(full_name)
if desc:
print(f"[INFO] {full_name} ์กฐํšŒ ์™„๋ฃŒ")
all_records.append({
"stock": full_name,
"description": desc
})
else:
print(f"[WARN] {full_name} description ์—†์Œ")
return {
"success": True,
"data": all_records,
"count": len(all_records)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 2. Investment Company API
@app.post("/investment_company")
def get_investment_company(request: InvestmentCompanyRequest):
"""ํˆฌ์žํšŒ์‚ฌ ์ •๋ณด ์กฐํšŒ"""
try:
print(f"[INFO] Processing company: {request.name}")
ic = investment_company(DATA_PATHS["investment_company"])
ic.make()
desc = ic.get(request.name)
return {
"success": True,
"data": {
"company_name": request.name,
"description": desc
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 3. Similar Investors API
@app.post("/similar_investors")
def get_similar_investors(request: SimilarInvestorsRequest):
"""์‚ฌ์šฉ์ž์™€ ์œ ์‚ฌํ•œ ํˆฌ์žํšŒ์‚ฌ ๊ฒ€์ƒ‰ (CSV ๋ฌธ์ž์—ด ์ž…๋ ฅ)"""
try:
print(f"[INFO] Processing CSV text (length={len(request.csv_text)}), top: {request.top}")
# SimilarInvestors ์ค€๋น„
si = SimilarInvestors(DATA_PATHS["similar_investors_ko"], DATA_PATHS["similar_investors_us"])
if request.region == 'ko':
created_path = si.make_ko()
elif request.region == 'us':
created_path = si.make_us()
else:
print("[INFO] region ์ž…๋ ฅ ์˜ค๋ฅ˜.")
raise HTTPException(status_code=400, detail="region์€ 'ko' ๋˜๋Š” 'us' ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
print(f"[INFO] ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ: {created_path}")
# 1) csv_text ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (๊ฐ„๋‹จ)
header = request.csv_text.strip().splitlines()[0]
required_cols = ["์ข…๋ชฉ์ฝ”๋“œ", "์ข…๋ชฉ๋ช…", "๋งค์ž…์ฃผ๊ฐ€", "๋ณด์œ ์ฃผ์‹์ˆ˜"]
for col in required_cols:
if col not in header.split(","):
raise HTTPException(status_code=400, detail=f"CSV ํ—ค๋”์— '{col}' ์ปฌ๋Ÿผ์ด ์—†์Šต๋‹ˆ๋‹ค.")
print("1์™„๋ฃŒ \n")
# 2) ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜์—ฌ ๊ธฐ์กด si.get ์žฌ์‚ฌ์šฉ
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as tmp:
tmp_path = tmp.name
tmp.write(request.csv_text)
print(f"[INFO] ์ž„์‹œ CSV ํŒŒ์ผ ์ƒ์„ฑ: {tmp_path}")
try:
result = si.get(tmp_path, request.region, output_path=None, top_n=request.top)
count = sum(len(rows) for rows in result.values())
finally:
# ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
if os.path.exists(tmp_path):
os.remove(tmp_path)
print(f"[INFO] ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ ์™„๋ฃŒ: {tmp_path}")
return {
"success": True,
"data": result,
"count": count,
"top_n": request.top
}
except HTTPException as e:
raise e
except Exception as e:
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
# 4. Stock News API
@app.post("/stock_news")
def get_stock_news(request: StockNewsRequest):
"""๊ธฐ์—… ๋‰ด์Šค ํฌ๋กค๋ง"""
try:
print(f"[INFO] Processing stock: {request.stock}, period: {request.period}")
data_folder = "data_stock_news"
sn = StockNews(data_folder)
# make ํ•จ์ˆ˜ ์‹คํ–‰
created = sn.make(request.stock, request.period)
print(f"[INFO] ์ƒ์„ฑ๋œ ๊ฑด์ˆ˜: {len(created) if created is not None else 0}")
# get ํ•จ์ˆ˜ ์‹คํ–‰
loaded = sn.get(request.stock)
print(f"[INFO] ๋กœ๋“œ๋œ ๊ฑด์ˆ˜: {len(loaded) if loaded is not None else 0}")
stock = sn.normalize_ticker(request.stock)
records = loaded.to_dict('records') if loaded is not None else []
return {
"success": True,
"data": {
stock: records
},
"count": len(records)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 5. Stock Price API
@app.post("/stock_price")
def get_stock_price(request: StockPriceRequest):
"""์ฃผ๊ฐ€ ๋ฐ์ดํ„ฐ ์กฐํšŒ"""
try:
stocks = [t.strip().upper() for t in request.stock.split(",") if t.strip()]
print(f"[INFO] Processing {len(stocks)} stocks")
data_folder = "data_stock_price/"
sp = StockPrice(data_folder)
stock_to_records = {}
for stock in stocks:
df = sp.make(stock)
if df is None:
print(f"[WARN] ์ƒ์„ฑ ์‹คํŒจ: {stock}")
continue
df = sp.get(stock)
stock_to_records[sp.normalize_ticker(stock)] = df.to_dict('records')
return {
"success": True,
"data": stock_to_records,
"stocks_processed": len(stock_to_records)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 6. Theme Info API
@app.post("/theme_info")
def get_theme_info(request: ThemeInfoRequest):
"""์ข…๋ชฉ์˜ ํ…Œ๋งˆ ๊ฒ€์ƒ‰"""
try:
print(f"[INFO] Processing stocks: {request.stock}")
ti = theme_info(DATA_PATHS["theme_info"])
# make ํ•จ์ˆ˜ ์‹คํ–‰
make_results = []
for stock in request.stock:
print(f"[INFO] ์ฒ˜๋ฆฌ ์ค‘: {stock}")
rows = ti.make(stock)
if rows:
make_results.extend(rows)
# get ํ•จ์ˆ˜ ์‹คํ–‰
all_records = []
for row in make_results:
official_name = row["NAME"]
desc = ti.get(official_name)
all_records.append({
"stock": official_name,
"desc": desc
})
print(f"[INFO] {official_name} ์กฐํšŒ ์™„๋ฃŒ")
return {
"success": True,
"data": all_records,
"count": len(all_records)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 7. Portfolio Report API
@app.post("/report")
def generate_portfolio_report(request: PortfolioRequest):
try:
print(f"[INFO] Generating report for user: {request.user_name}")
csv_text = request.csv_text
json_obj = request.json_text
print("[INFO] ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์™„๋ฃŒ")
final_prompt = build_final_prompt(request.date, request.user_name, csv_text, json_obj)
print("[INFO] ํ”„๋กฌํ”„ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ")
report_text = chatgpt(final_prompt)
print("[INFO] ๋ฆฌํฌํŠธ ์ƒ์„ฑ ์™„๋ฃŒ")
return {
"success": True,
"data": {
"user_name": request.user_name,
"report": report_text
}
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--port', type=int, default=8080)
args = parser.parse_args()
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=args.port)