01Projektübersicht
Alarmkartezeigt, wo sich gerade ein Luftangriffsalarm befindet: nach Oblast und nach Bezirk, mit beweglichen Zielen auf der Karte, der Frontlinie, thermischen Punkten von Satelliten und einem Feed von Überwachungskanälen darunter. Es wird von Menschen gelesen, die die Antwort in Sekunden benötigen, und es wird auf einem Telefon, nachts, bei schlechtem mobilen Internet gelesen.
Die Seite ist jedoch nur ein Teil des Produkts. Die gleichen Daten werden über eine offene JSON-API mit Schlüsseln und Ratenlimits sowie über ein Widget, das andere Websites in einen eigenen Rahmen einbetten, bereitgestellt. Wir haben alles entworfen und gebaut: die Sammler, die Speicherung, die API, die Karte und das Widget.
02Kontext und das Problem
Die Daten, die die Frage «Gibt es hier einen Alarm?» beantworten, existieren nicht an einem Ort. Offizielle Alarme kommen nach Oblast. Lokale Alarme für Gemeinden stammen von einem anderen Dienst. Wo sich die Ziele befinden, wird in Worten von Überwachungskanälen geschrieben. Die Frontlinie wird von einem Mapping-Projekt veröffentlicht. Brände werden von Satelliten gesehen. Fünf Quellen, fünf Formate, fünf verschiedene Vorstellungen davon, was «jetzt» bedeutet.
Das zweite Problem ist die Stille. Jede dieser Quellen kann aufhören — ein Kanal wird still, eine API beginnt, Zeitüberschreitungen zu haben — und der ehrliche Fehlerzustand ist es, dies zu sagen, nicht das Bild von gestern weiter zu zeichnen. Eine Karte, die ruhig aussieht, weil ihre Quelle gestorben ist, ist schlimmer als keine Karte.
03Projektziele
- ein Bild, das aus fünf unabhängigen Quellen zusammengesetzt ist, jede mit ihrer eigenen Aktualisierungsuhr;
- eine Antwort in Sekunden auf einem Telefon, bei einer schwachen Verbindung, nachts;
- Daten, die die Seite verlassen können: eine offene API mit Schlüsseln, Limits und Tarifen sowie ein einbettbares Widget;
- drei Sprachen, weil das Publikum nicht nur lokal ist;
- eine Quelle, die verstummt ist, muss als stumm sichtbar sein, nicht als ruhig;
- die eigene Adresse des Servers darf niemals die Quellen erreichen, die sie liest.
04Fünf Quellen, ein Bild
- Offizielle Warnungen nach Oblast — das Rückgrat der Karte;
- lokale Warnungen für Gemeinden und Bezirke, von einem separaten Dienst;
- Überwachungskanäle in Telegram — sowohl aus öffentlichen Vorschauen als auch, für geschlossene Kanäle, über das Messaging-Protokoll selbst lesen;
- die Frontlinie, von einem Mapping-Projekt, das es veröffentlicht;
- thermische Punkte von NASA-Satelliten — Brände, die aus dem Orbit gesehen werden.
Sammler laufen als zwei langlebige Arbeiter unter dem System-Supervisor, jeder wird für sich selbst neu gestartet und schreibt in den lokalen Speicher. Die Website kommuniziert niemals direkt mit einer Quelle: Sie liest, was die Sammler bereits geschrieben haben, weshalb eine langsame Quelle die Karte älter macht, anstatt sie unzugänglich zu machen.
05Vier Dateien statt einer Datenbank
Die Daten leben in vier separaten SQLite-Dateien: dem Alarmprotokoll, den Push-Abonnements und ihrem status pro Region, den Kartenereignissen mit dem Feed und den API-Schlüsseln mit ihren Zählerständen. Es gibt absichtlich keine gemeinsame Datenbank — der Ausfall einer Datei darf die anderen nicht beeinträchtigen. Wenn Push-Abonnements nicht verfügbar sind, wird die Karte trotzdem gezeichnet; wenn der Schlüssel-Speicher beschäftigt ist, antwortet die Seite trotzdem.
Die gleichen Dateien werden vom Kontrollpanel der gesamten Gruppe geschrieben — einen Schlüssel ausgeben, einen Alarmstatus zurücksetzen — sodass jede Verbindung mit einer Wartezeit geöffnet wird. SQLite gibt standardmäßig sofort auf einer beschäftigten Datenbank auf, und «sofort» würde hier eine Fehlerseite bedeuten, während ein Arbeiter lediglich eine Zeile schrieb.
06Drei Uhren auf einer Seite
Verschiedene Daten altern mit unterschiedlichen Geschwindigkeiten, daher aktualisieren sie sich mit unterschiedlichen Geschwindigkeiten: Alarme alle 15 Sekunden, bewegende Ziele alle 10, der Feed alle 20. Ein gemeinsames Intervall wäre in beide Richtungen gleichzeitig falsch — zu langsam für Ziele, verschwenderisch für den Feed und eine unnötige Anfrage alle paar Sekunden für Tausende von offenen Tabs.
Jeder Block trägt auch sein eigenes «zuletzt aktualisiert». Eine Seite, die lautlos neu gezeichnet wird, lehrt dich, ihr genau dann zu vertrauen, wenn sie nicht vertraut werden sollte; ein Zeitstempel, der sich nicht mehr bewegt, ist der schnellste Weg zu sagen «dieser Teil ist veraltet».
07Eine offene API mit Schlüsseln und Tarifen
Die Karte veröffentlicht, was sie weiß: aktuelle Warnungen, Ereignisse, einen Analyse-Endpunkt mit einer Heatmap von «oblast × Zeitbucket», den Rhythmus des Tages, Zieltypen und Richtungen. Anonyme Anrufer haben ein bescheidenes Limit pro Minute; ein Schlüssel erhöht es, und die Tarife steigen in Stufen für diejenigen, die darauf aufbauen.
Schlüssel werden vom gemeinsamen Kontrollpanel ausgegeben, niemals von Hand eingegeben, und die Nutzung wird pro Schlüssel und Tag gezählt. Ein Endpunkt ist absichtlich für alle außer dem Besitzer des Schlüssels geschlossen — derjenige, der einem Schlüssel seinen eigenen Tarif, Limit und Verbrauch mitteilt. Alles andere ist offen, denn eine öffentliche Karte, die ihre Daten verbirgt, ist nur halb ein öffentlicher Dienst.
08Ein Widget, das auf den Websites anderer lebt
Das Widget ist der Teil des Produkts, der das Zuhause verlässt. Zwei feste Größen — ein Statusstreifen und ein quadrantes Panel — drei Sprachen, zwei Themen und eine Rahmenrichtlinie, die speziell so geschrieben wurde, dass das Einbetten hier und nirgendwo sonst auf der Seite erlaubt ist: die Karte und das Widget dürfen gerahmt werden, das Konto und die Support-Seiten dürfen es nicht.
Da es in einem Rahmen lebt, hat das Widget keine eigenen Cookies und wählt keine Schnittstellengeneration: es sieht für alle gleich aus. Das ist eine Entscheidung, keine Einschränkung — ein Widget, das je nach den vorherigen Entscheidungen eines Besuchers unterschiedlich dargestellt wird, wäre unmöglich auf der Seite eines anderen zu unterstützen.
09Push, der deine Region kennt
Ein Abonnement ist an eine Region gebunden, und der Zustand jeder Region wird neben den Abonnements gehalten. Das ist es, was eine Benachrichtigung an den Rand bringt — wenn die Warnung in deiner oblast beginnt oder endet — anstatt bei jedem Refresh der Quelle. Der Unterschied ist das ganze Produkt: das eine ist ein Signal, das andere ist Lärm, den die Leute innerhalb eines Tages ausschalten.
Es gibt eine native Anwendung, die auf denselben Daten basiert und mit einer App-API kommuniziert, anstatt mit den Webseiten. Ein eigener Gesundheitsendpunkt beantwortet das Flottenverzeichnis, da eine Lebensprüfungsüberprüfung nicht teurer sein darf als die Arbeit, die sie überprüft.
10Drei Sprachen, einschließlich des Widgets
Die Karte spricht Ukrainisch, Englisch und Russisch, ebenso wie die API-Dokumentation, die Kontoseiten und das Widget. Ein neuer Schnittstellenstring wird gleichzeitig zu allen dreien hinzugefügt und eine Überprüfung zählt die fehlenden: die Anzahl muss null sein. Halbübersetzte Schnittstellen kündigen sich nicht an – sie zeigen leise eine Sprache innerhalb einer anderen und sehen für denjenigen, der sie geschrieben hat, gut aus.
Regionennamen sind eine eigene Geschichte: Sie stammen aus den Quellen in ihrer eigenen Schreibweise, sodass die Karte ein eigenes Wörterbuch führt, anstatt dem zu vertrauen, was im Feed angekommen ist.
11Quellen lesen, ohne deine Adresse anzuzeigen
Alles, was die Sammler abrufen, geht über einen SOCKS5-Proxy, einschließlich des Messaging-Protokolls, das für geschlossene Kanäle verwendet wird – das Proxysystem dort erfolgt im Code, ohne eine zusätzliche Abhängigkeit. Die eigene Adresse des Servers erreicht niemals eine Quelle, und eine Quelle, die sich entscheidet, jemanden zu blockieren, blockiert den Proxy, nicht die Maschine, die die Karte bereitstellt.
Der Ratenbegrenzer sitzt aus demselben Grund umgekehrt vor der API: Ein öffentlicher Endpunkt mit Live-Daten zieht Scraper an, und ein Limit pro Schlüssel pro Minute sorgt dafür, dass ein öffentlicher Dienst für die Menschen, für die er geschrieben wurde, nutzbar bleibt.
12Technologie-Stack
- PHP 8.3 ohne ein Framework: ein kleiner Router, Endpunkte und Renderer;
- Python-Arbeiter unter dem Systemüberwacher – einer nur mit der Standardbibliothek, einer, der das Messaging-Protokoll spricht;
- SQLite, vier separate Dateien, jede mit ihrem eigenen Grund zu existieren;
- Leafletfür die Karte, gehashte Assets, ein Widget mit festen Größen;
- Web-Push mit zustandsabhängiger Region, eine native App mit denselben Daten;
- SOCKS5-Ausgang für jede ausgehende Anfrage, pro Schlüssel Rate-Limits vor der API.
13Das Ergebnis
Eine Karte, die in Sekunden antwortet und weiter antwortet, wenn eine ihrer fünf Quellen ausfällt. Daten, die andere Seiten nutzen können: eine offene API mit Schlüsseln und Tarifen sowie ein Widget in drei Sprachen und zwei Themen. Benachrichtigungen, die am Rand eines Ereignisses ausgelöst werden, anstatt bei jeder Abfrage. Eine native Anwendung mit denselben Daten und ein Kontrollpanel, in dem Schlüssel ausgegeben und Zustände zurückgesetzt werden.
14Erkenntnisse
Drei Dinge, die wir in jedes Projekt mit Live-Daten mitnehmen würden.Trenne den Sammler von der Seite: Eine Website, die eine Quelle abruft, während ein Besucher wartet, ist eine Website, die immer dann ausfällt, wenn die Quelle ausfällt.Gib jedem Block seine eigene Uhr und seinen eigenen Zeitstempel: Ein einzelnes Intervall ist in beide Richtungen falsch, und ein stilles Neuzeichnen verbirgt die Veralterung.Sag, wenn du es nicht weißt: Ein leerer Block, der es zugibt, ist mehr wert als eine ruhig aussehende Karte mit den Daten von gestern.
Und eine Produktlektion: In dem Moment, in dem Daten anderswo eingebettet werden können, wird das Widget zum Produkt. Es benötigt feste Größen, eine eigene Rahmenrichtlinie und Übersetzungen – die gleiche Sorgfalt wie die Hauptseite, nicht ein Überbleibsel davon.
Wenn du Live-Daten hast und eine Karte, eine API und ein Widget benötigst, die andere Seiten verwenden können, ist das die Art von Arbeit, die wir von Anfang bis Ende machen.



