KarlQuant commited on
Commit
8e6b64c
Β·
verified Β·
1 Parent(s): 7e41b02

Upload hub_dashboard_service.py

Browse files
Files changed (1) hide show
  1. hub_dashboard_service.py +137 -19
hub_dashboard_service.py CHANGED
@@ -27,18 +27,9 @@ from pathlib import Path
27
  from typing import Dict, List, Optional
28
 
29
  import websocket
30
- from flask import Flask, jsonify, request, send_from_directory
31
  from flask_cors import CORS
32
 
33
- # ── Ranker Logs Blueprint ─────────────────────────────────────────────────────
34
- # Import lazily so the service starts even if ranker_logs_api.py is absent.
35
- try:
36
- from ranker_logs_api import ranker_logs_bp, init_ranker_logs_api as _init_bp
37
- _RANKER_LOGS_BP_AVAILABLE = True
38
- except ImportError:
39
- _RANKER_LOGS_BP_AVAILABLE = False
40
- logger_import_warning = "ranker_logs_api.py not found β€” Blueprint endpoints disabled"
41
-
42
  # ── Logging ───────────────────────────────────────────────────────────────────────────
43
  logging.basicConfig(
44
  level=logging.INFO,
@@ -701,14 +692,139 @@ _hub_subscriber.start()
701
  app = Flask(__name__)
702
  CORS(app)
703
 
704
- # ── Register ranker logs Blueprint (powers /export, /clear, /asset/<a>, /level/<l>) ──
705
- if _RANKER_LOGS_BP_AVAILABLE:
706
- _file_logger_adapter = FileBasedLoggerAdapter(log_dir=_LOG_DIR)
707
- _init_bp(_file_logger_adapter)
708
- app.register_blueprint(ranker_logs_bp)
709
- logger.info("[Blueprint] ranker_logs_bp registered β€” all /api/ranker/logs/* endpoints active")
710
- else:
711
- logger.warning("[Blueprint] ranker_logs_api.py not found β€” inline routes only")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
 
713
 
714
  @app.route("/")
@@ -767,10 +883,12 @@ def health():
767
 
768
 
769
  if __name__ == "__main__":
770
- logger.info("=== K1RL QUASAR HUB DASHBOARD SERVICE v2.2 (FIXED) ===")
771
  logger.info(f"Dashboard: http://localhost:{_DASHBOARD_PORT}")
772
  logger.info(f"Log directory: {_LOG_DIR}")
773
  logger.info("Fixes applied:")
 
 
774
  logger.info(" βœ… FIX #1: Now reading *.log* (includes rotated files)")
775
  logger.info(" βœ… FIX #2: Improved regex to catch all trade close formats")
776
  logger.info(" βœ… FIX #4: Dedicated exit_price regex (no optional-group ambiguity)")
 
27
  from typing import Dict, List, Optional
28
 
29
  import websocket
30
+ from flask import Flask, jsonify, request, send_from_directory, send_file
31
  from flask_cors import CORS
32
 
 
 
 
 
 
 
 
 
 
33
  # ── Logging ───────────────────────────────────────────────────────────────────────────
34
  logging.basicConfig(
35
  level=logging.INFO,
 
692
  app = Flask(__name__)
693
  CORS(app)
694
 
695
+ # ── Instantiate the file-based log adapter (used by all /api/ranker/logs/* routes) ──
696
+ _log_adapter = FileBasedLoggerAdapter(log_dir=_LOG_DIR)
697
+
698
+
699
+ # ══════════════════════════════════════════════════════════════════════════════════════
700
+ # SECTION 4 β€” RANKER LOG ROUTES (self-contained β€” no Blueprint dependency)
701
+ # ══════════════════════════════════════════════════════════════════════════════════════
702
+ #
703
+ # FIX v2.4: These routes were previously delegated to ranker_logs_api.py Blueprint.
704
+ # That Blueprint was never registered, so every /api/ranker/logs/* call returned 404.
705
+ # Routes are now defined inline so hub_dashboard_service.py is fully self-contained.
706
+ # FileBasedLoggerAdapter (above) satisfies the full RankerLogger interface by reading
707
+ # the ranker's disk log files β€” no in-process ranker instance required.
708
+
709
+ _TRAINING_RE_INLINE = re.compile(
710
+ r'step=(\d+)\s*\|\s*loss=([\d.]+)\s*\|\s*lr=([\d.eE+\-]+)\s*\|\s*assets=(\d+)'
711
+ )
712
+ _JSON_BLOB_RE_INLINE = re.compile(r'(\{.*\})\s*$')
713
+
714
+
715
+ def _enrich_training(entry: dict) -> dict:
716
+ """Attach parsed `data` dict to TRAINING entries so dashboard KPI cards populate."""
717
+ if entry.get("category", "").upper() != "TRAINING":
718
+ return entry
719
+ if entry.get("data"):
720
+ return entry
721
+ msg = entry.get("message", "")
722
+ m = _TRAINING_RE_INLINE.search(msg)
723
+ if m:
724
+ entry["data"] = {
725
+ "step": int(m.group(1)),
726
+ "loss": float(m.group(2)),
727
+ "lr": float(m.group(3)),
728
+ "asset_count": int(m.group(4)),
729
+ }
730
+ return entry
731
+ jm = _JSON_BLOB_RE_INLINE.search(msg)
732
+ if jm:
733
+ try:
734
+ blob = json.loads(jm.group(1))
735
+ if "step" in blob:
736
+ entry["data"] = {
737
+ "step": blob.get("step", 0),
738
+ "loss": blob.get("loss", 0.0),
739
+ "lr": blob.get("lr", 0.0),
740
+ "asset_count": blob.get("asset_count", blob.get("assets", 0)),
741
+ }
742
+ except (ValueError, KeyError):
743
+ pass
744
+ return entry
745
+
746
+
747
+ @app.route("/api/ranker/logs/recent", methods=["GET"])
748
+ def api_logs_recent():
749
+ """GET /api/ranker/logs/recent?limit=50&category=TRAINING"""
750
+ try:
751
+ limit = int(request.args.get("limit", 50))
752
+ category = request.args.get("category")
753
+ entries = _log_adapter.get_recent(n=limit, category=category)
754
+ entries = [_enrich_training(e) for e in entries]
755
+ return jsonify({
756
+ "logs": entries,
757
+ "count": len(entries),
758
+ "stats": _log_adapter.get_stats(),
759
+ })
760
+ except Exception as exc:
761
+ logger.exception(f"[api_logs_recent] error: {exc}")
762
+ return jsonify({"logs": [], "count": 0, "error": str(exc)}), 200
763
+
764
+
765
+ @app.route("/api/ranker/logs/stats", methods=["GET"])
766
+ def api_logs_stats():
767
+ """GET /api/ranker/logs/stats"""
768
+ try:
769
+ return jsonify(_log_adapter.get_stats())
770
+ except Exception as exc:
771
+ logger.exception(f"[api_logs_stats] error: {exc}")
772
+ return jsonify({"total_events": 0, "by_level": {}, "by_category": {},
773
+ "by_asset": {}, "errors": {}, "error": str(exc)}), 200
774
+
775
+
776
+ @app.route("/api/ranker/logs/asset/<asset>", methods=["GET"])
777
+ def api_logs_asset(asset: str):
778
+ """GET /api/ranker/logs/asset/V75?limit=30"""
779
+ try:
780
+ limit = int(request.args.get("limit", 30))
781
+ entries = _log_adapter.get_by_asset(asset, n=limit)
782
+ return jsonify({"asset": asset, "logs": entries, "count": len(entries)})
783
+ except Exception as exc:
784
+ logger.exception(f"[api_logs_asset] error: {exc}")
785
+ return jsonify({"asset": asset, "logs": [], "count": 0, "error": str(exc)}), 200
786
+
787
+
788
+ @app.route("/api/ranker/logs/level/<level>", methods=["GET"])
789
+ def api_logs_level(level: str):
790
+ """GET /api/ranker/logs/level/ERROR?limit=50"""
791
+ try:
792
+ limit = int(request.args.get("limit", 50))
793
+ entries = _log_adapter.get_by_level(level, n=limit)
794
+ return jsonify({"level": level.upper(), "logs": entries, "count": len(entries)})
795
+ except Exception as exc:
796
+ logger.exception(f"[api_logs_level] error: {exc}")
797
+ return jsonify({"level": level.upper(), "logs": [], "count": 0, "error": str(exc)}), 200
798
+
799
+
800
+ @app.route("/api/ranker/logs/export", methods=["GET"])
801
+ def api_logs_export():
802
+ """GET /api/ranker/logs/export?limit=500 β†’ download JSON"""
803
+ try:
804
+ limit = int(request.args.get("limit", 500))
805
+ export_path = Path("/tmp/ranker_logs_export.json")
806
+ _log_adapter.export_json(str(export_path), n=limit)
807
+ return send_file(
808
+ export_path,
809
+ mimetype="application/json",
810
+ as_attachment=True,
811
+ download_name="ranker_logs_export.json",
812
+ )
813
+ except Exception as exc:
814
+ logger.exception(f"[api_logs_export] error: {exc}")
815
+ return jsonify({"error": str(exc)}), 500
816
+
817
+
818
+ @app.route("/api/ranker/logs/clear", methods=["POST"])
819
+ def api_logs_clear():
820
+ """POST /api/ranker/logs/clear β€” no-op for file-based adapter"""
821
+ try:
822
+ _log_adapter.clear_buffer()
823
+ return jsonify({"status": "cleared"})
824
+ except Exception as exc:
825
+ return jsonify({"error": str(exc)}), 500
826
+
827
+
828
 
829
 
830
  @app.route("/")
 
883
 
884
 
885
  if __name__ == "__main__":
886
+ logger.info("=== K1RL QUASAR HUB DASHBOARD SERVICE v2.4 (SELF-CONTAINED) ===")
887
  logger.info(f"Dashboard: http://localhost:{_DASHBOARD_PORT}")
888
  logger.info(f"Log directory: {_LOG_DIR}")
889
  logger.info("Fixes applied:")
890
+ logger.info(" βœ… FIX v2.4: All /api/ranker/logs/* routes inline β€” no Blueprint dependency")
891
+ logger.info(" βœ… FIX v2.4: Training KPI enrichment (_enrich_training) applied on /recent")
892
  logger.info(" βœ… FIX #1: Now reading *.log* (includes rotated files)")
893
  logger.info(" βœ… FIX #2: Improved regex to catch all trade close formats")
894
  logger.info(" βœ… FIX #4: Dedicated exit_price regex (no optional-group ambiguity)")