def create_app(prefix: str = "") -> FastAPI:
"""Create the Yoro FastAPI application.
Args:
prefix: Optional URL prefix for all routes (e.g. ``"/api/v1/yoro"``).
"""
app = FastAPI(
title="Yoro API",
description="Geographic addressing via Hilbert curves — GPS ↔ compact codes",
version=__version__,
)
@app.get(f"{prefix}/encode", response_model=EncodeResponse)
def encode_endpoint(
lat: float = Query(..., description="Latitude (WGS 84)"),
lon: float = Query(..., description="Longitude (WGS 84)"),
domain: str = Query("CI", description="Domain ISO code"),
precision: int = Query(12, ge=1, le=20, description="Hilbert order"),
) -> EncodeResponse:
domain = domain_for_country(domain)
try:
code_str = encode(lat, lon, precision=precision, domain=domain)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
decoded = decode(code_str)
res = resolution(decoded["precision"], domain=domain)
return EncodeResponse(
code=code_str,
lat=decoded["lat"],
lon=decoded["lon"],
precision=decoded["precision"],
resolution_m=round(res, 2),
bounds=BoundsSchema(**decoded["bounds"]),
)
@app.get(f"{prefix}/decode", response_model=DecodeResponse)
def decode_endpoint(
code: str = Query(..., description="Yoro string (e.g. CI-4H7A3B)"),
) -> DecodeResponse:
if "-" not in code:
raise HTTPException(
status_code=400,
detail="code must contain a dash (e.g. 'CI-4H7A3B')",
)
try:
decoded = decode(code)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
res = resolution(decoded["precision"], domain=decoded["domain"])
nbrs = neighbors(code)
return DecodeResponse(
code=code.upper(),
lat=decoded["lat"],
lon=decoded["lon"],
precision=decoded["precision"],
domain=decoded["domain"],
resolution_m=round(res, 2),
bounds=BoundsSchema(**decoded["bounds"]),
neighbors=nbrs,
)
@app.get(f"{prefix}/precisions", response_model=PrecisionsResponse)
def precisions_endpoint(
domain: str = Query("CI", description="Domain ISO code"),
max_code_length: int = Query(10, ge=1, le=15, description="Max code characters"),
) -> PrecisionsResponse:
domain = domain_for_country(domain)
try:
levels = precision_levels(domain=domain, max_code_length=max_code_length)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return PrecisionsResponse(
domain=domain,
levels=[PrecisionLevelSchema(**lv) for lv in levels],
)
@app.get(f"{prefix}/domains", response_model=DomainsResponse)
def domains_endpoint() -> DomainsResponse:
result = {}
for key, dom in DOMAINS.items():
result[key] = DomainSchema(
name=dom["name"],
bounds=BoundsSchema(
lat_min=dom["lat_min"],
lat_max=dom["lat_max"],
lon_min=dom["lon_min"],
lon_max=dom["lon_max"],
),
)
return DomainsResponse(domains=result)
@app.get(f"{prefix}/health")
def health() -> dict:
return {"status": "ok", "version": __version__}
return app