Spaces:
Runtime error
Runtime error
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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) |