Really-amin commited on
Commit
21b7d33
·
verified ·
1 Parent(s): 8796871

Upload 322 files

Browse files
Files changed (3) hide show
  1. Dockerfile +1 -1
  2. api_server_extended.py +1 -1
  3. hf_unified_server.py +190 -0
Dockerfile CHANGED
@@ -21,4 +21,4 @@ ENV PYTHONUNBUFFERED=1
21
  EXPOSE 7860
22
 
23
  # Launch command
24
- CMD ["uvicorn", "api_server_extended:app", "--host", "0.0.0.0", "--port", "7860"]
 
21
  EXPOSE 7860
22
 
23
  # Launch command
24
+ CMD ["uvicorn", "hf_unified_server:app", "--host", "0.0.0.0", "--port", "7860"]
api_server_extended.py CHANGED
@@ -247,7 +247,7 @@ except Exception as e:
247
  @app.get("/", response_class=HTMLResponse)
248
  async def serve_admin_dashboard():
249
  """Serve admin dashboard"""
250
- html_path = WORKSPACE_ROOT / "index.html"
251
  if html_path.exists():
252
  return FileResponse(html_path)
253
  return HTMLResponse("<h1>Admin Dashboard</h1><p>admin.html not found</p>")
 
247
  @app.get("/", response_class=HTMLResponse)
248
  async def serve_admin_dashboard():
249
  """Serve admin dashboard"""
250
+ html_path = WORKSPACE_ROOT / "admin.html"
251
  if html_path.exists():
252
  return FileResponse(html_path)
253
  return HTMLResponse("<h1>Admin Dashboard</h1><p>admin.html not found</p>")
hf_unified_server.py CHANGED
@@ -35,6 +35,8 @@ import json
35
  from pathlib import Path
36
  import httpx
37
 
 
 
38
  from ai_models import (
39
  analyze_chart_points,
40
  analyze_crypto_sentiment,
@@ -84,6 +86,27 @@ provider_collector = ProviderStatusCollector()
84
  WORKSPACE_ROOT = Path(__file__).parent
85
  PROVIDERS_CONFIG_PATH = settings.providers_config_path
86
  FALLBACK_RESOURCE_PATH = WORKSPACE_ROOT / "crypto_resources_unified_2025-11-11.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  def load_providers_config():
89
  """Load providers from providers_config_extended.json"""
@@ -643,6 +666,24 @@ async def get_market():
643
  if not prices:
644
  raise HTTPException(status_code=503, detail="Unable to fetch market data")
645
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  return {
647
  "total_market_cap": overview.get("total_market_cap", 0),
648
  "btc_dominance": overview.get("btc_dominance", 0),
@@ -2413,3 +2454,152 @@ async def websocket_endpoint(websocket: WebSocket):
2413
  ws_manager.disconnect(websocket)
2414
  except:
2415
  pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  from pathlib import Path
36
  import httpx
37
 
38
+ from database import CryptoDatabase
39
+
40
  from ai_models import (
41
  analyze_chart_points,
42
  analyze_crypto_sentiment,
 
86
  WORKSPACE_ROOT = Path(__file__).parent
87
  PROVIDERS_CONFIG_PATH = settings.providers_config_path
88
  FALLBACK_RESOURCE_PATH = WORKSPACE_ROOT / "crypto_resources_unified_2025-11-11.json"
89
+ LOG_DIR = WORKSPACE_ROOT / "logs"
90
+ APL_REPORT_PATH = WORKSPACE_ROOT / "PROVIDER_AUTO_DISCOVERY_REPORT.json"
91
+
92
+ # Ensure log directory exists
93
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
94
+
95
+ # Initialize SQLite database for real market data persistence
96
+ db = CryptoDatabase()
97
+
98
+ def tail_log_file(path: Path, max_lines: int = 200) -> List[str]:
99
+ """Return the last max_lines from a log file, if it exists."""
100
+ if not path.exists():
101
+ return []
102
+ try:
103
+ with path.open("r", encoding="utf-8", errors="ignore") as f:
104
+ lines = f.readlines()
105
+ return lines[-max_lines:]
106
+ except Exception as e:
107
+ logger.error(f"Error reading log file {path}: {e}")
108
+ return []
109
+
110
 
111
  def load_providers_config():
112
  """Load providers from providers_config_extended.json"""
 
666
  if not prices:
667
  raise HTTPException(status_code=503, detail="Unable to fetch market data")
668
 
669
+ # Persist real market data into SQLite database (no mocks)
670
+ try:
671
+ for item in prices:
672
+ payload = {
673
+ "symbol": item.get("symbol", "").upper(),
674
+ "name": item.get("name"),
675
+ "price_usd": item.get("current_price") or item.get("price"),
676
+ "volume_24h": item.get("total_volume"),
677
+ "market_cap": item.get("market_cap"),
678
+ "percent_change_1h": item.get("price_change_1h") or item.get("price_change_percentage_1h_in_currency"),
679
+ "percent_change_24h": item.get("price_change_percentage_24h"),
680
+ "percent_change_7d": item.get("price_change_percentage_7d_in_currency"),
681
+ "rank": item.get("market_cap_rank"),
682
+ }
683
+ db.save_price(payload)
684
+ except Exception as db_err:
685
+ logger.warning(f"Failed to save market data to DB: {db_err}")
686
+
687
  return {
688
  "total_market_cap": overview.get("total_market_cap", 0),
689
  "btc_dominance": overview.get("btc_dominance", 0),
 
2454
  ws_manager.disconnect(websocket)
2455
  except:
2456
  pass
2457
+
2458
+ @app.get("/api/market/history")
2459
+ async def get_market_history(symbol: str = "BTC", limit: int = 10):
2460
+ """
2461
+ Get historical prices from the real SQLite database.
2462
+
2463
+ This uses only stored real market data inserted by /api/market
2464
+ and never generates synthetic or mock values.
2465
+ """
2466
+ symbol = symbol.upper()
2467
+ try:
2468
+ history = db.get_price_history(symbol, limit=limit)
2469
+ except Exception as e:
2470
+ logger.error(f"Error reading history for {symbol}: {e}")
2471
+ raise HTTPException(status_code=500, detail="Error reading market history")
2472
+
2473
+ if not history:
2474
+ return {
2475
+ "symbol": symbol,
2476
+ "history": [],
2477
+ "count": 0,
2478
+ "message": "No history available",
2479
+ }
2480
+
2481
+ return {
2482
+ "symbol": symbol,
2483
+ "history": history,
2484
+ "count": len(history),
2485
+ "source": "SQLite database (real data)",
2486
+ }
2487
+
2488
+
2489
+ @app.get("/api/status")
2490
+ async def get_status():
2491
+ """
2492
+ System status endpoint used by the admin UI.
2493
+
2494
+ This reports real-time information about providers and database,
2495
+ without fabricating any market data.
2496
+ """
2497
+ providers_cfg = load_providers_config()
2498
+ providers = providers_cfg or {}
2499
+ validated_count = sum(1 for p in providers.values() if p.get("validated"))
2500
+
2501
+ db_path = Path(db.db_path)
2502
+ db_status = "connected" if db_path.exists() else "initializing"
2503
+
2504
+ return {
2505
+ "system_health": "healthy",
2506
+ "timestamp": datetime.now().isoformat(),
2507
+ "total_providers": len(providers),
2508
+ "validated_providers": validated_count,
2509
+ "database_status": db_status,
2510
+ "apl_available": APL_REPORT_PATH.exists(),
2511
+ "use_mock_data": False,
2512
+ }
2513
+
2514
+
2515
+ @app.get("/api/logs/recent")
2516
+ async def get_recent_logs():
2517
+ """
2518
+ Return recent log lines for the admin UI.
2519
+
2520
+ We read from the main server log file if available.
2521
+ This does not fabricate content; if there are no logs,
2522
+ an empty list is returned.
2523
+ """
2524
+ log_file = LOG_DIR / "server.log"
2525
+ lines = tail_log_file(log_file, max_lines=200)
2526
+ # Wrap plain text lines as structured entries
2527
+ logs = [{"line": line.rstrip("\n")} for line in lines]
2528
+ return {"logs": logs, "count": len(logs)}
2529
+
2530
+
2531
+ @app.get("/api/logs/errors")
2532
+ async def get_error_logs():
2533
+ """
2534
+ Return recent error log lines from the same log file.
2535
+
2536
+ This is a best-effort filter based on typical ERROR prefixes.
2537
+ """
2538
+ log_file = LOG_DIR / "server.log"
2539
+ lines = tail_log_file(log_file, max_lines=400)
2540
+ error_lines = [line for line in lines if "ERROR" in line or "WARNING" in line]
2541
+ logs = [{"line": line.rstrip("\n")} for line in error_lines[-200:]]
2542
+ return {"errors": logs, "count": len(logs)}
2543
+
2544
+
2545
+ def _load_apl_report() -> Optional[Dict[str, Any]]:
2546
+ """Load the APL (Auto Provider Loader) validation report if available."""
2547
+ if not APL_REPORT_PATH.exists():
2548
+ return None
2549
+ try:
2550
+ with APL_REPORT_PATH.open("r", encoding="utf-8") as f:
2551
+ return json.load(f)
2552
+ except Exception as e:
2553
+ logger.error(f"Error reading APL report: {e}")
2554
+ return None
2555
+
2556
+
2557
+ @app.get("/api/apl/summary")
2558
+ async def get_apl_summary():
2559
+ """
2560
+ Summary of the Auto Provider Loader (APL) report.
2561
+
2562
+ If the report is missing, we return a clear not_available status
2563
+ instead of fabricating metrics.
2564
+ """
2565
+ report = _load_apl_report()
2566
+ if not report or "stats" not in report:
2567
+ return {
2568
+ "status": "not_available",
2569
+ "message": "APL report not found",
2570
+ }
2571
+
2572
+ stats = report.get("stats", {})
2573
+ return {
2574
+ "status": "ok",
2575
+ "http_candidates": stats.get("total_http_candidates", 0),
2576
+ "http_valid": stats.get("http_valid", 0),
2577
+ "http_invalid": stats.get("http_invalid", 0),
2578
+ "http_conditional": stats.get("http_conditional", 0),
2579
+ "hf_candidates": stats.get("total_hf_candidates", 0),
2580
+ "hf_valid": stats.get("hf_valid", 0),
2581
+ "hf_invalid": stats.get("hf_invalid", 0),
2582
+ "hf_conditional": stats.get("hf_conditional", 0),
2583
+ "timestamp": datetime.now().isoformat(),
2584
+ }
2585
+
2586
+
2587
+ @app.get("/api/hf/models")
2588
+ async def get_hf_models_from_apl():
2589
+ """
2590
+ Return the list of Hugging Face models discovered by the APL report.
2591
+
2592
+ This is used by the admin UI. The data comes from the real
2593
+ PROVIDER_AUTO_DISCOVERY_REPORT.json file if present.
2594
+ """
2595
+ report = _load_apl_report()
2596
+ if not report:
2597
+ return {"models": [], "count": 0, "source": "none"}
2598
+
2599
+ hf_models = report.get("hf_models", {}).get("results", [])
2600
+ return {
2601
+ "models": hf_models,
2602
+ "count": len(hf_models),
2603
+ "source": "APL report",
2604
+ }
2605
+