GUILD5
# guild5/api.py — guild5.net
# portfolio を座標に置いて最短巡回路を返す API。
from fastapi import FastAPI, HTTPException
from heapq import heappush, heappop
from dataclasses import dataclass
from pathlib import Path
import math, json
ROOT, SITE = Path(__file__).parent, "guild5.net"
app = FastAPI(title="GUILD5", contact={"url": SITE})
@dataclass(frozen=True)
class Node:
slug: str
pos: tuple[float, float]
stack: tuple[str, ...] = ()
NOTE = ROOT / "about.md"
def tour(ns: list[Node], start: int = 0) -> list[int]:
"""portfolio を1周する最短路。heap で Held-Karp。"""
goal, seen = (1 << len(ns)) - 1, {}
heap = [(0.0, start, 1 << start, (start,))]
while heap:
c, i, mask, path = heappop(heap)
if mask == goal:
return list(path)
for j, nd in enumerate(ns):
nc = c + math.dist(ns[i].pos, nd.pos)
key = (j, mask | 1 << j)
if mask >> j & 1 or seen.get(key, math.inf) <= nc:
continue
seen[key] = nc
heappush(heap, (nc, j, key[1], path+(j,)))
raise HTTPException(404, "no tour")
@app.get("/portfolio")
async def visit(tag: str = "python"):
data = json.loads((ROOT / "works.json").read_text())
ns = [Node(**w) for w in data if tag in w["stack"]]
return [ns[i] for i in tour(ns)]
@app.get("/about")
async def note() -> str:
return NOTE.read_text()
@app.get("/contact")
async def mail() -> str:
return f"hello@{SITE}"