Многоагентный аудит + имплементация: один воркер на файл, точечные правки. Верификация: py_compile (47/47 .py) + tsc --noEmit (0 ошибок). Unit-тесты не прогонялись (окружение не поднято: rollup native dep / нет pytest-venv). Полностью исправлено (169): #1336, #1337, #1339, #1340, #1341, #1342, #1343, #1345, #1346, #1348, #1349, #1350, #1351, #1354, #1356, #1358, #1359, #1360, #1362, #1364, #1365, #1366, #1367, #1368, #1369, #1370, #1371, #1372, #1373, #1374, #1375, #1376, #1377, #1378, #1379, #1380, #1381, #1382, #1384, #1385, #1386, #1387, #1388, #1389, #1390, #1391, #1392, #1394, #1395, #1396, #1397, #1399, #1400, #1401, #1402, #1403, #1404, #1408, #1409, #1410, #1411, #1412, #1413, #1414, #1415, #1416, #1417, #1418, #1420, #1423, #1425, #1426, #1427, #1428, #1429, #1430, #1431, #1432, #1433, #1434, #1435, #1437, #1438, #1439, #1440, #1441, #1442, #1443, #1444, #1445, #1446, #1447, #1448, #1449, #1450, #1451, #1452, #1453, #1454, #1455, #1456, #1457, #1458, #1459, #1460, #1461, #1462, #1463, #1464, #1465, #1466, #1467, #1468, #1469, #1471, #1472, #1473, #1474, #1476, #1478, #1479, #1481, #1482, #1483, #1484, #1485, #1487, #1488, #1489, #1490, #1491, #1492, #1493, #1494, #1495, #1496, #1497, #1499, #1500, #1501, #1502, #1504, #1505, #1506, #1507, #1510, #1514, #1515, #1516, #1517, #1518, #1519, #1521, #1522, #1523, #1524, #1525, #1526, #1527, #1528, #1529, #1531, #1532, #1533, #1534, #1535, #1536, #1537, #1538 Частично (9, in-file часть, остаток cross-file): #1361, #1419, #1422, #1424, #1470, #1475, #1477, #1480, #1498 Требуют cross-file (3, не тронуты): #1338, #1363, #1421 Пропущено (1): #1539 Не входило в партию: 22 needs-Leha issue (нужны решения владельца). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
"""Add more POI categories: parking, gym/fitness, theater, taxi, marketplaces, etc."""
|
|
import sqlite3, pathlib, requests, time, json, math, urllib3
|
|
urllib3.disable_warnings()
|
|
|
|
DB = pathlib.Path(__file__).parent / "analysis.db"
|
|
CACHE = pathlib.Path(__file__).parent / "cache" / "overpass_raw.json"
|
|
|
|
NEW_QUERIES = [
|
|
("parking", '["amenity"="parking"]'),
|
|
("gym", '["leisure"="fitness_centre"]'),
|
|
("theater", '["amenity"="theatre"]'),
|
|
("cinema", '["amenity"="cinema"]'),
|
|
("marketplace", '["amenity"="marketplace"]'),
|
|
("museum", '["tourism"="museum"]'),
|
|
("hotel", '["tourism"~"^(hotel|hostel|apartment)$"]'),
|
|
("nightclub", '["amenity"~"^(nightclub|bar|pub)$"]'),
|
|
("car_wash", '["amenity"="car_wash"]'),
|
|
("car_rental", '["amenity"~"^(car_rental|taxi)$"]'),
|
|
("vet", '["amenity"="veterinary"]'),
|
|
("courier", '["amenity"~"^(parcel_locker|post_box)$"]'),
|
|
("kindergarten_priv", '["amenity"="kindergarten"]["fee"="yes"]'), # platnyye
|
|
("dentist", '["amenity"="dentist"]'),
|
|
("childcare", '["amenity"="childcare"]'),
|
|
]
|
|
ENDPOINTS = [
|
|
"https://overpass-api.de/api/interpreter",
|
|
"https://overpass.kumi.systems/api/interpreter",
|
|
]
|
|
|
|
def hav(la1,lo1,la2,lo2):
|
|
R=6371000;p1,p2=math.radians(la1),math.radians(la2)
|
|
dp=math.radians(la2-la1);dl=math.radians(lo2-lo1)
|
|
a=math.sin(dp/2)**2+math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
|
|
return 2*R*math.asin(math.sqrt(a))
|
|
|
|
def overpass(filt, b):
|
|
q = f'[out:json][timeout:120];(node{filt}({b[0]},{b[1]},{b[2]},{b[3]});way{filt}({b[0]},{b[1]},{b[2]},{b[3]}););out center tags;'
|
|
last = None
|
|
for ep in ENDPOINTS:
|
|
try:
|
|
r = requests.post(ep, data={"data": q}, timeout=180, verify=False,
|
|
headers={"User-Agent":"gendesign/0.7"})
|
|
if r.ok: return r.json().get("elements", [])
|
|
last = f"{ep} {r.status_code}"
|
|
time.sleep(2)
|
|
except Exception as e:
|
|
last = f"{ep} {e}"; time.sleep(2)
|
|
raise RuntimeError(last)
|
|
|
|
def main():
|
|
cache = json.loads(CACHE.read_text()) if CACHE.exists() else {}
|
|
conn = sqlite3.connect(DB)
|
|
sites = conn.execute("SELECT site_id, lat, lon FROM sites").fetchall()
|
|
lats=[s[1] for s in sites]; lons=[s[2] for s in sites]
|
|
b = (min(lats)-0.05, min(lons)-0.05, max(lats)+0.05, max(lons)+0.05)
|
|
|
|
for cat, filt in NEW_QUERIES:
|
|
if cat in cache:
|
|
print(f" {cat:<20} cached: {len(cache[cat])}")
|
|
continue
|
|
print(f" {cat:<20} fetching...", end=" ", flush=True)
|
|
try:
|
|
elems = overpass(filt, b)
|
|
cache[cat] = elems
|
|
print(f"{len(elems)}")
|
|
except Exception as e:
|
|
print(f"FAIL {e}")
|
|
cache[cat] = []
|
|
time.sleep(2)
|
|
|
|
CACHE.write_text(json.dumps(cache, ensure_ascii=False))
|
|
print(f"\nCache: {len(cache)} categories, {CACHE.stat().st_size/1024:.0f} KB")
|
|
|
|
# rebuild pois table
|
|
conn.execute("DELETE FROM pois")
|
|
n = 0
|
|
for cat, elems in cache.items():
|
|
for el in elems:
|
|
if el["type"] == "node":
|
|
la, lo = el.get("lat"), el.get("lon")
|
|
else:
|
|
c = el.get("center") or {}
|
|
la, lo = c.get("lat"), c.get("lon")
|
|
if la is None: continue
|
|
tags = el.get("tags") or {}
|
|
name = tags.get("name") or tags.get("operator") or ""
|
|
for sid, sla, slo in sites:
|
|
d = hav(sla, slo, la, lo)
|
|
if d <= 2000:
|
|
conn.execute("""INSERT INTO pois(site_id,category,osm_type,osm_id,name,lat,lon,distance_m,raw_tags)
|
|
VALUES (?,?,?,?,?,?,?,?,?)""",
|
|
(sid, cat, el["type"], str(el["id"]), name, la, lo, d, json.dumps(tags, ensure_ascii=False)))
|
|
n += 1
|
|
conn.commit()
|
|
print(f"recomputed {n} site-poi pairs")
|
|
print("\nTop categories:")
|
|
for c, n in conn.execute("SELECT category, count(*) FROM pois GROUP BY 1 ORDER BY 2 DESC LIMIT 25").fetchall():
|
|
print(f" {c:<20} {n}")
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|