@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
""" Генератор синтетических прайсов клиник (RU/KZ/EN) + эталонная нормализация + справочник канонов.
Выдаёт:
@@ -8,7 +7,13 @@
contracts/service_canon.json — справочник канонических услуг (seed для нормализации)
Детерминированно (seed=42).
"""
import csv , json , os , random , subprocess , sys
import csv
import json
import os
import random
import subprocess
import sys
ROOT = sys . argv [ 1 ] if len ( sys . argv ) > 1 else " . "
RAW = os . path . join ( ROOT , " fixtures/raw " )
@@ -18,145 +23,433 @@ for d in (RAW, NORM, CONTRACTS):
os . makedirs ( d , exist_ok = True )
random . seed ( 42 )
def ensure ( pkg , mod = None ) :
try : __import__ ( mod or pkg )
except ImportError : subprocess . check_call ( [ sys . executable , " -m " , " pip " , " install " , " -q " , pkg ] )
try :
__import__ ( mod or pkg )
except ImportError :
subprocess . check_call ( [ sys . executable , " -m " , " pip " , " install " , " -q " , pkg ] )
# Справочник канонических медуслуг. lang-тегированные синонимы — материал для нормализации.
CANONS = [
{ " id " : " cbc " , " ru " : " Общий анализ крови " , " kz " : " Қанның жалпы анализі " , " en " : " Complete blood count (CBC) " ,
" category " : " Лаборатория " , " code " : " B03.016.003 " , " unit " : " 1 иссл. " , " price " : ( 1200 , 4500 ) ,
" syn " : [ ( " ОАК " , " ru " ) , ( " Общий анализ крови (развернутый) " , " ru " ) , ( " Клинический анализ крови " , " ru " ) , ( " КАК " , " ru " ) , ( " CBC " , " en " ) , ( " Қанның жалпы талдауы " , " kz " ) ] } ,
{ " id " : " uzi_abd " , " ru " : " УЗИ органов брюшной полости " , " kz " : " Құрсақ қуысы ағзаларының УДЗ " , " en " : " Abdominal ultrasound " ,
" category " : " Диагностика/УЗИ " , " code " : " A04.16.001 " , " unit " : " 1 иссл. " , " price " : ( 6000 , 13000 ) ,
" syn " : [ ( " УЗИ ОБП " , " ru " ) , ( " УЗИ брюшной полости " , " ru " ) , ( " Ультразвуковое исследование брюшной полости " , " ru " ) , ( " Құрсақ қуысы УДЗ " , " kz " ) , ( " Abdominal US " , " en " ) ] } ,
{ " id " : " therapist " , " ru " : " Приём врача-терапевта " , " kz " : " Терапевт дәрігердің қабылдауы " , " en " : " General practitioner consultation " ,
" category " : " Приём специалистов " , " code " : " B01.047.001 " , " unit " : " 1 приём " , " price " : ( 4000 , 9000 ) ,
" syn " : [ ( " Прием терапевта " , " ru " ) , ( " Консультация терапевта первичная " , " ru " ) , ( " Терапевт қабылдауы " , " kz " ) , ( " GP visit " , " en " ) ] } ,
{ " id " : " ecg " , " ru " : " Электрокардиография (ЭКГ) " , " kz " : " Электрокардиография (ЭКГ) " , " en " : " Electrocardiography (ECG) " ,
" category " : " Диагностика " , " code " : " A05.10.006 " , " unit " : " 1 иссл. " , " price " : ( 2000 , 5000 ) ,
" syn " : [ ( " ЭКГ " , " ru " ) , ( " ЭКГ с расшифровкой " , " ru " ) , ( " Электрокардиография " , " ru " ) , ( " ECG " , " en " ) , ( " ЭКГ + описание " , " ru " ) ] } ,
{ " id " : " glucose " , " ru " : " Глюкоза крови " , " kz " : " Қандағы глюкоза " , " en " : " Blood glucose " ,
" category " : " Лаборатория " , " code " : " B03.016.006 " , " unit " : " 1 иссл. " , " price " : ( 800 , 2200 ) ,
" syn " : [ ( " Глюкоза " , " ru " ) , ( " Сахар крови " , " ru " ) , ( " Қандағы глюкоза " , " kz " ) , ( " Glucose " , " en " ) ] } ,
{ " id " : " cardiologist " , " ru " : " Консультация кардиолога " , " kz " : " Кардиолог кеңесі " , " en " : " Cardiologist consultation " ,
" category " : " Приём специалистов " , " code " : " B01.015.001 " , " unit " : " 1 приём " , " price " : ( 5000 , 12000 ) ,
" syn " : [ ( " Прием кардиолога " , " ru " ) , ( " Кардиолог консультация первичная " , " ru " ) , ( " Кардиолог қабылдауы " , " kz " ) ] } ,
{ " id " : " mri_brain " , " ru " : " МРТ головного мозга " , " kz " : " Бас миының МРТ " , " en " : " Brain MRI " ,
" category " : " Диагностика/МРТ " , " code " : " A05.23.009 " , " unit " : " 1 иссл. " , " price " : ( 18000 , 35000 ) ,
" syn " : [ ( " МРТ г /м " , " ru " ) , ( " Магнитно-резонансная томография головного мозга " , " ru " ) , ( " Бас миы МРТ " , " kz " ) , ( " Brain MRI " , " en " ) ] } ,
{ " id " : " biochem " , " ru " : " Биохимический анализ крови " , " kz " : " Қанның биохимиялық анализі " , " en " : " Blood chemistry panel " ,
" category " : " Лаборатория " , " code " : " B03.016.004 " , " unit " : " 1 иссл. " , " price " : ( 3500 , 9000 ) ,
" syn " : [ ( " Биохимия крови " , " ru " ) , ( " Б/х крови " , " ru " ) , ( " Биохимический анализ крови (базовый) " , " ru " ) , ( " Қан биохимиясы " , " kz " ) ] } ,
{ " id " : " tooth_ext " , " ru " : " Удаление зуба " , " kz " : " Тісті жұлу " , " en " : " Tooth extraction " ,
" category " : " Стоматология " , " code " : " A16.07.001 " , " unit " : " 1 зуб " , " price " : ( 6000 , 15000 ) ,
" syn " : [ ( " Удаление зуба простое " , " ru " ) , ( " Экстракция зуба " , " ru " ) , ( " Тіс жұлу " , " kz " ) , ( " Tooth removal " , " en " ) ] } ,
{ " id " : " xray_chest " , " ru " : " Рентген грудной клетки " , " kz " : " Кеуде клеткасының рентгені " , " en " : " Chest X-ray " ,
" category " : " Диагностика " , " code " : " A06.09.007 " , " unit " : " 1 иссл. " , " price " : ( 2500 , 6000 ) ,
" syn " : [ ( " Рентгенография ОГК " , " ru " ) , ( " Флюорография " , " ru " ) , ( " Кеуде рентгені " , " kz " ) , ( " Chest X-ray " , " en " ) ] } ,
{ " id " : " tsh " , " ru " : " Тиреотропный гормон (ТТГ) " , " kz " : " Тиреотропты гормон (ТТГ) " , " en " : " Thyroid-stimulating hormone (TSH) " ,
" category " : " Лаборатория " , " code " : " B03.016.021 " , " unit " : " 1 иссл. " , " price " : ( 1500 , 4000 ) ,
" syn " : [ ( " ТТГ " , " ru " ) , ( " Тиреотропный гормон " , " ru " ) , ( " TSH " , " en " ) , ( " ТТГ гормоны " , " kz " ) ] } ,
{ " id " : " urinalysis " , " ru " : " Общий анализ мочи " , " kz " : " Зәрдің жалпы анализі " , " en " : " Urinalysis " ,
" category " : " Лаборатория " , " code " : " B03.016.010 " , " unit " : " 1 иссл. " , " price " : ( 900 , 2500 ) ,
" syn " : [ ( " ОАМ " , " ru " ) , ( " Общий анализ мочи " , " ru " ) , ( " Зәр анализі " , " kz " ) , ( " Urine test " , " en " ) ] } ,
{ " id " : " gyn " , " ru " : " Приём гинеколога " , " kz " : " Гинеколог қабылдауы " , " en " : " Gynecologist consultation " ,
" category " : " Приём специалистов " , " code " : " B01.001.001 " , " unit " : " 1 приём " , " price " : ( 5000 , 11000 ) ,
" syn " : [ ( " Прием гинеколога " , " ru " ) , ( " Консультация гинеколога первичная " , " ru " ) , ( " Гинеколог кеңесі " , " kz " ) ] } ,
{ " id " : " covid_pcr " , " ru " : " ПЦР на COVID-19 " , " kz " : " COVID-19 ПТР " , " en " : " COVID-19 PCR test " ,
" category " : " Лаборатория " , " code " : " A26.08.027 " , " unit " : " 1 иссл. " , " price " : ( 7000 , 16000 ) ,
" syn " : [ ( " ПЦР COVID-19 " , " ru " ) , ( " ПЦР-тест на коронавирус " , " ru " ) , ( " COVID PCR " , " en " ) , ( " COVID ПТР " , " kz " ) ] } ,
{ " id " : " vit_d " , " ru " : " Витамин D (25-OH) " , " kz " : " D дәрумені (25-OH) " , " en " : " Vitamin D (25-OH) " ,
" category " : " Лаборатория " , " code " : " B03.016.115 " , " unit " : " 1 иссл. " , " price " : ( 6000 , 12000 ) ,
" syn " : [ ( " 25-OH витамин D " , " ru " ) , ( " Витамин Д " , " ru " ) , ( " Vitamin D " , " en " ) , ( " D витамині " , " kz " ) ] } ,
{
" id " : " cbc " ,
" ru " : " Общий анализ крови " ,
" kz " : " Қанның жалпы анализі " ,
" en " : " Complete blood count (CBC) " ,
" category " : " Лаборатория " ,
" code " : " B03.016.003 " ,
" unit " : " 1 иссл. " ,
" price " : ( 1200 , 4500 ) ,
" syn " : [
( " ОАК " , " ru " ) ,
( " Общий анализ крови (развернутый) " , " ru " ) ,
( " Клинический анализ крови " , " ru " ) ,
( " КАК " , " ru " ) ,
( " CBC " , " en " ) ,
( " Қанның жалпы талдауы " , " kz " ) ,
] ,
} ,
{
" id " : " uzi_abd " ,
" ru " : " УЗИ органов брюшной полости " ,
" kz " : " Құрсақ қуысы ағзаларының УДЗ " ,
" en " : " Abdominal ultrasound " ,
" category " : " Диагностика/УЗИ " ,
" code " : " A04.16.001 " ,
" unit " : " 1 иссл. " ,
" price " : ( 6000 , 13000 ) ,
" syn " : [
( " УЗИ ОБП " , " ru " ) ,
( " УЗИ брюшной полости " , " ru " ) ,
( " Ультразвуковое исследование брюшной полости " , " ru " ) ,
( " Құрсақ қуысы УДЗ " , " kz " ) ,
( " Abdominal US " , " en " ) ,
] ,
} ,
{
" id " : " therapist " ,
" ru " : " Приём врача-терапевта " ,
" kz " : " Терапевт дәрігердің қабылдауы " ,
" en " : " General practitioner consultation " ,
" category " : " Приём специалистов " ,
" code " : " B01.047.001 " ,
" unit " : " 1 приём " ,
" price " : ( 4000 , 9000 ) ,
" syn " : [
( " Прием терапевта " , " ru " ) ,
( " Консультация терапевта первичная " , " ru " ) ,
( " Терапевт қабылдауы " , " kz " ) ,
( " GP visit " , " en " ) ,
] ,
} ,
{
" id " : " ecg " ,
" ru " : " Электрокардиография (ЭКГ) " ,
" kz " : " Электрокардиография (ЭКГ) " ,
" en " : " Electrocardiography (ECG) " ,
" category " : " Диагностика " ,
" code " : " A05.10.006 " ,
" unit " : " 1 иссл. " ,
" price " : ( 2000 , 5000 ) ,
" syn " : [
( " ЭКГ " , " ru " ) ,
( " ЭКГ с расшифровкой " , " ru " ) ,
( " Электрокардиография " , " ru " ) ,
( " ECG " , " en " ) ,
( " ЭКГ + описание " , " ru " ) ,
] ,
} ,
{
" id " : " glucose " ,
" ru " : " Глюкоза крови " ,
" kz " : " Қандағы глюкоза " ,
" en " : " Blood glucose " ,
" category " : " Лаборатория " ,
" code " : " B03.016.006 " ,
" unit " : " 1 иссл. " ,
" price " : ( 800 , 2200 ) ,
" syn " : [
( " Глюкоза " , " ru " ) ,
( " Сахар крови " , " ru " ) ,
( " Қандағы глюкоза " , " kz " ) ,
( " Glucose " , " en " ) ,
] ,
} ,
{
" id " : " cardiologist " ,
" ru " : " Консультация кардиолога " ,
" kz " : " Кардиолог кеңесі " ,
" en " : " Cardiologist consultation " ,
" category " : " Приём специалистов " ,
" code " : " B01.015.001 " ,
" unit " : " 1 приём " ,
" price " : ( 5000 , 12000 ) ,
" syn " : [
( " Прием кардиолога " , " ru " ) ,
( " Кардиолог консультация первичная " , " ru " ) ,
( " Кардиолог қабылдауы " , " kz " ) ,
] ,
} ,
{
" id " : " mri_brain " ,
" ru " : " МРТ головного мозга " ,
" kz " : " Бас миының МРТ " ,
" en " : " Brain MRI " ,
" category " : " Диагностика/МРТ " ,
" code " : " A05.23.009 " ,
" unit " : " 1 иссл. " ,
" price " : ( 18000 , 35000 ) ,
" syn " : [
( " МРТ г /м " , " ru " ) ,
( " Магнитно-резонансная томография головного мозга " , " ru " ) ,
( " Бас миы МРТ " , " kz " ) ,
( " Brain MRI " , " en " ) ,
] ,
} ,
{
" id " : " biochem " ,
" ru " : " Биохимический анализ крови " ,
" kz " : " Қанның биохимиялық анализі " ,
" en " : " Blood chemistry panel " ,
" category " : " Лаборатория " ,
" code " : " B03.016.004 " ,
" unit " : " 1 иссл. " ,
" price " : ( 3500 , 9000 ) ,
" syn " : [
( " Биохимия крови " , " ru " ) ,
( " Б/х крови " , " ru " ) ,
( " Биохимический анализ крови (базовый) " , " ru " ) ,
( " Қан биохимиясы " , " kz " ) ,
] ,
} ,
{
" id " : " tooth_ext " ,
" ru " : " Удаление зуба " ,
" kz " : " Тісті жұлу " ,
" en " : " Tooth extraction " ,
" category " : " Стоматология " ,
" code " : " A16.07.001 " ,
" unit " : " 1 зуб " ,
" price " : ( 6000 , 15000 ) ,
" syn " : [
( " Удаление зуба простое " , " ru " ) ,
( " Экстракция зуба " , " ru " ) ,
( " Тіс жұлу " , " kz " ) ,
( " Tooth removal " , " en " ) ,
] ,
} ,
{
" id " : " xray_chest " ,
" ru " : " Рентген грудной клетки " ,
" kz " : " Кеуде клеткасының рентгені " ,
" en " : " Chest X-ray " ,
" category " : " Диагностика " ,
" code " : " A06.09.007 " ,
" unit " : " 1 иссл. " ,
" price " : ( 2500 , 6000 ) ,
" syn " : [
( " Рентгенография ОГК " , " ru " ) ,
( " Флюорография " , " ru " ) ,
( " Кеуде рентгені " , " kz " ) ,
( " Chest X-ray " , " en " ) ,
] ,
} ,
{
" id " : " tsh " ,
" ru " : " Тиреотропный гормон (ТТГ) " ,
" kz " : " Тиреотропты гормон (ТТГ) " ,
" en " : " Thyroid-stimulating hormone (TSH) " ,
" category " : " Лаборатория " ,
" code " : " B03.016.021 " ,
" unit " : " 1 иссл. " ,
" price " : ( 1500 , 4000 ) ,
" syn " : [ ( " ТТГ " , " ru " ) , ( " Тиреотропный гормон " , " ru " ) , ( " TSH " , " en " ) , ( " ТТГ гормоны " , " kz " ) ] ,
} ,
{
" id " : " urinalysis " ,
" ru " : " Общий анализ мочи " ,
" kz " : " Зәрдің жалпы анализі " ,
" en " : " Urinalysis " ,
" category " : " Лаборатория " ,
" code " : " B03.016.010 " ,
" unit " : " 1 иссл. " ,
" price " : ( 900 , 2500 ) ,
" syn " : [
( " ОАМ " , " ru " ) ,
( " Общий анализ мочи " , " ru " ) ,
( " Зәр анализі " , " kz " ) ,
( " Urine test " , " en " ) ,
] ,
} ,
{
" id " : " gyn " ,
" ru " : " Приём гинеколога " ,
" kz " : " Гинеколог қабылдауы " ,
" en " : " Gynecologist consultation " ,
" category " : " Приём специалистов " ,
" code " : " B01.001.001 " ,
" unit " : " 1 приём " ,
" price " : ( 5000 , 11000 ) ,
" syn " : [
( " Прием гинеколога " , " ru " ) ,
( " Консультация гинеколога первичная " , " ru " ) ,
( " Гинеколог кеңесі " , " kz " ) ,
] ,
} ,
{
" id " : " covid_pcr " ,
" ru " : " ПЦР на COVID-19 " ,
" kz " : " COVID-19 ПТР " ,
" en " : " COVID-19 PCR test " ,
" category " : " Лаборатория " ,
" code " : " A26.08.027 " ,
" unit " : " 1 иссл. " ,
" price " : ( 7000 , 16000 ) ,
" syn " : [
( " ПЦР COVID-19 " , " ru " ) ,
( " ПЦР-тест на коронавирус " , " ru " ) ,
( " COVID PCR " , " en " ) ,
( " COVID ПТР " , " kz " ) ,
] ,
} ,
{
" id " : " vit_d " ,
" ru " : " Витамин D (25-OH) " ,
" kz " : " D дәрумені (25-OH) " ,
" en " : " Vitamin D (25-OH) " ,
" category " : " Лаборатория " ,
" code " : " B03.016.115 " ,
" unit " : " 1 иссл. " ,
" price " : ( 6000 , 12000 ) ,
" syn " : [
( " 25-OH витамин D " , " ru " ) ,
( " Витамин Д " , " ru " ) ,
( " Vitamin D " , " en " ) ,
( " D витамині " , " kz " ) ,
] ,
} ,
]
CLINICS = [
" Клиника «Аль-Шифа» " ,
" Медцентр «Сункар» " ,
" Diagnostic Lab Astana " ,
" Клиника «Береке» " ,
" Медцентр «Тенгри» " ,
]
CLINICS = [ " Клиника «Аль-Шифа» " , " Медцентр «Сункар» " , " Diagnostic Lab Astana " , " Клиника «Береке» " , " Медцентр «Тенгри» " ]
def price ( c ) : return random . randint ( c [ " price " ] [ 0 ] / / 100 , c [ " price " ] [ 1 ] / / 100 ) * 100
def variant ( c ) : return random . choice ( [ ( c [ " ru " ] , " ru " ) ] + c [ " syn " ] )
def price ( c ) :
return random . randint ( c [ " price " ] [ 0 ] / / 100 , c [ " price " ] [ 1 ] / / 100 ) * 100
def variant ( c ) :
return random . choice ( [ ( c [ " ru " ] , " ru " ) ] + c [ " syn " ] )
GOLDEN = [ ]
def build ( file_name , clinic , n ) :
rows = [ ]
for c in random . sample ( CANONS , k = min ( n , len ( CANONS ) ) ) :
raw_name , lang = variant ( c ) ; p = price ( c )
rows . append ( { " raw_name " : raw_name , " price " : p , " unit " : c [ " unit " ] } )
GOLDEN . append ( { " source_file " : file_name , " clinic " : clinic , " raw_name " : raw_name , " lang " : lang ,
" canon_id " : c [ " id " ] , " canonical_ru " : c [ " ru " ] , " canonical_kz " : c [ " kz " ] , " canonical_en " : c [ " en " ] ,
" category " : c [ " category " ] , " code " : c [ " code " ] , " price " : p , " currency " : " KZT " , " unit " : c [ " unit " ] } )
raw_name , lang = variant ( c )
p = price ( c )
rows . append ( { " raw_name " : raw_name , " price " : p , " unit " : c [ " unit " ] } )
GOLDEN . append (
{
" source_file " : file_name ,
" clinic " : clinic ,
" raw_name " : raw_name ,
" lang " : lang ,
" canon_id " : c [ " id " ] ,
" canonical_ru " : c [ " ru " ] ,
" canonical_kz " : c [ " kz " ] ,
" canonical_en " : c [ " en " ] ,
" category " : c [ " category " ] ,
" code " : c [ " code " ] ,
" price " : p ,
" currency " : " KZT " ,
" unit " : c [ " unit " ] ,
}
)
return rows
ensure ( " openpyxl " ) ; from openpyxl import Workbook
ensure ( " openpyxl " )
from openpyxl import Workbook
# 1) чистый Excel
rows = build ( " clinic_alpha_clean.xlsx " , CLINICS [ 0 ] , 9 )
wb = Workbook ( ) ; ws = wb . active ; ws . title = " Прайс " ; ws . append ( [ " Наименование услуги " , " Цена, тг " ] )
for r in rows : ws . append ( [ r [ " raw_name " ] , r [ " price " ] ] )
wb = Workbook ( )
ws = wb . active
ws . title = " Прайс "
ws . append ( [ " Наименование услуги " , " Цена, тг " ] )
for r in rows :
ws . append ( [ r [ " raw_name " ] , r [ " price " ] ] )
wb . save ( f " { RAW } /clinic_alpha_clean.xlsx " )
# 2) грязный Excel
rows = build ( " clinic_beta_dirty.xlsx " , CLINICS [ 1 ] , 9 )
wb = Workbook ( ) ; ws = wb . active ; ws . title = " PRICE 2026 "
ws . append ( [ " Прайс-лист медцентра «Сункар» (с 01.06.2026) " ] ) ; ws . append ( [ ] )
ws . append ( [ " № " , " Услуга / Қызмет " , " Стоимость (тенге) " , " Ед. " ] )
secs = [ " — Лаборатория / Зертхана — " , " — Диагностика — " , " — Приём специалистов — " ] ; i = 1
for k , r in enumerate ( rows ) :
if k % 3 == 0 and secs : ws . append ( [ secs . pop ( 0 ) , None , None , None ] )
val = f " от { r [ ' price ' ] } " if random . random ( ) < 0.3 else r [ " price " ]
ws . append ( [ i , r [ " raw_name " ] , val , r [ " unit " ] ] ) ; i + = 1
wb = Workbook ( )
ws = wb . active
ws . title = " PRICE 2026 "
ws . append ( [ " Прайс-лист медцентра «Сункар» (с 01.06.2026) " ] )
ws . append ( [ ] )
ws . append ( [ " № " , " Услуга / Қызмет " , " Стоимость (тенге) " , " Ед. " ] )
secs = [ " — Лаборатория / Зертхана — " , " — Диагностика — " , " — Приём специалистов — " ]
i = 1
for k , r in enumerate ( rows ) :
if k % 3 == 0 and secs :
ws . append ( [ secs . pop ( 0 ) , None , None , None ] )
val = f " от { r [ ' price ' ] } " if random . random ( ) < 0.3 else r [ " price " ]
ws . append ( [ i , r [ " raw_name " ] , val , r [ " unit " ] ] )
i + = 1
wb . save ( f " { RAW } /clinic_beta_dirty.xlsx " )
# 3) CSV ;
rows = build ( " clinic_gamma.csv " , CLINICS [ 2 ] , 8 )
with open ( f " { RAW } /clinic_gamma.csv " , " w " , encoding = " utf-8 " , newline = " " ) as f :
w = csv . writer ( f , delimiter = " ; " ) ; w . writerow ( [ " service " , " price_kzt " , " unit " ] )
for r in rows : w . writerow ( [ r [ " raw_name " ] , r [ " price " ] , r [ " unit " ] ] )
with open ( f " { RAW } /clinic_gamma.csv " , " w " , encoding = " utf-8 " , newline = " " ) as f :
w = csv . writer ( f , delimiter = " ; " )
w . writerow ( [ " service " , " price_kzt " , " unit " ] )
for r in rows :
w . writerow ( [ r [ " raw_name " ] , r [ " price " ] , r [ " unit " ] ] )
# 4) грязный текст
rows = build ( " clinic_delta_messy.txt " , CLINICS [ 4 ] , 7 )
with open ( f " { RAW } /clinic_delta_messy.txt " , " w " , encoding = " utf-8 " ) as f :
with open ( f " { RAW } /clinic_delta_messy.txt " , " w " , encoding = " utf-8 " ) as f :
f . write ( " МЕДЦЕНТР «ТЕНГРИ» — действующие цены \n \n " )
for r in rows :
sep = random . choice ( [ " — " , " ... " , " : " , " " ] ) ; cur = random . choice ( [ " тг " , " тенге " , " ₸ " , " KZT " ] )
sep = random . choice ( [ " — " , " ... " , " : " , " " ] )
cur = random . choice ( [ " тг " , " тенге " , " ₸ " , " KZT " ] )
f . write ( f " { r [ ' raw_name ' ] } { sep } { r [ ' price ' ] } { cur } ( { r [ ' unit ' ] } ) \n " )
f . write ( " \n * Цены справочные, уточняйте в регистратуре. Бағалар анықтамалық. \n " )
# 5) PNG-скан
rows = build ( " clinic_epsilon_scan.png " , CLINICS [ 3 ] , 9 )
ensure ( " Pillow " , " PIL " ) ; from PIL import Image , ImageDraw , ImageFont
ensure ( " Pillow " , " PIL " )
from PIL import Image , ImageDraw , ImageFont
def font ( sz ) :
for p in [ " /System/Library/Fonts/Supplemental/Arial.ttf " , " /Library/Fonts/Arial.ttf " , " /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf " ] :
if os . path . exists ( p ) : return ImageFont . truetype ( p , sz )
for p in [
" /System/Library/Fonts/Supplemental/Arial.ttf " ,
" /Library/Fonts/Arial.ttf " ,
" /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf " ,
] :
if os . path . exists ( p ) :
return ImageFont . truetype ( p , sz )
return ImageFont . load_default ( )
img = Image . new ( " RGB " , ( 900 , 720 ) , ( 250 , 248 , 244 ) ) ; d = ImageDraw . Draw ( img )
d . text ( ( 40 , 30 ) , " Клиника «Береке» — прайс-лист " , fill = ( 20 , 20 , 20 ) , font = font ( 34 ) ) ; d . line ( ( 40 , 85 , 860 , 85 ) , fill = ( 180 , 180 , 180 ) , width = 2 )
y = 110
img = Image . new ( " RGB " , ( 900 , 720 ) , ( 250 , 248 , 244 ) )
d = ImageDraw . Draw ( img )
d . text ( ( 40 , 30 ) , " Клиника «Береке» — прайс-лист " , fill = ( 20 , 20 , 20 ) , font = font ( 34 ) )
d . line ( ( 40 , 85 , 860 , 85 ) , fill = ( 180 , 180 , 180 ) , width = 2 )
y = 110
for r in rows :
d . text ( ( 50 , y ) , r [ " raw_name " ] [ : 42 ] , fill = ( 30 , 30 , 30 ) , font = font ( 23 ) ) ; d . text ( ( 720 , y ) , f " { r [ ' price ' ] } тг " , fill = ( 30 , 30 , 30 ) , font = font ( 23 ) ) ; y + = 62
img = img . rotate ( - 1.4 , expand = False , fillcolor = ( 250 , 248 , 244 ) ) ; img . save ( f " { RAW } /clinic_epsilon_scan.png " )
d . text ( ( 50 , y ) , r [ " raw_name " ] [ : 42 ] , fill = ( 30 , 30 , 30 ) , font = font ( 23 ) )
d . text ( ( 720 , y ) , f " { r [ ' price ' ] } тг " , fill = ( 30 , 30 , 30 ) , font = font ( 23 ) )
y + = 62
img = img . rotate ( - 1.4 , expand = False , fillcolor = ( 250 , 248 , 244 ) )
img . save ( f " { RAW } /clinic_epsilon_scan.png " )
# 6) PDF
rows = build ( " clinic_zeta_prices.pdf " , CLINICS [ 2 ] , 10 )
pdf = " skip "
pdf = " skip "
try :
ensure ( " reportlab " )
from reportlab . lib . pagesizes import A4 ; from reportlab . pdfgen import canvas
from reportlab . pdfbase import pdfmetrics ; from reportlab . pdfbase . ttfonts import TTFont
fp = next ( ( p for p in [ " /Library/Fonts/Arial.ttf " , " /System/Library/Fonts/Supplemental/Arial.ttf " , " /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf " ] if os . path . exists ( p ) ) , None )
from reportlab . lib . pagesizes import A4
from reportlab . pdfbase import pdfmetrics
from reportlab . pdfbase . ttfonts import TTFont
from reportlab . pdfgen import canvas
fp = next (
(
p
for p in [
" /Library/Fonts/Arial.ttf " ,
" /System/Library/Fonts/Supplemental/Arial.ttf " ,
" /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf " ,
]
if os . path . exists ( p )
) ,
None ,
)
if fp :
pdfmetrics . registerFont ( TTFont ( " U " , fp ) ) ; c = canvas . Canvas ( f " { RAW } /clinic_zeta_prices.pdf " , pagesize = A4 )
c . setFont ( " U " , 18 ) ; c . drawString ( 60 , 790 , " Diagnostic Lab Astana — Price list / Прайс " ) ; c . setFont ( " U " , 12 ) ; y = 750
for r in rows : c . drawString ( 60 , y , r [ " raw_name " ] [ : 55 ] ) ; c . drawRightString ( 540 , y , f " { r [ ' price ' ] } KZT " ) ; y - = 28
c . save ( ) ; pdf = " ok "
except Exception as e : pdf = f " skip ( { e } ) "
pdfmetrics . registerFont ( TTFont ( " U " , fp ) )
c = canvas . Canvas ( f " { RAW } /clinic_zeta_prices.pdf " , pagesize = A4 )
c . setFont ( " U " , 18 )
c . drawString ( 60 , 790 , " Diagnostic Lab Astana — Price list / Прайс " )
c . setFont ( " U " , 12 )
y = 750
for r in rows :
c . drawString ( 60 , y , r [ " raw_name " ] [ : 55 ] )
c . drawRightString ( 540 , y , f " { r [ ' price ' ] } KZT " )
y - = 28
c . save ( )
pdf = " ok "
except Exception as e :
pdf = f " skip ( { e } ) "
# эталон нормализации + справочник
with open ( f " { NORM } /golden.json " , " w " , encoding = " utf-8 " ) as f :
with open ( f " { NORM } /golden.json " , " w " , encoding = " utf-8 " ) as f :
json . dump ( GOLDEN , f , ensure_ascii = False , indent = 2 )
canon_out = [ { " canon_id " : c [ " id " ] , " canonical_ru " : c [ " ru " ] , " canonical_kz " : c [ " kz " ] , " canonical_en " : c [ " en " ] ,
" category " : c [ " category " ] , " code " : c [ " code " ] , " unit " : c [ " unit " ] ,
" synonyms " : [ { " text " : t , " lang " : l } for t , l in c [ " syn " ] ] } for c in CANONS ]
with open ( f " { CONTRACTS } /service_canon.json " , " w " , encoding = " utf-8 " ) as f :
canon_out = [
{
" canon_id " : c [ " id " ] ,
" canonical_ru " : c [ " ru " ] ,
" canonical_kz " : c [ " kz " ] ,
" canonical_en " : c [ " en " ] ,
" category " : c [ " category " ] ,
" code " : c [ " code " ] ,
" unit " : c [ " unit " ] ,
" synonyms " : [ { " text " : t , " lang " : l } for t , l in c [ " syn " ] ] ,
}
for c in CANONS
]
with open ( f " { CONTRACTS } /service_canon.json " , " w " , encoding = " utf-8 " ) as f :
json . dump ( canon_out , f , ensure_ascii = False , indent = 2 )
print ( " PDF: " , pdf )