Spaces:
Sleeping
Sleeping
| import yfinance as yf | |
| import pandas as pd | |
| from fastapi import FastAPI | |
| import uvicorn | |
| app = FastAPI(title="Vertex Coders Risk API") | |
| class VertexRiskEngine: | |
| def __init__(self, ticker): | |
| self.ticker_str = ticker | |
| self.company = yf.Ticker(ticker) | |
| self.scores = {} | |
| def get_value(self, df, possible_keys): | |
| """Busca un valor en el DataFrame intentando varias llaves posibles.""" | |
| for key in possible_keys: | |
| if key in df.index: | |
| return df.loc[key] | |
| raise ValueError(f"Ninguna de las llaves {possible_keys} encontrada.") | |
| def run_audit(self): | |
| try: | |
| balance = self.company.balance_sheet | |
| financials = self.company.financials | |
| # --- ALTMAN Z-SCORE --- | |
| # Usamos iloc[0] para el año más reciente | |
| working_capital = self.get_value(balance, ['Working Capital']).iloc[0] | |
| total_assets = self.get_value(balance, ['Total Assets']).iloc[0] | |
| retained_earnings = self.get_value(balance, ['Retained Earnings']).iloc[0] | |
| ebit = self.get_value(financials, ['EBIT']).iloc[0] | |
| total_liabilities = self.get_value(balance, ['Total Liabilities Net Minority Interest', 'Total Liabilities']).iloc[0] | |
| sales = self.get_value(financials, ['Total Revenue']).iloc[0] | |
| market_cap = self.company.info.get('marketCap', 1) | |
| z = (1.2 * (working_capital/total_assets) + | |
| 1.4 * (retained_earnings/total_assets) + | |
| 3.3 * (ebit/total_assets) + | |
| 0.6 * (market_cap/total_liabilities) + | |
| 1.0 * (sales/total_assets)) | |
| self.scores['altman_z'] = round(z, 2) | |
| # --- BENEISH M-SCORE (DSRI) --- | |
| # Comparamos Año T (iloc[0]) vs Año T-1 (iloc[1]) | |
| sales_t = sales | |
| sales_t1 = self.get_value(financials, ['Total Revenue']).iloc[1] | |
| receivables_keys = ['Net Receivables', 'Accounts Receivable', 'Receivables'] | |
| rec_t = self.get_value(balance, receivables_keys).iloc[0] | |
| rec_t1 = self.get_value(balance, receivables_keys).iloc[1] | |
| dsri = (rec_t / sales_t) / (rec_t1 / sales_t1) | |
| self.scores['m_score_dsri'] = round(dsri, 2) | |
| return self.scores | |
| except Exception as e: | |
| return {"error": str(e)} | |
| def audit_company(ticker: str): | |
| engine = VertexRiskEngine(ticker.upper()) | |
| result = engine.run_audit() | |
| # 1. Validar si el motor devolvió un error de ejecución | |
| if "error" in result: | |
| return { | |
| "ticker": ticker.upper(), | |
| "status": "ERROR_TECNICO", | |
| "analysis": None, | |
| "msg": f"No se pudo completar la auditoría: {result['error']}" | |
| } | |
| # 2. Extraer valores con seguridad (Type Casting) | |
| # Forzamos a float para evitar errores de comparación con tipos desconocidos | |
| try: | |
| z_score = float(result.get('altman_z', 0)) | |
| m_dsri = float(result.get('m_score_dsri', 0)) | |
| except (ValueError, TypeError): | |
| z_score = 0.0 | |
| m_dsri = 0.0 | |
| # 3. Lógica de Semáforo Robusta | |
| # Prioridad: Rojo (Peligro inminente o posible fraude) | |
| if z_score < 1.8 or m_dsri > 1.4: | |
| status = "ROJO" | |
| msg = "Riesgo crítico detectado: Posible quiebra o manipulación." | |
| # Zona Gris | |
| elif 1.8 <= z_score < 3.0: | |
| status = "AMARILLO" | |
| msg = "Empresa en zona gris. Requiere supervisión manual." | |
| # Zona Segura | |
| else: | |
| status = "VERDE" | |
| msg = "Fundamentos financieros sólidos según modelos automáticos." | |
| return { | |
| "ticker": ticker.upper(), | |
| "status": status, | |
| "analysis": result, | |
| "msg": msg, | |
| "engine_version": "1.0-Némesis" | |
| } | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=8010) |