(function () {
"use strict";
const REG = [
{
num: "7271UV",
type: "BRW SIP",
station: "BRW SIP платформа (Алматы)",
city: "Алматы",
desc: "Основной оконечный номер экстренных служб",
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ОПТС-6, Алматы)",
},
{
num: "727103",
type: "BRW SIP",
station: "BRW SIP платформа (7273130333)",
city: "Алматы",
desc: "Номер пересчёта принадлежит BRW SIP 7273130333",
reserve: "Есть: SIP Trunk 7272222222 (принадлежит ОПТС-6, Алматы)",
},
{
num: "7272222222",
type: "ТДМ",
station: "ОПТС-6 (Алматы)",
city: "Алматы",
desc: "Резервный номер в SIP Trunk, принадлежит ОПТС-6",
reserve: "Резерв для 7271UV / 727103",
},
];
const STATIONS = {
almaty: {
name: "Алматы",
x: 520, y: 505,
sub: { label: "ОПТС3 · ОПТС-72 · ОПТС-6", dx: 0, dy: 14, anchor: "middle" },
op: {
id: "op6", name: "ОПТС-6", x: 578, y: 552, w: 112, h: 26,
note: "резерв 7272222222",
anchor: "start",
},
},
astana: {
name: "Астана",
x: 478, y: 208,
sub: { label: "SSW рег. · SSW транзит", dx: 0, dy: 14, anchor: "middle" },
},
aktobe: { name: "Актобе", x: 198, y: 438 },
kostanay: { name: "Костанай", x: 352, y: 272 },
kokshetau: { name: "Кокшетау", x: 478, y: 268 },
petropavlovsk: { name: "Петропавловск", x: 418, y: 118 },
taraz: { name: "Тараз", x: 438, y: 578 },
shytkent: { name: "Шымкент", x: 468, y: 626 },
pavlodar: { name: "Павлодар", x: 598, y: 162 },
ustkam: { name: "Усть-Каменогорск", x: 738, y: 248 },
};
const NS = "http://www.w3.org/2000/svg";
function normalize(v) {
return String(v == null ? "" : v).replace(/\s+/g, "").toUpperCase();
}
function findEntry(num) {
const n = normalize(num);
if (!n) return null;
return REG.find(function (e) { return normalize(e.num) === n; }) || null;
}
function derive(raw) {
let n = normalize(raw);
n = n.replace(/^(?:[8]\+?7|0)/, "");
if (!/^[0-9A-Z]+$/.test(n)) return null;
const digits = n.replace(/[^0-9]/g, "");
const city = digits.startsWith("727") ? "Алматы" : "неизвестен";
const entry = findEntry(raw);
let owner, reserve, viaStation, isReserve = false;
if (entry) {
owner = ownerByType(entry.type);
reserve = entry.reserve;
viaStation = entry.station;
isReserve = /резерв/i.test(entry.desc);
} else {
owner = digits.length >= 8 ? "pbx" : "zone";
reserve = "Не определён (номер не в реестре)";
viaStation = "неизвестна";
}
const ownerName = ownerLabel(owner);
const endLabel = entry ? "Оконечный номер " + entry.num : "Оконечный номер";
const mobile = [
{ t: "Мобильный оператор" },
{ t: "SSW транзит (ОДС / Астана)" },
{ t: "SSW рег. (регион номера)" },
{ t: endLabel, active: true },
];
const fixed = [
{ t: "Станция владельца номера абонента" },
{ t: "SSW рег. (своя)" },
{ t: "SSW рег. (регион номера)" },
{ t: endLabel, active: true },
];
if (owner === "brw-sip") {
fixed.splice(3, 0, { t: "BRW SIP платформа (стык со всеми SSW рег.)", active: true });
}
if (reserve && !/не определён/i.test(reserve) && !isReserve) {
const rnum = reserve.replace(/^Есть[:\s]*/, "").split(/[\s(]/)[0];
mobile.push({ t: "Резерв", sub: rnum + " · ОПТС-6, при недоступности BRW SIP" });
fixed.push({ t: "Резерв", sub: rnum + " · ОПТС-6, при недоступности BRW SIP" });
}
return { entry: entry, city: city, owner: owner, ownerName: ownerName, reserve: reserve, viaStation: viaStation, mobile: mobile, fixed: fixed };
}
function ownerByType(type) {
const t = normalize(type);
if (t.indexOf("BRW") === 0) return "brw-sip";
if (t === "SIP" || t === "GPON") return "pbx";
if (t === "ТДМ") return "own";
return "zone";
}
function ownerLabel(o) {
if (o === "brw-sip") return "BRW SIP платформа";
if (o === "pbx") return "АТС (станция)";
if (o === "own") return "Собственный номер станции";
return "АТС зоны";
}
function renderChain(id, nodes) {
const el = document.getElementById(id);
el.innerHTML = "";
nodes.forEach(function (node, i) {
const d = document.createElement("div");
d.className = "chain-node" + (node.active ? " active" : "");
const s = document.createElement("strong");
s.textContent = node.t;
d.appendChild(s);
if (node.sub) {
const sp = document.createElement("span");
sp.textContent = node.sub;
d.appendChild(sp);
}
el.appendChild(d);
if (i < nodes.length - 1) {
const a = document.createElement("span");
a.className = "chain-arrow";
a.setAttribute("aria-hidden", "true");
a.innerHTML = "→";
el.appendChild(a);
}
});
}
function chip(text, tone) {
const s = document.createElement("span");
s.className = "kt-ai-chip";
if (tone) s.setAttribute("data-tone", tone);
s.textContent = text;
return s;
}
function show(num) {
const d = derive(num);
const detail = document.getElementById("resDetail");
if (!d) {
detail.style.display = "none";
const empty = '
Проверьте номерВведите ABC21UV
';
document.getElementById("chain1").innerHTML = empty;
document.getElementById("chain2").innerHTML = empty;
document.getElementById("r1src").textContent = "";
document.getElementById("r2src").textContent = "";
return;
}
detail.style.display = "block";
document.getElementById("rdNum").textContent = d.entry ? d.entry.num : num.trim().toUpperCase();
const chips = document.getElementById("rdChips");
chips.innerHTML = "";
if (d.entry) {
chips.appendChild(chip(d.entry.type, "blue"));
chips.appendChild(chip(d.entry.station, "gray"));
chips.appendChild(chip("Город: " + d.entry.city, "gray"));
} else {
chips.appendChild(chip("Город: " + d.city, "gray"));
}
document.getElementById("rdMeta").textContent = d.entry ? d.entry.desc : "Номер не найден в реестре — показан базовый маршрут.";
document.getElementById("rdOwn").textContent = "Владелец (по пересчёту): " + d.ownerName;
const r = document.getElementById("rdReserve");
if (/не определён/i.test(d.reserve)) {
r.style.display = "none";
} else {
r.style.display = "block";
r.textContent = "Резерв: " + d.reserve;
}
document.getElementById("r1src").textContent = "Мобильный оператор → SSW транзит (ОДС/Астана) → SSW рег. → " + (d.entry ? d.entry.num : "оконечный номер");
document.getElementById("r2src").textContent = "Станция → SSW рег. (своя) → SSW рег. (регион) → " + (d.entry ? d.entry.num : "оконечный номер");
renderChain("chain1", d.mobile);
renderChain("chain2", d.fixed);
renderScheme(d.entry ? d.entry.num : num);
}
function renderRegister() {
const list = document.getElementById("regList");
list.innerHTML = "";
REG.forEach(function (e) {
const row = document.createElement("div");
row.className = "kt-ai-card reg-row";
const top = document.createElement("div");
top.className = "reg-row-top";
const num = document.createElement("span");
num.className = "reg-row-num";
num.textContent = e.num;
top.appendChild(num);
top.appendChild(chip(e.type, "blue"));
top.appendChild(chip(e.station, "gray"));
top.appendChild(chip("Резерв: есть", "green"));
row.appendChild(top);
const meta = document.createElement("div");
meta.className = "reg-row-meta";
meta.textContent = e.desc + " · Город: " + e.city + " · Формат набора: ABC21UV";
row.appendChild(meta);
const res = document.createElement("div");
res.className = "reg-row-reserve";
res.textContent = "Резерв: " + e.reserve;
row.appendChild(res);
list.appendChild(row);
});
document.getElementById("regCounter").textContent = "Каждый номер: тип (ТДМ / GPON / SIP), станция владения, формат набора ABC21UV и резерв. В реестре: " + REG.length + ".";
}
function renderScheme(focusNum) {
const svg = document.createElementNS(NS, "svg");
svg.setAttribute("viewBox", "0 0 900 700");
svg.setAttribute("role", "img");
svg.setAttribute("aria-label", "Схема маршрутизации вызовов экстренных служб по карте Казахстана");
function el(tag, attrs) {
const e = document.createElementNS(NS, tag);
Object.keys(attrs || {}).forEach(function (k) { e.setAttribute(k, attrs[k]); });
return e;
}
function line(x1, y1, x2, y2, opts) {
const l = el("line", {
x1: x1, y1: y1, x2: x2, y2: y2,
stroke: opts.accent ? "var(--vibe-accent)" : "var(--kt-ai-fg-muted)",
"stroke-width": opts.width || 1.4,
});
if (opts.dash) l.setAttribute("stroke-dasharray", opts.dash);
if (opts.marker) l.setAttribute("marker-end", "url(#arrow" + (opts.accent ? "A" : "M") + ")");
l.setAttribute("opacity", opts.opacity || 0.8);
return l;
}
function text(x, y, s, size, fill, weight, anchor) {
const t = el("text", { x: x, y: y, "text-anchor": anchor || "middle" });
t.setAttribute("style", "font-size:" + size + "px; fill:" + fill + ";" + (weight ? " font-weight:" + weight + ";" : ""));
t.textContent = s;
return t;
}
const defs = el("defs");
defs.innerHTML =
'' +
'';
svg.appendChild(defs);
// Стили точек городов и подписей
svg.setAttribute("style", "svg text { font-family: var(--kt-ai-font-sans); }");
// Контур Казахстана (упрощённый)
const outline = el("path", {
d: "M60,200 L150,115 L255,82 L368,60 L480,52 L592,62 L688,85 L772,122 L828,172 L856,252 L848,338 L808,404 L848,458 L806,528 L738,588 L656,632 L564,654 L484,668 L408,652 L344,606 L284,552 L222,496 L162,442 L114,378 L82,302 L60,252 Z",
fill: "var(--kt-ai-bg)",
stroke: "var(--kt-ai-border)",
"stroke-width": 1.5,
});
svg.appendChild(outline);
// Вспомогательные линии SSW (серые)
[[198, 438, 478, 208], [352, 272, 478, 208], [478, 268, 478, 208], [418, 118, 478, 208],
[598, 162, 478, 208], [738, 248, 478, 208], [438, 578, 520, 505], [468, 626, 520, 505],
[60, 200, 478, 208],
].forEach(function (p) {
svg.appendChild(line(p[0], p[1], p[2], p[3], { opacity: 0.5 }));
});
// Основная цепочка ЭС для Алматы (акцент): мобильные/транзит Астана → SSW рег. Алматы → BRW SIP → оконечный
const almaty = STATIONS.almaty;
svg.appendChild(line(478, 208 + 28, 520, 505 - 18, { accent: true, marker: true }));
svg.appendChild(line(520, 505, 520, 588, { accent: true, marker: true }));
// Резерв: SSW рег. Алматы → ОПТС-6 (пунктир)
const op6 = almaty.op;
svg.appendChild(line(555, 525, 578, 552, { dash: "5 4", marker: true, width: 1.4 }));
// Узел BRW SIP
svg.appendChild(el("rect", { x: 428, y: 588, width: 184, height: 26, rx: 6, fill: "var(--vibe-tint)", stroke: "var(--vibe-accent)", "stroke-width": 1.2 }));
svg.appendChild(text(520, 605, "BRW SIP · стык со всеми SSW рег.", 11, "var(--kt-ai-fg)", 600));
// Узел оконечного номера (под Алматы)
svg.appendChild(el("rect", { x: 448, y: 618, width: 144, height: 24, rx: 6, fill: "var(--kt-ai-card-bg)", stroke: "var(--vibe-accent)", "stroke-width": 1.5 }));
const endNum = focusNum ? normalize(focusNum) : "7271UV";
svg.appendChild(text(520, 634, "Оконечный: " + (endNum || "7271UV"), 11, "var(--kt-ai-fg)", 600));
// Точки городов
Object.keys(STATIONS).forEach(function (key) {
const c = STATIONS[key];
const isAlmaty = key === "almaty";
const isAstana = key === "astana";
const circle = el("circle", {
cx: c.x, cy: c.y, r: isAlmaty ? 6 : 4.5,
fill: isAlmaty || isAstana ? "var(--vibe-accent)" : "var(--kt-ai-card-bg)",
stroke: "var(--kt-ai-border-strong)",
"stroke-width": 1.2,
});
svg.appendChild(circle);
let anchor = "middle", dy = -10;
if (c.x < 240) { anchor = "start"; dy = 4; }
if (c.x > 690) { anchor = "end"; dy = 4; }
svg.appendChild(text(c.x, c.y + dy, c.name, 12, "var(--kt-ai-fg)", 500, anchor));
if (c.sub) {
svg.appendChild(text(c.x, c.y + dy + 12, c.sub.label, 10, "var(--kt-ai-fg-muted)", null, anchor));
}
});
// Блок «ОПТС-6»
svg.appendChild(el("rect", { x: op6.x - 4, y: op6.y - 16, width: op6.w, height: 34, rx: 6, fill: "var(--kt-ai-card-bg)", stroke: "var(--kt-ai-border-strong)", "stroke-width": 1 }));
svg.appendChild(text(op6.x + 2, op6.y, op6.name, 12, "var(--kt-ai-fg)", 600, "start"));
svg.appendChild(text(op6.x + 2, op6.y + 14, op6.note, 10, "var(--kt-ai-fg-muted)", null, "start"));
const elBox = document.getElementById("schemeSvg");
elBox.innerHTML = "";
elBox.appendChild(svg);
}
document.addEventListener("DOMContentLoaded", function () {
renderRegister();
renderScheme("7271UV");
const input = document.getElementById("num");
const btn = document.getElementById("findBtn");
btn.addEventListener("click", function () { show(input.value); });
input.addEventListener("keydown", function (ev) {
if (ev.key === "Enter") show(input.value);
});
input.addEventListener("input", function () { show(input.value); });
show("7271UV");
});
})();