Skip to content

Commit 013ff4d

Browse files
committed
Normalize inflected NPP station names
1 parent 5b7727d commit 013ff4d

3 files changed

Lines changed: 58 additions & 54 deletions

File tree

src/normalize.ts

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolveNppStationNameFromText } from "./npp-stations";
12
import type { NormalizedSourceEvent, RawSourceEvent } from "./types";
23

34
const ENTITY_BOILERPLATE_MARKERS = [
@@ -94,7 +95,7 @@ function normalizeEisRawEvent(input: RawSourceEvent): NormalizedSourceEvent {
9495
const supplierName = sanitizeEntityName(toStringOrUndefined(raw.supplierName));
9596
const targetStationName =
9697
toStringOrUndefined(raw.targetStationName) ??
97-
resolveStationNameFromText([
98+
resolveNppStationNameFromText([
9899
toStringOrUndefined(raw.title),
99100
toStringOrUndefined(raw.description),
100101
customerName,
@@ -739,56 +740,3 @@ function calculateProfileCompleteness(input: {
739740

740741
return Math.round((filled / fields.length) * 100);
741742
}
742-
743-
function resolveStationNameFromText(values: Array<string | undefined>): string | undefined {
744-
const haystack = values.filter(Boolean).join(" ").toLowerCase();
745-
746-
if (!haystack) {
747-
return undefined;
748-
}
749-
750-
const stations = [
751-
{
752-
canonical: "Балаковская атомная станция",
753-
variants: ["балаковская атомная станция", "балаковская аэс", "балаковская аэс-авто"]
754-
},
755-
{
756-
canonical: "Белоярская атомная станция",
757-
variants: ["белоярская атомная станция", "белоярская аэс"]
758-
},
759-
{
760-
canonical: "Билибинская атомная станция",
761-
variants: ["билибинская атомная станция", "билибинская аэс"]
762-
},
763-
{
764-
canonical: "Калининская атомная станция",
765-
variants: ["калининская атомная станция", "калининская аэс", "калининская аэс-сервис"]
766-
},
767-
{
768-
canonical: "Кольская атомная станция",
769-
variants: ["кольская атомная станция", "кольская аэс"]
770-
},
771-
{
772-
canonical: "Курская атомная станция",
773-
variants: ["курская атомная станция", "курская аэс", "курская аэс-сервис"]
774-
},
775-
{
776-
canonical: "Ленинградская атомная станция",
777-
variants: ["ленинградская атомная станция", "ленинградская аэс", "ленинградская аэс-авто"]
778-
},
779-
{
780-
canonical: "Нововоронежская атомная станция",
781-
variants: ["нововоронежская атомная станция", "нововоронежская аэс"]
782-
},
783-
{
784-
canonical: "Ростовская атомная станция",
785-
variants: ["ростовская атомная станция", "ростовская аэс"]
786-
},
787-
{
788-
canonical: "Смоленская атомная станция",
789-
variants: ["смоленская атомная станция", "смоленская аэс", "смоленская аэс-сервис"]
790-
}
791-
] as const;
792-
793-
return stations.find((station) => station.variants.some((variant) => haystack.includes(variant)))?.canonical;
794-
}

src/npp-stations.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, it } from "vitest";
2+
import { resolveNppStationNameFromText } from "./npp-stations";
3+
4+
describe("resolveNppStationNameFromText", () => {
5+
it("maps EIS titles with inflected station names to canonical station labels", () => {
6+
expect(
7+
resolveNppStationNameFromText([
8+
"Выполнение работ для Калининской атомной станции"
9+
])
10+
).toBe("Калининская атомная станция");
11+
12+
expect(
13+
resolveNppStationNameFromText([
14+
"Поставка оборудования для нужд Белоярской АЭС"
15+
])
16+
).toBe("Белоярская атомная станция");
17+
});
18+
});

src/npp-stations.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const STATION_NAME_ENDINGS = "(?:ая|ой|ую|ое|ом|ие|их)?";
2+
const ATOMIC_LABEL_PATTERN =
3+
"(?:атомн(?:ая|ой|ую|ое|ом|ых)?\\s+станци(?:я|и|ю|е|ей|ям|ями|ях)|аэс(?:-[а-яa-z]+)*)";
4+
5+
const NPP_STATION_DEFINITIONS = [
6+
{ canonical: "Балаковская атомная станция", stem: "балаковск" },
7+
{ canonical: "Белоярская атомная станция", stem: "белоярск" },
8+
{ canonical: "Билибинская атомная станция", stem: "билибинск" },
9+
{ canonical: "Калининская атомная станция", stem: "калининск" },
10+
{ canonical: "Кольская атомная станция", stem: "кольск" },
11+
{ canonical: "Курская атомная станция", stem: "курск" },
12+
{ canonical: "Ленинградская атомная станция", stem: "ленинградск" },
13+
{ canonical: "Нововоронежская атомная станция", stem: "нововоронежск" },
14+
{ canonical: "Ростовская атомная станция", stem: "ростовск" },
15+
{ canonical: "Смоленская атомная станция", stem: "смоленск" }
16+
] as const;
17+
18+
const NPP_STATION_PATTERNS = NPP_STATION_DEFINITIONS.map((station) => ({
19+
canonical: station.canonical,
20+
pattern: new RegExp(`${station.stem}${STATION_NAME_ENDINGS}\\s+${ATOMIC_LABEL_PATTERN}`, "i")
21+
}));
22+
23+
export function resolveNppStationNameFromText(
24+
values: ReadonlyArray<string | null | undefined>
25+
): string | undefined {
26+
const haystack = values
27+
.filter((value): value is string => typeof value === "string" && value.trim().length > 0)
28+
.map((value) => value.trim().toLowerCase())
29+
.join(" ")
30+
.replace(/\s+/g, " ")
31+
.trim();
32+
33+
if (!haystack) {
34+
return undefined;
35+
}
36+
37+
return NPP_STATION_PATTERNS.find((station) => station.pattern.test(haystack))?.canonical;
38+
}

0 commit comments

Comments
 (0)