SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
3.4 KB · 55 lines python
Raw Blame History
1"""Country Atlas: `/countries`, `/countries/{code}` — code accepts ISO-2 (`CA`) or a name slug (`canada`). Cached 300 s."""2from __future__ import annotations34from typing import Any56from fastapi import APIRouter, HTTPException, Request78from companyatlas.api import aggregates as agg9from companyatlas.api import queries as q10from companyatlas.api import serializers as ser11from companyatlas.api.common import cached, cached_response12from companyatlas.db import connection, fetch_all1314ORDER = 2015router = APIRouter(prefix="/api/v1", tags=["countries"])161718@router.get("/countries", summary="Living index per country")19async def countries(request: Request) -> Any:20    return cached_response(request, {"items": await agg.cached_country_rows()}, 300)212223@router.get("/countries/{code}", summary="Country detail")24async def country_detail(code: str, request: Request) -> Any:25    async def produce() -> dict[str, Any] | None:26        async with connection() as conn:27            ref = await agg.resolve_country(conn, code)28            if ref is None:29                return None30            cc = ref["code"]31            rows = await agg.cached_country_rows()32            row = next((r for r in rows if r["code"] == cc), None) or {33                "code": cc, "slug": None, "name": ref["name"], "region": ref.get("region"), "subregion": ref.get("subregion"), "companies": 0, "events_7d": 0,34                "events_30d": 0, "hiring_momentum_30d": None, "activity_score": None, "ai_adoption": None, "industry_mix": [], "lat": ref.get("lat"),35                "lon": ref.get("lon")}36            where, params = q.company_filters(country=cc, status="ACTIVE")37            ids, _t = await q.company_page_ids(conn, where, params, sort="activity", limit=24)38            companies = [ser.company_card(r) for r in await q.fetch_cards_by_ids(conn, ids, sparkline=True)]39            events = await agg.live_events(conn, 20, country=cc)40            movers = await agg.ranking_cards(conn, "most_active", "7d", country=cc, limit=10)41            new_ids, _t2 = await q.company_page_ids(conn, where, params, sort="recent", limit=10)42            new_entrants = [ser.company_card(r) for r in await q.fetch_cards_by_ids(conn, new_ids)]43            series = await fetch_all(conn, "select ms.day, avg(ms.value) as value, avg(ms.confidence) as confidence from metric_series ms "44                                           "join companies c on c.id = ms.company_id where ms.metric = 'activity_score' and c.country = cast(:c as char(2)) "45                                           "and ms.day >= :d group by ms.day order by ms.day", c=cc, d=q.days_ago(90).date())46            industries = await agg.industry_rows(conn, country=cc)47            signals = await fetch_all(conn, "select * from signals where scope = 'country' and scope_key = :c and status = 'active' order by detected_at desc limit 10", c=cc)48        return {**row, "companies_total": row["companies"], "companies": companies, "events": events, "movers": movers, "new_entrants": new_entrants,49                "series": [ser.metric_point(r) for r in series], "industries": [i for i in industries if i["companies"] > 0][:40],50                "signals": [ser.signal(s) for s in signals]}51    payload = await cached(f"country:{code.strip().lower()[:80]}", 300, produce)52    if payload is None:53        raise HTTPException(status_code=404, detail="country not found")54    return cached_response(request, payload, 300)55