Files
APAW/landing/api/server.py
Deploy Bot dbbf4c32e1 feat(landing): add state API service with real-fit score drill-down
- Add apaw-state-api Flask service (landing/api/server.py) that serves
  agent fit scores, best models, and explanations from real-fit.db
- Add nginx proxy rule: /api/state → apaw-state-api:8080
- Add fit-score drill-down modal (click heatmap cell → score breakdown
  + explanation) in api.js, styles.css, and index.html
- Add real-fit-recalc.py script for offline score recalculation from
  stored SQLite responses
- Add real-fit-engine.py (evaluation engine) and sync-dashboard-data.py
- Add Dockerfile ENTRYPOINT + entrypoint.sh for landing container
- Add docker-compose.ollama.yml for local Ollama inference
- Update kilo.jsonc command models and agent-versions.json
- Regenerate index.standalone.html with latest dashboard data
- Add .gitignore entries for __pycache__, runtime data, and backups
2026-05-27 19:53:40 +01:00

200 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Micro API for landing page — reads live agent configs and returns JSON."""
import json, os, glob, re
from datetime import datetime, timezone
import socketserver
import http.server
PORT = 8080
FALLBACK_DIR = "/usr/share/nginx/html"
def find_dir(sub):
candidates = [
os.path.join(FALLBACK_DIR, sub),
os.path.join(os.path.dirname(__file__), sub),
f"/app/{sub}",
f"./{sub}",
]
for c in candidates:
if os.path.isdir(c):
return c
return None
def find_file(name):
candidates = [
os.path.join(FALLBACK_DIR, "api", name),
os.path.join(os.path.dirname(__file__), name),
f"/app/landing/api/{name}",
]
for c in candidates:
if os.path.isfile(c):
return c
return None
def parse_frontmatter(path):
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
except Exception:
return None
if not content.startswith("---"):
return None
end = content.find("---", 3)
if end == -1:
return None
fm = content[3:end]
data = {}
for line in fm.strip().split("\n"):
m = re.match(r"^(\w+):\s*(.+)$", line)
if m:
data[m.group(1)] = m.group(2).strip()
return data
def load_dashboard_data():
path = find_file("dashboard-data.json")
if not path:
return None
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if data.get("agents"):
return data
except Exception:
pass
return None
def load_real_fit_scores():
candidates = [
os.path.join(os.path.dirname(__file__), "real-fit-report.json"),
os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "real-fit-report.json"),
os.path.join(FALLBACK_DIR, "data", "real-fit-report.json"),
"/app/agent-evolution/data/real-fit-report.json",
]
for path in candidates:
if path and os.path.isfile(path):
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("fit_scores", {})
except Exception:
continue
return {}
def build_state_from_md():
agents_dir = find_dir(".kilo/agents")
commands_dir = find_dir(".kilo/commands")
agents = []
if agents_dir:
for f in sorted(glob.glob(os.path.join(agents_dir, "*.md"))):
fm = parse_frontmatter(f)
if fm and fm.get("model"):
agents.append({
"name": os.path.basename(f).replace(".md", ""),
"model": fm.get("model", ""),
"mode": fm.get("mode", "subagent"),
"description": fm.get("description", ""),
"category": infer_category(fm.get("mode", ""), os.path.basename(f)),
"fit_score": None,
"model_meta": None,
})
commands = []
if commands_dir:
for f in sorted(glob.glob(os.path.join(commands_dir, "*.md"))):
fm = parse_frontmatter(f)
if fm and fm.get("model"):
commands.append({
"name": os.path.basename(f).replace(".md", ""),
"model": fm.get("model", ""),
"mode": fm.get("mode", "command"),
"description": fm.get("description", ""),
"fit_score": None,
})
model_stats = {}
for a in agents:
model_stats[a["model"]] = model_stats.get(a["model"], 0) + 1
for c in commands:
model_stats[c["model"]] = model_stats.get(c["model"], 0) + 1
return {
"generated": datetime.now(timezone.utc).isoformat().replace("+00:00", "") + "Z",
"total_agents": len(agents),
"total_commands": len(commands),
"model_distribution": model_stats,
"agents": agents,
"commands": commands,
}
def build_state():
dashboard = load_dashboard_data()
fit_scores = load_real_fit_scores()
if dashboard:
agents = dashboard.get("agents", [])
for a in agents:
key = a.get("name")
fs = fit_scores.get(key)
if fs:
a["fit_score"] = fs.get("fit")
a["fit_explanation"] = fs.get("explanation")
a["best_model"] = fs.get("model")
state = {
"generated": datetime.now(timezone.utc).isoformat().replace("+00:00", "") + "Z",
"total_agents": dashboard.get("total_agents", 0),
"total_commands": len(dashboard.get("commands", [])),
"model_distribution": dashboard.get("model_distribution", {}),
"agents": agents,
"commands": dashboard.get("commands", []),
}
state["fit_scores"] = fit_scores
return state
return build_state_from_md()
def infer_category(mode, filename):
f = filename.lower()
if "security" in f:
return "Security"
if "devops" in f:
return "DevOps"
if "frontend" in f or "flutter" in f:
return "Frontend"
if "backend" in f or "php" in f or "python" in f or "go" in f:
return "Backend"
if "test" in f or "sdet" in f:
return "QA"
return "Core"
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api/state":
state = build_state()
body = json.dumps(state, ensure_ascii=False).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass # silent
if __name__ == "__main__":
with socketserver.TCPServer(("0.0.0.0", PORT), Handler) as httpd:
print(f"[state-api] listening on :{PORT}")
httpd.serve_forever()