dataScript = FileAttachment("data/db/data.json").json()
// Материалы доступные на Меркурии
mercuryMaterials = new Set(
dataScript.planetMaterials
.filter(pm => pm.planet_id === "mercury")
.map(pm => pm.material_id)
)
// Словари для быстрого поиска
materialsById = new Map(dataScript.materials.map(m => [m.id, m]))
unitsById = new Map(dataScript.units.map(u => [u.id, u]))
planetsById = new Map(dataScript.planets.map(p => [p.id, p]))
// Форматирование массы (кг или г)
formatMass = function(massKg) {
if (!massKg || massKg === 0) return '—';
if (massKg >= 1) return massKg.toFixed(0) + ' кг';
return (massKg * 1000).toFixed(0) + ' г';
}
// Функция для получения материалов компонента
getComponentMaterials = function(componentId) {
return dataScript.unitMaterials
.filter(um => um.unit_id === componentId)
.map(um => {
const material = materialsById.get(um.material_id);
return {
symbol: material?.symbol,
name: material?.name || um.material_id,
fraction_pct: um.fraction_pct,
isLocal: mercuryMaterials.has(um.material_id)
};
});
}
// Функция для отображения виджета единицы
unitWidget = function(unitId) {
const unit = unitsById.get(unitId);
if (!unit) return html`<div class="db-widget">Единица ${unitId} не найдена</div>`;
const planet = planetsById.get(unit.production_planet_id);
// Материалы базового юнита
const materials = dataScript.unitMaterials
.filter(um => um.unit_id === unitId)
.map(um => {
const material = materialsById.get(um.material_id);
const isLocal = mercuryMaterials.has(um.material_id);
const massKg = unit.mass_kg ? (unit.mass_kg * um.fraction_pct / 100).toFixed(0) : null;
return { ...um, material, isLocal, massKg };
});
// Компоненты (подсборки)
const components = dataScript.unitComponents
.filter(uc => uc.assembly_id === unitId)
.map(uc => {
const component = unitsById.get(uc.component_id);
const totalMassKg = component?.mass_kg ? (component.mass_kg * uc.quantity) : null;
const isLocal = component?.production_planet_id === unit.production_planet_id;
const componentMaterials = getComponentMaterials(uc.component_id);
return {
id: uc.component_id,
name: component?.name || uc.component_id,
quantity: uc.quantity,
massKg: totalMassKg,
isLocal,
materials: componentMaterials
};
});
// Итого: материалы + компоненты (по массе)
const importMatMassKg = materials.filter(m => !m.isLocal).reduce((sum, m) => sum + (unit.mass_kg ? unit.mass_kg * m.fraction_pct / 100 : 0), 0);
const totalMatMassKg = materials.reduce((sum, m) => sum + (unit.mass_kg ? unit.mass_kg * m.fraction_pct / 100 : 0), 0);
const importCompMassKg = components.filter(c => !c.isLocal).reduce((sum, c) => sum + (c.massKg || 0), 0);
const totalCompMassKg = components.reduce((sum, c) => sum + (c.massKg || 0), 0);
const totalMassKg = totalMatMassKg + totalCompMassKg;
const importPct = totalMassKg > 0 ? (importMatMassKg + importCompMassKg) / totalMassKg * 100 : 0;
const localPct = 100 - importPct;
const hasCriticalImports = components.some(c => !c.isLocal);
// Строки таблицы компонентов
const componentRows = components.map(c => html`
<tr class="component-row">
<td>
<strong>${c.name}</strong>${c.quantity > 1 ? html` ×${c.quantity}` : ''}
<div class="component-materials">
${c.materials.map(m => html`<span class="${m.isLocal ? 'local' : 'import'}">${m.symbol || m.name} ${m.fraction_pct}%</span>`)}
</div>
</td>
<td>${formatMass(c.massKg)}</td>
<td class="${c.isLocal ? 'local' : 'import'}">${c.isLocal ? '✓' : '⚠ импорт'}</td>
</tr>
`);
// Строки таблицы материалов
const materialRows = materials.map(m => html`
<tr>
<td>
${m.material?.symbol ? html`<strong>${m.material.symbol}</strong> ` : ''}
${m.material?.name || m.material_id}
</td>
<td>${m.fraction_pct}%</td>
<td>${formatMass(parseFloat(m.massKg))}</td>
<td class="${m.isLocal ? 'local' : 'import'}">${m.isLocal ? '✓ местный' : '⚠ импорт'}</td>
</tr>
`);
return html`
<div class="db-widget">
<h4>${unit.name} <span style="color: #6c757d; font-weight: normal;">(${unit.id})</span></h4>
<div class="meta">
${unit.mass_kg ? html`<strong>${unit.mass_kg.toLocaleString()}</strong> кг` : ''}
${unit.power_kw ? html` | <strong>${unit.power_kw.toLocaleString()}</strong> кВт` : ''}
${planet ? html` | Производство: ${planet.name}` : ''}
</div>
${components.length > 0 ? html`
<div class="section-header">Компоненты</div>
<table class="components-table">
<thead><tr><th>Компонент</th><th>Масса</th><th>Источник</th></tr></thead>
<tbody>${componentRows}</tbody>
</table>
` : ''}
${materials.length > 0 ? html`
<div class="section-header">Материалы</div>
<table class="materials-table">
<thead><tr><th>Материал</th><th>%</th><th>Масса</th><th>Источник</th></tr></thead>
<tbody>${materialRows}</tbody>
</table>
<div class="summary">
Итого: <span class="local-pct">${importPct > 0 && importPct < 1 ? '>99' : localPct.toFixed(0)}% местное</span> |
<span class="import-pct">${importPct > 0 && importPct < 1 ? '<1' : importPct.toFixed(0)}% импорт</span>
${hasCriticalImports ? html`<br/><strong>⚠ Содержит импортные компоненты</strong>` : ''}
</div>
` : ''}
<div class="link">
<a href="data/nomenclature.html?unit=${unitId}">Подробнее →</a>
</div>
</div>
`;
}