<# .SYNOPSIS Собирает справочник городов и посёлков РК для выпадающих списков портала из официального КАТО (Бюро национальной статистики, stat.gov.kz) и пишет его TS-модулем src/webparts/requestPortal/shared/dictionary/kato/katoCities.ts. .DESCRIPTION Берём только города («г.») и посёлки («п.»): ~180 записей, несколько KB в бандле. Сёла (~10 тыс.) не берём — тяжело для фронта; их вводят вручную в то же поле. Административные единицы («Г.А.», «п.а.», «г.а.» — администрации, не населённые пункты) отбрасываются. КАТО обновляется редко: перезапустить скрипт со ссылкой на свежий файл со страницы https://stat.gov.kz/ru/classifiers/statistical/21/ и закоммитить сгенерированный модуль. .EXAMPLE ./scripts/Build-KatoCities.ps1 ./scripts/Build-KatoCities.ps1 -XlsxUrl "https://stat.gov.kz/upload/iblock/.../КАТО_ДД.ММ.ГГГГ.xlsx" #> param( [string]$XlsxUrl = 'https://stat.gov.kz/upload/iblock/a7d/eqogm92xc0udumlzfzffhcrutk0fjhb4/КАТО_18.09.2026.xlsx', [string]$OutFile = (Join-Path $PSScriptRoot '..\src\webparts\requestPortal\shared\dictionary\kato\katoCities.ts') ) $ErrorActionPreference = 'Stop' $work = Join-Path ([System.IO.Path]::GetTempPath()) "kato-$([guid]::NewGuid())" New-Item -ItemType Directory -Path $work | Out-Null try { # xlsx — это zip: читаем XML листа напрямую, без Excel и сторонних модулей. $zip = Join-Path $work 'kato.zip' Invoke-WebRequest -Uri $XlsxUrl -OutFile $zip -UseBasicParsing Expand-Archive -Path $zip -DestinationPath $work [xml]$sharedXml = Get-Content -Raw -Encoding UTF8 (Join-Path $work 'xl\sharedStrings.xml') $shared = @($sharedXml.sst.si | ForEach-Object { $_.InnerText }) [xml]$sheetXml = Get-Content -Raw -Encoding UTF8 (Join-Path $work 'xl\worksheets\sheet1.xml') # Колонки листа: A — код КАТО, B — код области (ab), C/D/E — нижние уровни, G/H — название на казахском/русском. $rows = foreach ($row in $sheetXml.worksheet.sheetData.row) { $cells = @{} foreach ($c in $row.c) { $column = $c.r -replace '\d', '' $value = if ($c.t -eq 's') { $shared[[int]$c.v] } elseif ($c.t -eq 'inlineStr') { $c.is.InnerText } else { $c.v } $cells[$column] = "$value".Trim() } [pscustomobject]@{ Kato = $cells['A']; Region = $cells['B']; Level2 = $cells['C']; Level3 = $cells['D']; Level4 = $cells['E']; Name = $cells['H']; NameKz = $cells['G'] } } # Области — строки без нижних уровней. Города республиканского значения (Астана, # Алматы, Шымкент) сами себе «область» — для них регион не показываем. $regions = @{} $rows | Where-Object { $_.Level2 -eq '00' -and $_.Level3 -eq '00' -and $_.Level4 -eq '000' } | ForEach-Object { $regions[$_.Region] = if ($_.Name -match '^г\.') { '' } else { $_.Name } } # «г.Атырау», «п. Асыката» — населённые пункты; «п.а.», «г.а.» — администрации. $cities = $rows | Where-Object { $_.Name -match '^(г|п)\.\s*[^\s.]' -and $_.Name -notmatch '^(г|п)\.а\.' } | ForEach-Object { [pscustomobject]@{ Kato = $_.Kato Name = ($_.Name -replace '^(г|п)\.\s*', '') # «Құлсары қ.» / «Шаған к.» — тип пункта последним словом с точкой (в источнике # встречается и опечатка «ќ.»), поэтому отрезаем любое такое слово. NameKz = ($_.NameKz -replace '\s+\S+\.$', '') Kind = if ($_.Name -match '^г\.') { 'city' } else { 'settlement' } Region = $regions[$_.Region] } } | Sort-Object Name, Region $esc = { param($s) $s -replace '\\', '\\' -replace "'", "\'" } $lines = $cities | ForEach-Object { " { kato: '$($_.Kato)', name: '$(& $esc $_.Name)', nameKz: '$(& $esc $_.NameKz)', kind: '$($_.Kind)', region: '$(& $esc $_.Region)' }," } $source = [System.Uri]::UnescapeDataString(($XlsxUrl -split '/')[-1]) $content = @" // СГЕНЕРИРОВАНО scripts/Build-KatoCities.ps1 — руками не править, перезапустить скрипт. // Источник: КАТО, Бюро национальной статистики РК (stat.gov.kz), файл «$source». import { IKatoCity } from './IKatoCity'; export const KATO_CITIES: ReadonlyArray = [ $($lines -join "`n") ]; "@ $outPath = [System.IO.Path]::GetFullPath($OutFile) New-Item -ItemType Directory -Force -Path (Split-Path $outPath) | Out-Null # Без BOM и с LF — как остальные исходники репозитория. [System.IO.File]::WriteAllText($outPath, $content.Replace("`r`n", "`n") + "`n", (New-Object System.Text.UTF8Encoding $false)) $cityCount = @($cities | Where-Object Kind -eq 'city').Count Write-Host "Готово: $($cities.Count) записей (городов $cityCount, посёлков $($cities.Count - $cityCount)) → $OutFile" } finally { Remove-Item -Recurse -Force $work }