portfolio / server.py
eric2digit's picture
Upload folder using huggingface_hub
bf3714e verified
Raw
History Blame Contribute Delete
11.2 kB
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)