File size: 3,519 Bytes
0939b04
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""SolarFit API μ„œλ²„."""
from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
import httpx
from api.analysis import analyze
from api.models import AnalysisResult, ScanRequest, ScanResult
from api.config import VWORLD_API_KEY, VWORLD_SEARCH_URL
from api.scanner import scan_region

app = FastAPI(title="SolarFit API", version="0.1.0")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173", "http://localhost:5174", "http://localhost:3000", "https://solarfit-beta.vercel.app", "https://limtaek-solarfit-api.hf.space"],
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/api/health")
def health():
    return {"status": "ok"}


@app.get("/api/analyze", response_model=AnalysisResult)
def analyze_location(
    lat: float = Query(..., description="μœ„λ„ (WGS84)", ge=33.0, le=39.0),
    lon: float = Query(..., description="경도 (WGS84)", ge=124.0, le=132.0),
):
    """μ’Œν‘œ β†’ νƒœμ–‘κ΄‘ μž…μ§€ 뢄석 κ²°κ³Ό."""
    try:
        return analyze(lat=lat, lon=lon)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


def _vworld_search(q: str, search_type: str, category: str | None = None) -> list[dict]:
    """VWorld 단일 νƒ€μž… 검색 β€” κ²°κ³Ό μ—†μœΌλ©΄ []."""
    base = {
        "service": "search",
        "request": "search",
        "key": VWORLD_API_KEY,
        "query": q,
        "type": search_type,
        "format": "json",
        "size": "5",
        "crs": "EPSG:4326",
    }
    if category:
        base["category"] = category
    r = httpx.get(VWORLD_SEARCH_URL, params=base, timeout=10)
    r.raise_for_status()
    body = r.json()
    if body.get("response", {}).get("status") != "OK":
        return []
    items = body.get("response", {}).get("result", {}).get("items", [])
    results = []
    for item in items:
        addr = item.get("address", {})
        label = addr.get("road") or addr.get("parcel") or ""
        results.append({
            "title": item.get("title") or label,
            "address": label,
            "lat": float(item.get("point", {}).get("y", 0)),
            "lon": float(item.get("point", {}).get("x", 0)),
        })
    return results


@app.post("/api/scan", response_model=ScanResult)
def scan(req: ScanRequest):
    try:
        return scan_region(req.lat_min, req.lat_max, req.lon_min, req.lon_max, req.threshold)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/api/search")
def search_address(q: str = Query(..., description="μ£Όμ†Œ λ˜λŠ” μž₯μ†Œλͺ…")):
    """VWorld 검색 API β€” μž₯μ†Œλͺ…Β·λ„λ‘œλͺ… μ£Όμ†Œ β†’ μ’Œν‘œ."""
    if not VWORLD_API_KEY:
        return {"results": []}
    try:
        # μž₯μ†Œλͺ…(POI) 검색 β€” category λΆˆν•„μš”
        place_results = _vworld_search(q, "PLACE")
        # λ„λ‘œλͺ… μ£Όμ†Œ 검색 β€” category=ROAD ν•„μˆ˜
        road_results = _vworld_search(q, "ADDRESS", "ROAD")

        seen: set[tuple] = set()
        merged = []
        for item in place_results + road_results:
            key = (round(item["lat"], 5), round(item["lon"], 5))
            if key not in seen:
                seen.add(key)
                merged.append(item)
            if len(merged) >= 5:
                break

        return {"results": merged}
    except (httpx.TimeoutException, httpx.RequestError, httpx.HTTPStatusError, ValueError, KeyError) as e:
        raise HTTPException(status_code=502, detail=f"VWorld search error: {e}")