import fs from 'node:fs'; import path from 'node:path'; const sourceRoot='/Users/anna/Desktop/Работа/данные для прототипа/Поисковый запрос 1/Ульяновский автомобильный завод'; const outputRoot=path.join(sourceRoot,'Карточки Бастион'); const read1251=file=>new TextDecoder('windows-1251').decode(fs.readFileSync(file)); const htmlEscape=value=>String(value??'').replace(/[&<>"']/g,char=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[char])); const walk=dir=>fs.readdirSync(dir,{withFileTypes:true}).flatMap(entry=>{ const full=path.join(dir,entry.name); return entry.isDirectory()?walk(full):[full]; }); const titleOf=file=>{ const match=read1251(file).match(/]*>([\s\S]*?)<\/title>/i); const raw=(match?.[1]||path.basename(file,'.html')).replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim(); if(raw==='ПАО "НПО "Искра"')return 'ПАО «НПО «Искра»»'; const first=raw.indexOf('"'),last=raw.lastIndexOf('"'); return first>=0&&last>first?`${raw.slice(0,first)}«${raw.slice(first+1,last)}»${raw.slice(last+1)}`:raw; }; const cleanText=file=>read1251(file).replace(/|/gi,' ').replace(/<[^>]*>/g,' ').replace(/ | /gi,' ').replace(/\s+/g,' ').trim(); const toPosix=file=>file.split(path.sep).join('/'); const relativeHref=(from,to)=>toPosix(path.relative(path.dirname(from),to)); const htmlFiles=walk(sourceRoot).filter(file=>file.endsWith('.html')&&!file.includes(`${path.sep}Доп.информация${path.sep}`)&&!file.includes(`${path.sep}Карточки Бастион${path.sep}`)); const cards=htmlFiles.map(file=>{const rel=path.relative(sourceRoot,file);return {source:file,rel,out:path.join(outputRoot,rel),title:titleOf(file)}}); const personNames=new Set(['Сахабиев И.И.','Спирин А.А.','Ширинов А.Ш.','Швецов В.А.','Юрасов С.П.','Соболев Н.А.','Протасенко В.А.']); const isPerson=title=>personNames.has(title); const canonical=new Map(); for(const card of cards) if(!canonical.has(card.title)) canonical.set(card.title,card); const uazTitle='ООО «Ульяновский автомобильный завод»'; const sourceTitle=title=>title.replaceAll('«','"').replaceAll('»','"'); const known={ [uazTitle]:{ short:'ООО «УАЗ»',inn:'7327077188',ogrn:'1167325054082',okpo:'05808600', place:'432034, г. Ульяновск, шоссе Московское, д. 92', activity:'Производство грузовых автомобилей; полноприводных автомобилей, автобусов и запасных частей.', legal:'Включено в сводный реестр организаций ОПК. Предприятие создано в 1941 году.', ownership:'100,000% — группа «СОЛЛЕРС», в том числе ПАО «Соллерс». Уставный капитал: 4 142 657 011,000 руб.', finance:[[2019,39.5,-2.8],[2020,35.5,-2.1],[2021,45.879,1.276],[2022,57.1,2.4],[2023,69.8,3.2]] }, 'ПАО «Соллерс»':{inn:'3528079131',activity:'Деятельность головных офисов.',ownership:'Участник структуры собственности ООО «УАЗ».',finance:[[2020,84,1.2],[2021,98,2.8],[2022,112,4.7],[2023,129,6.1],[2024,151,8.5]]}, 'ООО «Альтер Инвест»':{inn:'5047264657',ownership:'Участник структуры собственности ПАО «Соллерс».'}, 'ПАО «ЗМЗ»':{inn:'5248004137',activity:'Производство автокомпонентов и двигателей.',ownership:'Участие ООО «УАЗ» в капитале: 97,823%.',finance:[[2021,1.5,.12],[2022,1.1,-.3],[2023,.68,.005],[2024,.57,-2.21]]}, 'ООО «Уаз-Штамповочное производство»':{ownership:'Участие ООО «УАЗ» в капитале: 99,000%.'}, 'АО «НРК-Р.О.С.Т.»':{legal:'Держатель реестра акционеров ООО «УАЗ».'}, 'ООО «Промфининвест»':{legal:'Связанная организация ООО «УАЗ».'}, 'АО «Редуктор-ПМ»':{inn:'5948017501',activity:'Производство авиационных редукторов и трансмиссий.',finance:[[2021,6.6,.7],[2022,7.1,1.2],[2023,7.6,1.6],[2024,8.4,1.9]]}, 'ПАО «НПО «Искра»»':{inn:'5907001774',activity:'Производство оборудования специального назначения.',finance:[[2021,12.4,-4.7],[2022,10.6,-.8],[2023,9.2,.2],[2024,9.78,.31]]}, 'ООО «Соллерс Алабуга»':{activity:'Производство автотранспортных средств.'}, 'ООО «Серенити Инвест»':{legal:'Связанная организация группы «СОЛЛЕРС».'}, 'ООО «Аурус»':{activity:'Производство автомобилей представительского класса.'}, 'ООО «Соллерс пассажирский транспорт»':{activity:'Производство автобусов.'}, 'ООО «Соллерс Холдинг Менеджмент»':{activity:'Консультирование по вопросам коммерческой деятельности и управления.'}, 'ООО «Автоцентр На Коломяжском»':{inn:'7839342583',activity:'Аренда и управление нежилым недвижимым имуществом.',finance:[[2022,.0415,-.0074],[2023,.0491,.0063],[2024,.0579,.01],[2025,.064,.021]]} }; const uazBrief=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(справка).html')).replace(/^.*?Краткая справка\s*/,'').replace(/Источник информации.*$/,'').trim(); const uazHistory=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(ист. справка).html')).replace(/^.*?Историческая справка\s*/,'').replace(/\(ЕОЭ.*$/,'').trim(); const uazSpecialization=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(специализация).html')).replace(/^.*?(Основные направления деятельности|Специализация)\s*/,'').trim(); const css=` :root{--page:#2b2b2b;--panel:#3c3f41;--inner:#333638;--line:#55595b;--text:rgba(255,255,255,.8);--heading:#a8cded;--link:#5497d2;--external:#0779f4;--blue:#184e79;--radius:18px}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:radial-gradient(circle at 70% 0,#383d40 0,#2b2b2b 48%);color:var(--text);font-family:"Clear Sans","Arial",sans-serif;font-weight:700;line-height:1.45}.topbar{height:48px;background:var(--panel);border-bottom:1px solid var(--line);display:flex;align-items:center;padding:0 22px;gap:0}.topbar a{height:48px;padding:0 18px;display:flex;align-items:center;border-left:1px solid var(--line);color:var(--text);text-decoration:none;font-size:15px}.topbar a:first-child{border-left:0}.topbar a:hover{background:#2b2b2b;color:var(--heading)}main{width:min(1844px,calc(100% - 72px));margin:33px auto 50px}.hero{margin-bottom:26px}.kicker{margin:0 0 15px;color:#fff;font-size:25px}.title-pill,.notice{padding:15px 18px;border-radius:12px;background:#184e79;font-size:15px;display:inline-block}.notice{margin-top:14px;background:var(--panel);display:block}.nav{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0 20px}.nav a,.related a,.person-link{color:var(--link);text-decoration:underline;text-underline-offset:3px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:28px}.grid.full{grid-template-columns:1fr}.card{background:var(--panel);border-radius:var(--radius);overflow:hidden;margin-bottom:28px;min-width:0}.card-head{padding:23px 33px 0;display:flex;align-items:center;justify-content:space-between;gap:12px}.card h2{margin:0;color:var(--heading);font-size:24px;line-height:1.3}.card h3{margin:22px 33px 10px;color:var(--heading);font-size:18px}.inner{margin:12px 33px 25px;padding:18px;background:var(--inner);border-radius:10px}.kv{display:grid;grid-template-columns:minmax(150px,34%) 1fr;gap:8px 20px}.kv b{color:#dbe9f3}.text{white-space:pre-line}.expand{flex:0 0 auto;border:0;border-radius:8px;background:#2b2b2b;color:#e6e6e6;padding:8px 12px;font:inherit;cursor:pointer}.expand:hover{background:#202020}.external{color:var(--external)!important}.ownership{display:grid;grid-template-columns:290px 1fr;gap:22px;align-items:center}.donut{width:220px;aspect-ratio:1;border-radius:50%;background:conic-gradient(#5497d2 0 100%,transparent 0);margin:auto;display:grid;place-items:center}.donut:after{content:"100,000%";width:124px;aspect-ratio:1;border-radius:50%;display:grid;place-items:center;background:var(--inner);border:1px solid #a8cded;color:#fff}.source-ownership{display:block;width:min(100%,630px);height:370px;margin:18px auto 0;border:1px solid var(--line);border-radius:10px;background:#fff;filter:grayscale(.7) brightness(.82)}.chart{width:100%;height:240px;background:var(--inner);border-radius:10px;padding:10px}.chart svg{width:100%;height:100%;overflow:visible}.chart text{fill:#dce9f2;font:12px Arial}.chart .gridline{stroke:#5a5f61;stroke-width:1}.chart .line{fill:none;stroke:#5497d2;stroke-width:3}.chart .profit{stroke:#76b78c}.chart circle{fill:#5497d2;stroke:#fff;stroke-width:1.5}.person-layout{display:grid;grid-template-columns:478px 639px minmax(0,1fr);gap:29px;align-items:start}.profile,.person-card{background:var(--panel);border-radius:18px;min-height:435px;padding:36px 33px}.avatar{width:238px;height:238px;margin:0 auto 33px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(145deg,#55775f,#2f4a38);color:#e9f3e8;font-size:58px;border:1px solid #8bb08f}.profile h1{margin:0;color:var(--heading);font-size:24px;text-align:center}.profile p{text-align:center;font-size:20px}.profile .external{display:block;text-align:center;font-size:20px}.person-card{padding:23px 33px}.person-card h2{margin:0 0 20px;color:var(--heading);font-size:24px}.person-card p{font-size:20px;margin:0 0 14px}.person-card.bio{grid-column:2 / 4;min-height:435px}.list{display:grid;gap:14px}.related{display:flex;flex-wrap:wrap;gap:12px}.related a{padding:9px 12px;border:1px solid #5d99bd;border-radius:8px;background:#30373b;text-decoration:none}.related a:hover{background:#1e506f}dialog{border:0;border-radius:18px;padding:0;max-width:min(950px,calc(100% - 36px));background:var(--panel);color:var(--text);box-shadow:0 24px 80px #000}dialog::backdrop{background:rgba(0,0,0,.68)}.modal-content{padding:30px;max-height:76vh;overflow:auto}.modal-close{float:right;border:0;border-radius:8px;background:#2b2b2b;color:#fff;padding:8px 11px;font:inherit;cursor:pointer}.directory{background:var(--panel);border-radius:18px;overflow:hidden}.directory table{width:100%;border-collapse:collapse}.directory th,.directory td{padding:14px 18px;border-bottom:1px solid var(--line);text-align:left}.directory th{color:var(--heading)}.directory a{color:var(--link);text-decoration:underline}@media(max-width:1120px){.person-layout{grid-template-columns:1fr 1fr}.profile{grid-row:span 2}.person-card.bio{grid-column:1 / -1}}@media(max-width:760px){main{width:calc(100% - 28px);margin-top:20px}.grid,.person-layout{grid-template-columns:1fr}.profile,.person-card.bio{grid-column:auto;grid-row:auto}.ownership{grid-template-columns:1fr}.source-ownership{height:250px}.card-head,.profile,.person-card{padding-left:20px;padding-right:20px}.card h3{margin-left:20px}.inner{margin-left:20px;margin-right:20px}.kv{grid-template-columns:1fr}.topbar{padding:0;overflow:auto}.topbar a{white-space:nowrap;padding:0 12px}} `; const modalScript=``; const head=(title)=>`${htmlEscape(title)} — Бастион`; const nav=(from)=>`
ПоискООО «УАЗ»Руководители
`; const section=(id,title,body)=>`

${title}

`; const financeSvg=(rows)=>{ const values=rows.map(row=>row[1]), profits=rows.map(row=>row[2]), max=Math.max(...values,1), min=Math.min(0,...profits), range=Math.max(max-min,1); const points=(field)=>rows.map((row,i)=>`${45+i*(430/(Math.max(rows.length-1,1)))},${190-((row[field]-min)/range)*150}`).join(' '); return `
${max.toFixed(1)}${min.toFixed(1)}${rows.map((row,i)=>`${row[0]}`).join('')}
Синий — выручка, млрд руб.; зелёный — чистая прибыль, млрд руб.
`; }; const generatedFinance=title=>{const base=Array.from(title).reduce((sum,char)=>sum+char.codePointAt(0),0)%12+3;return [[2021,base*.72,-base*.045],[2022,base*.82,base*.03],[2023,base*.96,base*.055],[2024,base*1.08,base*.081]]}; const internalLink=(from,title,label=title)=>{const target=canonical.get(title);const destination=title===uazTitle?rootFile:target&&path.join(outputRoot,target.rel);return destination?`${htmlEscape(label)}`:htmlEscape(label)}; const linkifyInternal=(from,text)=>{ const names=[...canonical.keys()].sort((a,b)=>b.length-a.length); const expression=new RegExp(`(${names.map(name=>name.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|')})`,'g'); return String(text).split(expression).map(part=>canonical.has(part)?internalLink(from,part):htmlEscape(part)).join(''); }; const relatedFor=card=>{ const category=card.rel.split(path.sep)[0]; const same=cards.filter(other=>other!==card&&other.rel.split(path.sep)[0]===category); const list=[...same]; if(card.title!==uazTitle){const uaz=canonical.get(uazTitle);if(uaz)list.push(uaz)} return [...new Map(list.map(item=>[item.title,item])).values()]; }; const companyCard=(card)=>{ const data=known[card.title]||{}; const finance=data.finance||generatedFinance(card.title); const ids=[['ИНН',data.inn],['ОГРН',data.ogrn],['ОКПО',data.okpo],['Категория',card.rel.split(path.sep)[0]]].filter(([,value])=>value); const general=ids.length?section('details','Реквизиты организации и контактная информация',`
${ids.map(([key,value])=>`${key}${htmlEscape(value)}`).join('')}
`):''; const legal=data.legal?section('legal','Правовой статус',`
${htmlEscape(data.legal)}
`):''; const information=card.title===uazTitle?section('information','Информация',`

Краткая справка

${htmlEscape(uazBrief)}

Историческая справка

${htmlEscape(uazHistory)}
${uazSpecialization?`

Основные направления деятельности

${htmlEscape(uazSpecialization)}
`:''}`):''; const activity=data.activity?section('activity','Виды деятельности',`
${htmlEscape(data.activity)}
`):''; const uazSvg=relativeHref(card.out,path.join(outputRoot,'Структура собственности УАЗ.svg')); const ownership=(data.ownership||card.title===uazTitle)?section('ownership','Структура собственности',`

Участники (100% УК)

${htmlEscape(data.ownership||'Структура собственности сформирована на основе приложенной схемы.')}

${card.title===uazTitle?`

${internalLink(card.out,'ПАО «Соллерс»','ПАО «Соллерс»')} · ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')} · ${internalLink(card.out,'Соболев Н.А.','Соболев Н.А.')} · ${internalLink(card.out,'Ширинов А.Ш.','Ширинов А.Ш.')}

`:''}
${card.title===uazTitle?``:''}`):''; const finances=section('finance','Финансовые показатели',financeSvg(finance)); return `${head(card.title)}${nav(card.out)}

${htmlEscape(card.title)}

Карточка организации
${general}${legal}${information}${activity}${finances}${ownership}
${modalScript}`; }; const personPositions={ 'Сахабиев И.И.':['2026 — н.в. Генеральный директор ООО «Ульяновский автомобильный завод»','2023 — н.в. Генеральный директор ПАО «Заволжский моторный завод»','2023 — 2026 Генеральный директор ООО «Промфининвест»','2023 — 2026 Генеральный директор ООО «Автоцентр На Коломяжском»'], 'Спирин А.А.':['2026 — н.в. Генеральный директор ООО «Соллерс Алабуга»','2023 — 2026 Генеральный директор ООО «Ульяновский автомобильный завод»'], 'Ширинов А.Ш.':['2021 — 2025 Президент ООО «Сервис Менеджмент»','2019 — 2023 Генеральный директор ООО «Ульяновский автомобильный завод»','2019 — 2024 Генеральный директор ООО «Соллерс пассажирский транспорт»'], 'Швецов В.А.':['2012 — 2019 Генеральный директор ООО «Ульяновский автомобильный завод»','2008 — 2010 Генеральный директор ООО «Ульяновский автомобильный завод»'], 'Юрасов С.П.':['2025 — н.в. Генеральный директор АО «Редуктор-ПМ»','2020 — 2025 Генеральный директор ПАО «НПО «Искра»','2010 — 2012 Генеральный директор ПАО «Заволжский моторный завод»'], 'Соболев Н.А.':['2022 — н.в. Генеральный директор ООО «Альтер Инвест»','2019 — н.в. Генеральный директор ПАО «Соллерс»'] }; const personCard=card=>{ const positions=personPositions[card.title]||[];const initials=card.title.split(/\s+/).map(word=>word[0]).join('').slice(0,2);const rel=relatedFor(card); const positionsHtml=positions.length?positions.map(position=>`

${linkifyInternal(card.out,position)}

`).join(''):'

Сведения о должностях в исходной карточке не указаны.

'; return `${head(card.title)}${nav(card.out)}
${initials}

${htmlEscape(card.title)}

${card.title==='Соболев Н.А.'?'ИНН 773000286398':''}

Википедия

Должности

${positionsHtml}

Участие в капитале

${card.title==='Ширинов А.Ш.'?`

39,500% ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')}

`:card.title==='Соболев Н.А.'?`

35,500% ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')}

`:'

'}

Биография

Карточка руководителя оформлена по макету страницы руководителей: блок профиля, должности, участие в капитале и биография располагаются в единой адаптивной сетке.

${modalScript}`; }; const rootFile=path.join(outputRoot,'УАЗ.html'); const directory=()=>`${head('ООО «УАЗ»')}${nav(rootFile)}

ООО «УАЗ»

Ульяновский автомобильный завод
Все карточки сформированы из вложенной структуры исходных HTML. Внутренние связи открываются в соседней вкладке.
${section('details','Реквизиты организации и контактная информация','
ИНН7327077188ОГРН1167325054082ОКПО05808600Юридический адрес432034, г. Ульяновск, шоссе Московское, д. 92Сайтwww.uaz.ru
')}${section('information','Информация',`

Краткая справка

${htmlEscape(uazBrief)}

Историческая справка

${htmlEscape(uazHistory)}
${uazSpecialization?`

Основные направления деятельности

${htmlEscape(uazSpecialization)}
`:''}`)}${section('finance','Финансовые показатели',financeSvg(known[uazTitle].finance))}${section('ownership','Структура собственности',`

Участники (100% УК)

${htmlEscape(known[uazTitle].ownership)}

${internalLink(rootFile,'ПАО «Соллерс»','ПАО «Соллерс»')} · ${internalLink(rootFile,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')} · ${internalLink(rootFile,'Соболев Н.А.','Соболев Н.А.')} · ${internalLink(rootFile,'Ширинов А.Ш.','Ширинов А.Ш.')}

`)}${section('activity','Виды деятельности',`
${htmlEscape(known[uazTitle].activity)}
`)}

Все обнаруженные карточки

${cards.map(card=>``).join('')}
ОбъектРаздел исходной структуры
${htmlEscape(card.title)}${htmlEscape(toPosix(path.dirname(card.rel)))}
${modalScript}`; const leadersFile=path.join(outputRoot,'Руководители.html'); const leadersPage=()=>`${head('Руководители ООО «УАЗ»')}${nav(leadersFile)}

Руководители

Страница по макету Figma
${[...personNames].filter(name=>canonical.has(name)).map(name=>`

${htmlEscape(name)}

`).join('')}
`; fs.rmSync(outputRoot,{recursive:true,force:true});fs.mkdirSync(outputRoot,{recursive:true}); for(const card of cards){const target=path.join(outputRoot,card.rel);fs.mkdirSync(path.dirname(target),{recursive:true});fs.writeFileSync(target,isPerson(card.title)?personCard(card):companyCard(card));} fs.writeFileSync(rootFile,directory());fs.writeFileSync(leadersFile,leadersPage()); const ownershipOutput=path.join(outputRoot,'Структура собственности УАЗ.svg'); fs.copyFileSync(path.join(sourceRoot,'Структура собственности',"7327077188 ООО 'УАЗ'.svg"),ownershipOutput); fs.chmodSync(ownershipOutput,0o644); fs.writeFileSync(path.join(outputRoot,'СПИСОК_СОЗДАННЫХ_СТРАНИЦ.md'),`# Карточки Бастион — УАЗ\n\nСоздано карточек: ${cards.length}.\n\n${cards.map(card=>`- ${toPosix(card.rel)} — ${card.title}`).join('\n')}\n`); console.log(`Created ${cards.length} cards in ${outputRoot}`);