Spaces:
Sleeping
Sleeping
| import json | |
| import os | |
| from fastapi import FastAPI | |
| import uvicorn | |
| from engine.risk_engine import VertexRiskEngine | |
| from engine.semantic import VertexSemanticAgent | |
| from engine.web3_engine import VertexWeb3Engine | |
| app = FastAPI() | |
| # Archivo de persistencia de la watchlist | |
| WATCHLIST_FILE = "app/watchlist.json" | |
| def run_batch_audit(): | |
| """Audita toda la watchlist y devuelve alertas para n8n.""" | |
| if not os.path.exists(WATCHLIST_FILE): | |
| return {"error": "Watchlist file not found", "alerts": []} | |
| try: | |
| with open(WATCHLIST_FILE, "r") as f: | |
| watchlist = json.load(f) | |
| except Exception as e: | |
| return {"error": f"Failed to read watchlist: {e}", "alerts": []} | |
| results = [] | |
| for ticker in watchlist: | |
| try: | |
| # Reutilizamos la lógica del motor Némesis | |
| engine = VertexRiskEngine(ticker.upper()) | |
| num_res = engine.run_audit() | |
| sem_agent = VertexSemanticAgent(ticker.upper()) | |
| risk_text = sem_agent.get_sec_risk_factors() | |
| sem_res = sem_agent.judge_risks(risk_text) | |
| # Blindaje de tipos para evitar errores de comparación | |
| z_score = float(num_res.get('altman_z', 0.0)) | |
| m_dsri = float(num_res.get('m_score_dsri', 0.0)) | |
| s_score = float(sem_res.get('semantic_score', 0.0)) | |
| # Lógica de Semáforo de Riesgo | |
| if z_score < 1.8 or m_dsri > 1.4 or s_score > 3: | |
| status = "RED" | |
| elif z_score < 3.0: | |
| status = "YELLOW" | |
| else: | |
| status = "GREEN" | |
| results.append({ | |
| "ticker": ticker, | |
| "status": status, | |
| "z_score": z_score, | |
| "summary": sem_res.get("summary", ""), | |
| "alert": True if status == "RED" else False | |
| }) | |
| except Exception as ticker_error: | |
| results.append({"ticker": ticker, "status": "ERROR", "msg": str(ticker_error)}) | |
| critical_alerts = [r for r in results if r.get("status") in ["RED", "YELLOW"]] | |
| return { | |
| "total_analyzed": len(watchlist), | |
| "critical_count": len(critical_alerts), | |
| "alerts": critical_alerts, | |
| "full_results": results | |
| } | |
| def audit_company(ticker: str): | |
| """Auditoría individual para el tab de Stock Audit.""" | |
| try: | |
| engine = VertexRiskEngine(ticker.upper()) | |
| num_res = engine.run_audit() | |
| sem_agent = VertexSemanticAgent(ticker.upper()) | |
| risk_text = sem_agent.get_sec_risk_factors() | |
| sem_res = sem_agent.judge_risks(risk_text) | |
| # Blindaje de tipos | |
| z_score = float(num_res.get('altman_z', 0.0)) | |
| m_dsri = float(num_res.get('m_score_dsri', 0.0)) | |
| s_score = float(sem_res.get('semantic_score', 0.0)) | |
| if z_score < 1.8 or m_dsri > 1.4 or s_score > 3: | |
| status = "RED" | |
| msg = "CRITICAL RISK: Red flags detected." | |
| elif z_score < 3.0: | |
| status = "YELLOW" | |
| msg = "CAUTION: Monitor closely." | |
| else: | |
| status = "GREEN" | |
| msg = "SAFE: Solid fundamentals." | |
| return { | |
| "ticker": ticker.upper(), | |
| "status": status, | |
| "numeric_analysis": {"altman_z": z_score, "m_score_dsri": m_dsri}, | |
| "semantic_analysis": sem_res, | |
| "msg": msg | |
| } | |
| except Exception as e: | |
| return {"status": "ERROR", "msg": str(e)} | |
| def audit_smart_contract(address: str): | |
| """Auditoría de Web3 usando el nuevo motor V2.""" | |
| try: | |
| web3_engine = VertexWeb3Engine(address) | |
| audit_res = web3_engine.get_contract_source() | |
| if not audit_res["success"]: | |
| return {"status": "ERROR", "msg": audit_res["error"]} | |
| source = audit_res["source_code"] | |
| vulnerabilities = web3_engine.scan_basic_vulnerabilities(source) | |
| return { | |
| "address": address, | |
| "status": "DANGER" if len(vulnerabilities) > 0 else "SAFE", | |
| "vulnerabilities": vulnerabilities, | |
| "source_preview": source[:500] + "..." # Ahora source es string y el slice no falla | |
| } | |
| except Exception as e: | |
| return {"status": "ERROR", "msg": str(e)} | |
| async def get_settings(): | |
| # Esta línea es la que saca las llaves de los Secrets de Hugging Face | |
| token = os.environ.get("BOT_TOKEN", "") | |
| chat = os.environ.get("CHAT_ID", "") | |
| if token and chat: | |
| return {"bot_token": token, "chat_id": chat, "status": "loaded_from_secrets"} | |
| return {"bot_token": "", "chat_id": "", "status": "secrets_not_found"} | |
| if __name__ == "__main__": | |
| # Hugging Face SIEMPRE usa el puerto 7860 internamente | |
| port = int(os.environ.get("PORT", 7860)) | |
| # Importante: host="0.0.0.0" para que sea accesible desde fuera del contenedor | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |