Zalaegerszegen élek. Ha egy villanyoszlop kidől, egy játszótéri hinta eltörik, vagy egy utca hónapokig nem kap zebrát ott, ahol kellene, akkor az az információ általában három helyre kerül: egy Facebook-csoportba, ahol elsüllyed; egy telefonbeszélgetésbe a szomszéddal; és sehová máshová. A városvezetés nem látja, a képviselők nem látják, és a lakó sem tudja, hogy van-e rajta kívül, aki szintén ezen bosszankodik.

Ez engem régóta zavart. Nem elsősorban azért, mert politikailag baj, hanem azért, mert rossz az információ-folyam. A lakó tudja mi a probléma, a döntéshozó nem; és nem is tud róla, mert nincs csatorna, amin odaérne az infó. Úgyhogy megcsináltam. Így született a zeghangja.hu — a Zalaegerszeg Hangja, röviden Z!.

Mi ez pontosan?

Egy független, nonprofit közösségi platform. Zalaegerszegen bárki feltehet egy bejelentést: cím, rövid leírás, kategória, fotó, térképen kijelölt hely. Más lakók anonim módon szavazhatnak rá — igen, egyetértek, ez tényleg gond; vagy nem, szerintem ez nem probléma. A szavazat a körzet lakosságával arányosítva súlyozódik, tehát nem az történik, hogy egy nagy körzetből kényelmesen eltapossák egy kis körzet ügyét.

12
Választókerület, saját toplistával
6
AI kategória: út, zöld, köztér, közvilágítás, egyéb, veszély
7
Napos közösségi szavazás „megoldódott"-e

A platform 12 zalaegerszegi választókerületet ismer. Mindegyiknek saját toplistája van, saját képviselővel, saját áttekintéssel. A térkép Leaflet-es, a körzethatárokat GeoJSON-ból rajzolja, és minden aktív bejelentés egy marker. Amikor valaki bejelenti hogy „ez megoldódott" — pl. kijavították a padkát — egy 7 napos közösségi szavazás indul arról, hogy tényleg megoldódott-e. Nem én mondom meg, nem a városvezetés, hanem a körzet lakói.

Miért kellett ide AI?

Először nem terveztem vele. Aztán rájöttem: ha egy lakó elkezdi írni a bejelentését és nem tud választani a „köztér" vagy „zöld" kategória között, akkor vagy rossz kategóriát választ, vagy feladja a felét. Ha két ember ugyanazt a problémát jelenti be két szinten, az két külön szavazás lesz ahelyett, hogy egymást erősítsék.

Itt jön be a GPT-4o-mini. Négy dologra használom, mindegyiket pici, fókuszált prompttal:

  1. Kategória-javaslat. Ahogy gépelsz, a szöveg elemzése alapján villan fel a javasolt kategória. Nem kényszerít — csak segít.
  2. Sürgősség-értékelés. 4 fokozatú skála (alacsony → sürgős). Nyitott lyuk az úttesten az nem olyan, mint egy ledőlt padka.
  3. Duplikátum-felismerés. Az utolsó 50 nyitott bejelentéssel összeveti a tiédet, és ha van hasonló, megmutatja: „hé, ez ugyanaz lehet, szavazz inkább arra".
  4. Tartalom-szűrés. Ha valaki magánügyet ír („szomszéddal bajom van"), üzletieket vagy politikai pamfletet — az LLM megszűri. Nem automatikusan töröl, csak jelzi.
Technikai megjegyzés

A prompt injection ellen system message + JSON-encoded user input a védelem. A felhasználó szövegét sosem interpolálom nyers stringként a promptba — JSON mezőben megy át, és a system message kimondja az LLM-nek, hogy a user-content NEM utasítás. Ez nem tökéletes, de egyszerű, olcsó és nagyjából megállítja a tipikus „ignore previous instructions"-támadásokat.

A 18+ ami nem tárol születési dátumot

Az egyik legizgalmasabb GDPR-döntés. A platform 18+, mert a tartalmi moderáció miatt kiskorúakat nem akarok a rendszerben. De a születési dátum egy érzékeny adat, amit felesleges tárolni: egyetlen dologra kell — ellenőrizni hogy van-e legalább 18 éve. Ehhez nem kell megőrizni.

A megoldás: regisztrációkor megadod a dátumot, a szerver kiszámolja a kort, és a dátumot eldobja. A DB-ben csak egy is_adult: true flag marad. Ez GDPR data minimization, textbook-példa.

Ugyanezt csinálom a lakcímmel. A pontos címet nem tárolom sehol: amikor beírod, a szerver SHA-256-tal hash-eli, plusz meghatározza a körzet-azonosítót. A DB-ben csak a hash és a körzet-ID van. Ha valaki feltörné a rendszert, a pontos címeket nem kapja meg — én magam sem tudom visszafejteni. Ennyi a lényege a one-way hash-nek.

Tech stack röviden

A nagy tanulság: Flask 3.1 + PostgreSQL még mindig remek alap egy ilyen közepes méretű projekthez. Nem kellett microservice, nem kellett React. Jinja2 template-ek, szerver oldali rendering, egy kis progresszív JS az interakcióhoz — és pont ennyi. Az egész app.py ~2200 sor, egy ember el tud igazodni benne egy hétvége alatt.

Biztonság és moderáció

A közösségi platform legnagyobb veszélye nem a hacker — a troll. Ezért a rendszer három dolgot csinál:

Plusz a moderátori admin felület: bejelentéseket elrejteni, felhasználót banolni, hozzászólásokat moderálni, mindet egy UI-ban. És egy biztonsági napló, ami minden sikertelen bejelentkezést, admin műveletet és gyanús eseményt rögzít — email címeket SHA-256 hash-ként, mert a logban sem érdemes PII-t tárolni.

Amit közben megtanultam

Két dolgot.

Az AI nem az „okos" rész. A rendszer 85%-a sima CRUD, szavazás, térképi rendering, GDPR compliance és email-küldés. Az AI csak egy réteg rajta, ami olcsón elvégez négy dolgot, amit egyébként az emberre hárítanék. Ha ezt a négy dolgot veled csináltatnám meg egy bonyolultabb űrlapon, feladnád a harmadát.

A privacy nem compliance — termék. Amikor úgy építesz, hogy a pontos címet sose tárolod, a dátumot eldobod, a jelszót bcrypt-elt egyedi salttal őrzöd, a logot email-hash-sel vezeted — nemcsak a GDPR-al vagy rendben. Azt a fajta platformot építed fel, amit te magad is használni akarnál. Ez vonzza a felhasználókat, nem a pro-forma „Adatkezelési tájékoztató" link a láblécben.

Ki akarod próbálni?

Ott van élesben: zeghangja.hu. Zalaegerszegi lakóknak szól, de ha máshonnan vagy, regisztrálás nélkül is megnézheted a térképet, a toplistákat, és a körzeti áttekintőket.

A kód nyilvános, auditálható: github.com/gaberun24/zeghang. Ha a saját városod lenne a következő, fork-old, cseréld ki a GeoJSON-t és a városnevet, és kész. Ha ezzel kapcsolatban van kérdésed, tanácsod vagy (még jobb) kódoddal segítenél — itt eléredsz.

A város nem az, ami a papíron van. A város az, amit a lakói együtt látnak belőle.