Alarm prolazi kroz viΕ‘e faza od aktivacije do razreΕ‘enja, sa automatskom eskalacijom ako niko ne reaguje.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ALARM LIFECYCLE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β AKTIVACIJA βββΊ ACTIVE βββΊ ESCALATED_1 βββΊ ESCALATED_2 β
β β β β
β βββββββββββΊ RESPONDING βββββββ β
β β β
β βΌ β
β ON_SCENE β
β β β
β βΌ β
β RESOLVED β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Status | Opis |
|---|---|
ACTIVE |
Alarm je aktivan, Δeka reakciju respondera na smeni |
ESCALATED_1 |
Niko nije reagovao za 90s, notifikovani svi responderi |
ESCALATED_2 |
Niko nije reagovao za 150s, notifikovani svi Δlanovi |
RESPONDING |
Neko je preuzeo alarm i ide na lokaciju |
ON_SCENE |
Responder je stigao na lokaciju |
RESOLVED |
Situacija je reΕ‘ena |
CANCELLED |
PoΕ‘iljalac je otkazao alarm |
FALSE_ALARM |
OznaΔeno kao laΕΎni alarm |
βββββββββββββββββββββββββββββββββββββββ
β β
β π΄ β
β DRΕ½I ZA ALARM β
β β
β ββββββββββββββββ 2.1s β
β β
β (pusti za otkazivanje) β
β β
βββββββββββββββββββββββββββββββββββββββ
ZaΕ‘to 3 sekunde?
- SpreΔava sluΔajne klikove
- Daje vreme za odustajanje
- Signal da je akcija namerna
βββββββββββββββββββββββββββββββββββββββ
β β
β π Lokacija: Detektovana β
β β
β Ε ta se deΕ‘ava? (opciono) β
β β
β [Prate me ] [TuΔa ] β
β [Kradu ] [Preti mi ] β
β β
β [ Drugo: __________________ ] β
β β
β [π¨ POΕ ALJI ALARM] β
β β
βββββββββββββββββββββββββββββββββββββββ
async function createAlarm({ location, message, group_id }) {
// 1. Kreiraj alarm
const alarm = await db.insert("alarms", {
group_id,
triggered_by: user._id,
location_lat: location.lat,
location_lng: location.lng,
message,
status: "ACTIVE",
created_at: Date.now(),
});
// 2. NaΔi RESPONDERE na smeni
const activeResponders = await getActiveResponders(group_id);
// 3. Sortiraj po blizini
const sorted = sortByDistance(activeResponders, location);
// 4. Kreiraj alarm_responses za svakog
for (const responder of sorted) {
await db.insert("alarm_responses", {
alarm_id: alarm._id,
user_id: responder.user_id,
notified_at: Date.now(),
distance_meters: responder.distance,
});
}
// 5. PoΕ‘alji Telegram
await sendTelegramAlert(group_id, alarm);
// 6. ZakaΕΎi eskalaciju
await scheduler.runAfter(90_000, "escalateAlarm", { alarm_id: alarm._id });
return alarm;
}βββββββββββββββββββββββββββββββββββββββββββββββ
β π¨ ALARM - OΕ KovaΔiΔ β
β β
β π 300m od tebe (Kod fontane) β
β π¬ "Prate me" β
β π€ Od: Dete MarkoviΔa β
β β
β Ti si 1 od 2 dostupna respondera β
β β
β β±οΈ Eskalacija za: 1:23 β
β β
β [π PREUZIMAM] β
β β
β Ne mogu jer: [ Izaberi razlog βΌ ] β
β β
β ποΈ Videlo: 2 β
Preuzelo: 0 β
βββββββββββββββββββββββββββββββββββββββββββββββ
π¨ *ALARM*
π Lokacija: https://maps.google.com/?q=44.81,20.46
π¬ "Prate me"
π€ Od: Dete MarkoviΔa
π Otvori app: https://patrola.rs/alarm/xyz
Scheduled function se pokreΔe 90 sekundi nakon kreiranja alarma.
async function escalateAlarm({ alarm_id }) {
const alarm = await db.get(alarm_id);
// Ako je veΔ neko preuzeo, ne radi niΕ‘ta
if (alarm.status !== "ACTIVE") return;
// Update status
await db.patch(alarm_id, {
status: "ESCALATED_1",
escalated_1_at: Date.now(),
});
// Notifikuj SVE respondere (ne samo one na smeni)
const allResponders = await getAllResponders(alarm.group_id);
await sendUrgentNotification(allResponders, alarm, "ESCALATED");
// ZakaΕΎi sledeΔu eskalaciju
await scheduler.runAfter(60_000, "escalateAlarmFinal", { alarm_id });
}βββββββββββββββββββββββββββββββββββββββββββββββ
β β οΈ ESKALIRAN ALARM - OΕ KovaΔiΔ β
β β
β Niko od dostupnih respondera nije β
β preuzeo alarm veΔ 90 sekundi! β
β β
β π Kod fontane β
β π¬ "Prate me" β
β β
β [π PREUZIMAM] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
Ako niko nije reagovao ni nakon eskalacije 1.
async function escalateAlarmFinal({ alarm_id }) {
const alarm = await db.get(alarm_id);
if (alarm.status !== "ESCALATED_1") return;
await db.patch(alarm_id, {
status: "ESCALATED_2",
escalated_2_at: Date.now(),
});
// Notifikuj SVE Δlanove grupe (ukljuΔujuΔi RODITELJE)
const allMembers = await getAllMembers(alarm.group_id);
await sendCriticalNotification(allMembers, alarm);
// Pozovi admina direktno (opciono)
await callAdmin(alarm.group_id, alarm);
}βββββββββββββββββββββββββββββββββββββββββββββββ
β π KRITIΔNO - ALARM BEZ ODGOVORA β
β β
β Alarm je aktivan veΔ 2.5 minuta β
β i NIKO nije preuzeo! β
β β
β Ako ste u blizini Ε‘kole KovaΔiΔ, β
β molimo reagujte! β
β β
β [OTVORI DETALJE] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
async function takeAlarm({ alarm_id, taken_by }) {
const alarm = await db.get(alarm_id);
if (!["ACTIVE", "ESCALATED_1", "ESCALATED_2"].includes(alarm.status)) {
throw new Error("Alarm nije u stanju za preuzimanje");
}
// Update alarm
await db.patch(alarm_id, {
status: "RESPONDING",
responded_at: Date.now(),
});
// Update alarm_response
await db.patch(responseId, {
response: "ACCEPTED",
response_at: Date.now(),
eta_minutes: calculateETA(responderLocation, alarmLocation),
});
// OTKAΕ½I zakazanu eskalaciju
await scheduler.cancel(escalationJobId);
// Telegram update
await sendTelegramUpdate(`β
${taken_by} je preuzeo alarm`);
}βββββββββββββββββββββββββββββββββββββββββββββββ
β β
Preuzeo si alarm β
β β
β π Navigacija do lokacije: β
β [OTVORI GOOGLE MAPS] β
β β
β π Pozovi dete: +381 63 xxx xxxx β
β π Pozovi roditelja: +381 64 xxx xxxx β
β β
β Javi kad stigneΕ‘: β
β [β
STIGAO SAM] β
β β
β Problem? β
β [β MORAM DA ODUSTANEM] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββ
β π¨ ALARM β
β β
β β
Petar je preuzeo β
β π Udaljen ~400m β
β β±οΈ ETA: ~4 min β
β β
β [π VIDI LOKACIJU] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββ
β β
PomoΔ je na putu! β
β β
β π€ Petar P. dolazi β
β π Udaljen ~400m β
β β±οΈ OΔekivano vreme: ~4 min β
β β
β Ako je laΕΎna uzbuna: β
β [OTKAΕ½I ALARM] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
async function arriveAtScene({ alarm_id }) {
await db.patch(alarm_id, {
status: "ON_SCENE",
});
await db.patch(responseId, {
arrived_at: Date.now(),
});
await sendTelegramUpdate("π Responder je stigao na lokaciju");
}βββββββββββββββββββββββββββββββββββββββββββββββ
β π Na licu mesta β
β β
β Kad se situacija reΕ‘i, oznaΔi: β
β β
β [β
REΕ ENO - Sve OK] β
β β
β [β οΈ REΕ ENO - Potrebna intervencija] β
β (treba pozvati policiju, roditelje...) β
β β
β [β LAΕ½NI ALARM] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββ
β Kako je reΕ‘eno? β
β β
β [β] Situacija procenjena kao neaktuelna β
β [β] Dete sprovedeno do bezbedne lokacije β
β [β] Pozvana policija β
β [β] Pozvani roditelji β
β [β] Drugo β
β β
β Dodatne napomene: β
β [ ___________________________________ ] β
β β
β [ZAVRΕ I] β
β β
βββββββββββββββββββββββββββββββββββββββββββββββ
async function resolveAlarm({ alarm_id, resolution, notes }) {
await db.patch(alarm_id, {
status: "RESOLVED",
resolved_at: Date.now(),
resolution_notes: notes,
resolved_by: user._id,
});
// Update responder statistike
await updateResponderStats(responderId, {
alarmsResolved: increment(1),
// ...
});
// Telegram
await sendTelegramUpdate("β
Alarm razreΕ‘en");
// Audit log
await db.insert("audit_log", {
action: "ALARM_RESOLVED",
alarm_id,
user_id: user._id,
details: JSON.stringify({ resolution, notes }),
});
} CANCELLED
β²
β
ββββββββββββββββββββββββββ€
β β
β β
βΌ β
ACTIVE ββββββ90sβββββΊ ESCALATED_1 ββββββ60sβββββΊ ESCALATED_2
β β β
β β β
β βββββββββββββββββββββ΄ββββββββββββββββββββββββββ
β β
β β neko preuzme
βΌ βΌ
RESPONDING
β
β stigao
βΌ
ON_SCENE
β
β reΕ‘i
βΌ
RESOLVED βββββββββββββββββββββββββββββββββββββββββ FALSE_ALARM
T=0 Alarm kreiran, status: ACTIVE
β Notifikovani responderi na smeni (2)
T=15s Responder 1 video alarm
T=32s Responder 1 kliknuo "Ne mogu" (razlog: na poslu)
T=45s Responder 2 video alarm
T=52s Responder 2 kliknuo "PREUZIMAM"
β Status: RESPONDING
β Eskalacija otkazana
T=4min Responder 2 kliknuo "STIGAO SAM"
β Status: ON_SCENE
T=9min Responder 2 oznaΔio kao RESOLVED
β Status: RESOLVED
β Ukupno vreme: 9 minuta
| Situacija | PonaΕ‘anje |
|---|---|
| Responder preuzme pa odustane | Alarm se vraΔa u ACTIVE, eskalacija kreΔe ispoΔetka |
| Dva respondera preuzmu istovremeno | Oba se beleΕΎe kao ACCEPTED, koordinacija u app-u |
| PoΕ‘iljalac otkaΕΎe dok neko ide | Responder dobija notifikaciju "Alarm povuΔen" |
| Niko ne reaguje ni na broadcast | Ostaje u ESCALATED_2, admin notifikovan |
Dokument kreiran: Januar 2026