Files
BrainWind/app/src/lib/audit/scenarios.ts
T

562 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AuditScenario, ModuleType } from './types';
import { MODULE_LABELS } from './types';
import {
determineStructureClass,
calculateS2,
calculateVk,
calculateDynamicPressure,
type TerrainCategory,
} from '../wind-kernel';
import { computeCpiSimplified, clampCpi, type PermeabilityCase } from '../internal-pressure';
import { getWallCpeOfficial, getRoofCpeOfficial } from '../coefficients';
import { getDragCoefficient } from '../drag';
import { calculateFriction } from '../friction';
import { getCpeCylinderProfile } from '../nbr-tables/table-13';
import { calculateSign, type SignInput } from '../nbr-tables/table-23';
import { evaluateComfort } from '../comfort';
import { classifyBridge, type BridgeClassificationInput } from '../modules/bridge';
import { calculateTower } from '../modules/tower';
import { calculatePiperack, type PiperackInput } from '../modules/piperack';
function calcWind(
v0: number, s1: number, s3: number,
cat: TerrainCategory, dim: number, z: number,
) {
const structCls = determineStructureClass(dim);
const s2 = calculateS2(z, cat, structCls);
const vk = calculateVk(v0, s1, s2, s3);
const q = calculateDynamicPressure(vk);
return { structClass: structCls, s2, vk, q };
}
function makeScenario(
id: string,
module: ModuleType,
description: string,
inputs: Record<string, unknown>,
intermediates: Record<string, number>,
outputs: Record<string, unknown>,
ranges: Record<string, { min: number; max: number }>,
diagramType: string,
diagramProps: Record<string, unknown>,
nbrSection: string,
isBibliographic?: boolean,
bibliografiaReferencia?: string,
enunciadoParafraseado?: string,
): AuditScenario {
return {
id,
module,
moduleLabel: MODULE_LABELS[module]?.['pt-BR'] ?? module,
description,
inputs,
intermediates,
outputs,
expectedRanges: ranges,
diagramType: diagramType as import('./types').DiagramType,
diagramProps,
nbrSection,
isBibliographic,
bibliografiaReferencia,
enunciadoParafraseado,
};
}
function galpaoScenario(
id: string,
description: string,
v0: number, s1: number, s3: number,
cat: TerrainCategory,
width: number, length: number, height: number, roofPitch: number,
windAngle: 0 | 90,
permCase: PermeabilityCase, cpiRatio: number,
isBibliographic?: boolean,
bibliografiaReferencia?: string,
enunciadoParafraseado?: string,
): AuditScenario {
const dim = Math.max(width, length);
const { s2, vk, q } = calcWind(v0, s1, s3, cat, dim, height);
const cpiRaw = computeCpiSimplified({ case: permCase, ratio: cpiRatio, windAngle });
const cpi = clampCpi(cpiRaw);
const wallCpe = getWallCpeOfficial(length, width, height, windAngle);
const roofCpe = getRoofCpeOfficial(length, width, height, roofPitch, windAngle);
const drag = getDragCoefficient(width, length, height, 'low');
const fric = calculateFriction({ roughness: 'smooth', length, height, width, roofPitch, q, windAngle });
const pressures: Record<string, number> = {};
for (const [zone, cpe] of Object.entries({ ...wallCpe, ...roofCpe })) {
if (typeof cpe === 'number') pressures[zone] = Number((q * (cpe - cpi)).toFixed(3));
}
return makeScenario(
id, 'galpao', description,
{ v0, s1, s3, terrainCategory: cat, width, length, height, roofPitch, windAngle, permCase, cpiRatio },
{ s2, vk, q, cpi },
{ wallCpe, roofCpe, drag, fricApplies: fric.applies ? 1 : 0, fricForce: fric.forceKN, pressures },
{
q: { min: 0.3, max: 5.0 },
cpi: { min: -0.9, max: 0.9 },
s2: { min: 0.5, max: 1.5 },
'wallCpe.A': { min: -1.5, max: 0.5 },
'wallCpe.C': { min: 0.0, max: 1.5 },
'roofCpe.E': { min: -2.5, max: 0.5 },
drag: { min: 0.9, max: 2.0 },
},
'warehouse',
{ width, length, height, roofPitch, wallCpe, roofCpe, windAngle: windAngle as 0 | 90, cpi },
'Sec. 6.1',
isBibliographic,
bibliografiaReferencia,
enunciadoParafraseado,
);
}
function cylinderScenario(
id: string,
description: string,
v0: number, s1: number, s3: number,
cat: TerrainCategory,
d: number, h: number,
surface: 'rough' | 'smooth',
endType: 'closed' | 'open-top' | 'open-bottom' | 'open-both',
_windAngle: 0 | 90,
isBibliographic?: boolean,
bibliografiaReferencia?: string,
enunciadoParafraseado?: string,
): AuditScenario {
const { s2, vk, q } = calcWind(v0, s1, s3, cat, Math.max(d, h), h);
const re = 70000 * vk * d;
const hOverD = h / d;
let cpiVal: number;
if (endType === 'open-top') cpiVal = hOverD >= 0.3 ? -0.8 : -0.5;
else if (endType === 'open-bottom') cpiVal = -0.5;
else if (endType === 'open-both') cpiVal = -0.7;
else cpiVal = 0;
const cpi = clampCpi(cpiVal);
const profile = getCpeCylinderProfile(hOverD, surface, 13);
return makeScenario(
id, 'cilindro', description,
{ v0, s1, s3, terrainCategory: cat, d, h, surface, endType, vk },
{ s2, vk, q, re, hOverD, cpi, supercritical: re > 400_000 ? 1 : 0 },
{ profile: profile.map(p => ({ angle: p.angle, cpe: p.cpe })), cpi },
{
q: { min: 0.3, max: 5.0 },
cpi: { min: -0.9, max: 0.9 },
re: { min: 0, max: 50000000 },
},
'cylinder',
{ diameter: d, height: h, cpi, cpeProfile: profile.map(p => ({ angle: p.angle, cpe: p.cpe })) },
'Sec. 6.2.1',
isBibliographic,
bibliografiaReferencia,
enunciadoParafraseado,
);
}
export function generateAllScenarios(): AuditScenario[] {
const scenarios: AuditScenario[] = [];
// 1. BLESSMANN CASES
scenarios.push(galpaoScenario(
'blessmann-galpao-30x15x6', 'Blessmann: Galpão 30x15x6 Cat II V0=40',
40, 1.0, 1.0, 'II', 15, 30, 6, 10, 0, 'four-equally-permeable', 1.0,
true,
'Pfeil & Pfeil, Estruturas de Aço, 8ª ed. (Exemplo clássico de galpão adaptado e Blessmann Cap. 5, Exemplo 5.1)',
'Cenário de Validação estruturado com base nos parâmetros físicos típicos da literatura. O teste valida a ação do vento transversal (0°) em pórtico de duas águas simétrico (30x15x6 m) em terreno plano.'
));
{
const v0 = 40, s1 = 1.0, s3 = 1.0, cat: TerrainCategory = 'III', dim = 60, z = 100;
const { s2, vk, q } = calcWind(v0, s1, s3, cat, dim, z);
scenarios.push(makeScenario(
'blessmann-edificio-60x20x100', 'galpao', 'Blessmann: Edifício 60x20x100 Cat III V0=40',
{ v0, s1, s3, cat, dim, z },
{ s2, vk, q },
{},
{ q: { min: 0.8, max: 2.0 }, s2: { min: 1.0, max: 1.4 }, vk: { min: 35, max: 60 } },
'warehouse',
{ width: 20, length: 60, height: 100, roofPitch: 0, wallCpe: { A: -0.9, B: -0.6, C: 0.7, D: -0.5 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.3',
true,
'Blessmann, J. O Vento na Engenharia Estrutural, Cap. 9',
'Validação de edifício alto paralelepipédico de seção retangular 20x60m e altura 100m. Avaliação da variação do fator S2 com a altura (z) até 100m.'
));
}
scenarios.push(cylinderScenario(
'blessmann-silo-d8-h24', 'Blessmann: Silo d=8m h=24m smooth',
40, 1.0, 1.0, 'II', 8, 24, 'smooth', 'open-top', 0,
true,
'NBR 6123 Tabela 13 (Cilindros) / Blessmann Cap. 6',
'Cilindro liso com diâmetro de 8m e altura de 24m. Validação do Número de Reynolds (Re) no topo do silo e coeficientes de pressão externa nos ângulos característicos.'
));
for (const z of [5, 10, 20, 50, 100]) {
const { s2 } = calcWind(40, 1.0, 1.0, 'II', 30, z);
scenarios.push(makeScenario(
`blessmann-s2-z${z}`, 'galpao', `Blessmann: S2 em z=${z}m Cat II`,
{ v0: 40, s1: 1.0, s3: 1.0, cat: 'II', dim: 30, z },
{ s2 },
{ s2 },
{ s2: { min: 0.5, max: 1.5 } },
'warehouse',
{ width: 15, length: 30, height: 10, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.3',
));
}
{
const S3_VALS: Record<string, number> = { '1': 1.11, '2': 1.06, '3': 1.00, '4': 0.95, '5': 0.83 };
for (const [grp, s3val] of Object.entries(S3_VALS)) {
scenarios.push(makeScenario(
`blessmann-s3-grupo${grp}`, 'galpao', `Blessmann: S3 Grupo ${grp} = ${s3val}`,
{ v0: 40, s1: 1.0, s3: s3val, cat: 'II', dim: 30, z: 10 },
{ s3: s3val },
{ s3: s3val },
{ s3: { min: 0.7, max: 1.2 } },
'warehouse',
{ width: 15, length: 30, height: 10, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.4',
));
}
}
{
const bridgeInput: BridgeClassificationInput = {
lp: 120, width: 14, massPerLength: 18000, fv: 0.6,
v0: 40, s1: 1.0, deckHeight: 15, category: 'II',
};
const result = classifyBridge(bridgeInput);
scenarios.push(makeScenario(
'blessmann-ponte-120m', 'bridge', 'Blessmann: Ponte Lp=120m Cat II',
{ ...bridgeInput },
{ pse: result.pse, vit: result.vit },
{ pse: result.pse, vit: result.vit, bridgeClass: result.bridgeClass },
{ pse: { min: 0.01, max: 2.0 }, vit: { min: 15, max: 50 } },
'bridge',
{ lp: 120, width: 14, deckHeight: 15, heg: 1.5, cx: 1.2, fxPerLength: 16.8, fzPerLength: -2.1 },
'Sec. 11.2',
true,
'Blessmann, J. NBR 6123 Aplicações, Cap. 11',
'Determinação das forças de arrasto e sustentação para uma ponte de 120m de vão em terreno Categoria II. O tabuleiro possui 14m de largura, 15m de altura livre e massa de 18 ton/m. Avalia-se as reações estáticas aerodinâmicas.'
));
}
scenarios.push(cylinderScenario(
'blessmann-chamine-d1.5-h30', 'Blessmann: Chaminé d=1.5m h=30m rough',
40, 1.0, 1.0, 'II', 1.5, 30, 'rough', 'closed', 0,
true,
'Blessmann, J. Ação do Vento em Chaminés, Exercício Resolvido',
'Cálculo das pressões normais em uma chaminé circular de 30 metros de altura por 1.5m de diâmetro (superfície rugosa, topo fechado).'
));
{
const signInput: SignInput = { length: 6, height: 2, groundClearance: 3, alpha: 90, hasEndPlates: false };
const q = 0.981;
const signResult = calculateSign(signInput, q);
scenarios.push(makeScenario(
'blessmann-placa-6x2', 'sign', 'Blessmann: Placa 6x2m',
{ length: 6, height: 2, groundClearance: 3, alpha: 90, hasEndPlates: false, q },
{ cf: signResult.cf },
{ cf: signResult.cf, forceKN: signResult.forceKN },
{ cf: { min: 0.8, max: 2.0 }, forceKN: { min: 5, max: 30 } },
'sign',
{ length: 6, height: 2, groundClearance: 3, alpha: 90, cf: signResult.cf, forceKN: signResult.forceKN },
'Sec. 7.1',
true,
'Pfeil & Pfeil, Estruturas de Aço, Cap. Placas e Muros',
'Determinação da força de arrasto do vento a 90° em uma placa retangular isolada de 6x2m. A placa encontra-se içada com uma folga em relação ao solo de 3 metros, sem placas de extremidade (endplates).'
));
}
// 2. GALPÃO VARREDURA
const galpaoGeos = [
{ w: 10, l: 20, h: 4 }, { w: 15, l: 30, h: 6 }, { w: 20, l: 50, h: 8 },
{ w: 30, l: 60, h: 10 }, { w: 8, l: 15, h: 5 }, { w: 25, l: 40, h: 7 },
{ w: 40, l: 80, h: 12 }, { w: 12, l: 24, h: 5 }, { w: 18, l: 36, h: 6 },
];
const cats: TerrainCategory[] = ['I', 'II'];
let gIdx = 0;
for (const geo of galpaoGeos) {
for (const cat of cats) {
scenarios.push(galpaoScenario(
`galpao-varredura-${gIdx++}`, `Galpão ${geo.w}x${geo.l}x${geo.h} Cat ${cat}`,
40, 1.0, 1.0, cat, geo.w, geo.l, geo.h, 10, 0, 'four-equally-permeable', 1.0,
));
}
}
// 3. CILINDRO VARREDURA
const cylParams = [
{ d: 2, h: 10 }, { d: 5, h: 20 }, { d: 8, h: 24 }, { d: 10, h: 30 },
{ d: 3, h: 15 }, { d: 6, h: 18 }, { d: 4, h: 12 }, { d: 7, h: 21 },
{ d: 1.2, h: 8 }, { d: 9, h: 27 },
];
let cIdx = 0;
for (const p of cylParams) {
scenarios.push(cylinderScenario(
`cilindro-varredura-${cIdx++}`, `Cilindro d=${p.d} h=${p.h} smooth closed`,
40, 1.0, 1.0, 'II', p.d, p.h, 'smooth', 'closed', 0,
));
}
// 4. CPI VARIANTS
const permCases: PermeabilityCase[] = [
'two-opposite-permeable', 'four-equally-permeable',
'dominant-windward', 'dominant-leeward',
'dominant-lateral', 'airtight',
];
let pi = 0;
for (const pc of permCases) {
const cpiRaw = computeCpiSimplified({ case: pc, ratio: 1.0, windAngle: 0 });
const cpi = clampCpi(cpiRaw);
scenarios.push(makeScenario(
`cpi-variant-${pi++}`, 'galpao', `Cpi variant: ${pc}`,
{ permCase: pc, ratio: 1.0, windAngle: 0 },
{ cpi, cpiRaw },
{ cpi },
{ cpi: { min: -0.9, max: 0.9 } },
'warehouse',
{ width: 15, length: 30, height: 6, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi },
'Sec. 6.3',
));
}
// 5. EDGE CASES
scenarios.push(cylinderScenario(
'edge-cilindro-hd-extremo', 'Edge: Cilindro h/d=0.3',
40, 1.0, 1.0, 'II', 10, 3, 'smooth', 'closed', 0,
));
{
const { s2, vk, q } = calcWind(40, 1.0, 1.0, 'V', 30, 5);
scenarios.push(makeScenario(
'edge-s2-min-z5-catV', 'galpao', 'Edge: S2 mínimo z=5m Cat V',
{ v0: 40, s1: 1.0, s3: 1.0, cat: 'V', dim: 30, z: 5 },
{ s2, vk, q },
{ s2, vk, q },
{ s2: { min: 0.5, max: 0.8 }, q: { min: 0.3, max: 1.5 } },
'warehouse',
{ width: 15, length: 30, height: 5, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.3',
));
}
{
const { s2, vk, q } = calcWind(40, 1.0, 1.0, 'IV', 30, 500);
scenarios.push(makeScenario(
'edge-s2-saturado-z500-catIV', 'galpao', 'Edge: S2 saturado z=500m Cat IV',
{ v0: 40, s1: 1.0, s3: 1.0, cat: 'IV', dim: 30, z: 500 },
{ s2, vk, q },
{ s2, vk, q },
{ s2: { min: 1.2, max: 1.4 } },
'warehouse',
{ width: 15, length: 30, height: 10, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.3',
));
}
for (const dim of [20, 21, 50, 51]) {
const { structClass, s2 } = calcWind(40, 1.0, 1.0, 'II', dim, 10);
scenarios.push(makeScenario(
`edge-classe-${dim}m`, 'galpao', `Edge: Classe boundary dim=${dim}m`,
{ v0: 40, s1: 1.0, s3: 1.0, cat: 'II', dim, z: 10 },
{ s2 },
{ s2, structClass },
{ s2: { min: 0.8, max: 1.3 } },
'warehouse',
{ width: 15, length: 30, height: 10, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 5.3.2',
));
}
for (const v0 of [20, 50, 60]) {
const { vk, q } = calcWind(v0, 1.0, 1.0, 'II', 30, 10);
scenarios.push(makeScenario(
`edge-v0-${v0}`, 'galpao', `Edge: V0=${v0} m/s`,
{ v0, s1: 1.0, s3: 1.0, cat: 'II', dim: 30, z: 10 },
{ vk, q },
{ vk, q },
{ q: { min: 0.1, max: 3.0 }, vk: { min: 15, max: 65 } },
'warehouse',
{ width: 15, length: 30, height: 10, roofPitch: 10, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 4.2',
));
}
// 6. CROSS-MODULE
{
const v0 = 45, cat: TerrainCategory = 'III', z = 15;
const { vk, q } = calcWind(v0, 1.0, 1.0, cat, 25, z);
scenarios.push(makeScenario(
'cross-v45-catIII-galpao', 'galpao', 'Cross-module: V0=45 Cat III — Galpão',
{ v0, s1: 1.0, s3: 1.0, cat, dim: 25, z },
{ vk, q },
{ vk, q },
{ q: { min: 0.8, max: 2.0 } },
'warehouse',
{ width: 15, length: 25, height: 8, roofPitch: 12, wallCpe: { A: -0.8, B: -0.5, C: 0.7, D: -0.4 }, roofCpe: { E: -0.8, F: -0.4, G: 0.2, H: -0.3, I: -0.5, J: 0 }, windAngle: 0 as const, cpi: 0 },
'Sec. 6.1',
));
const cylWind = calcWind(v0, 1.0, 1.0, cat, 20, 20); // cil h=20, max(d,h)=20, z=20
scenarios.push(makeScenario(
'cross-v45-catIII-cilindro', 'cilindro', 'Cross-module: V0=45 Cat III — Cilindro',
{ v0, s1: 1.0, s3: 1.0, cat, d: 5, h: 20 },
{ vk: cylWind.vk, q: cylWind.q },
{ vk: cylWind.vk, q: cylWind.q },
{ q: { min: 0.8, max: 2.0 } },
'cylinder',
{ diameter: 5, height: 20, cpi: -0.8, cpeProfile: [{ angle: 0, cpe: 1.0 }, { angle: 90, cpe: -1.2 }] },
'Sec. 6.2.1',
));
}
// 7. DINÂMICA
const comfortFreqs = [0.1, 0.2, 0.5, 0.8];
const comfortUses: ('residential' | 'commercial')[] = ['residential', 'commercial'];
let di = 0;
for (const freq of comfortFreqs) {
for (const use of comfortUses) {
const aMax = 4 * Math.PI * Math.PI * freq * freq * 0.01;
const result = evaluateComfort({ freq, aMax, use });
scenarios.push(makeScenario(
`dynamics-conforto-${di++}`, 'dynamics', `Conforto f=${freq}Hz ${use}`,
{ freq, aMax, use },
{ aLim: result.aLim, ratio: result.ratio },
{ ok: result.ok ? 1 : 0, aLim: result.aLim },
{ aLim: { min: 0, max: 0.5 }, ratio: { min: 0, max: 5 } },
'dynamics',
{ height: 100, freq, windSpeed: 40, scruton: 10, sectionShape: 'circle', sectionSize: 1.5, showVortexStreet: true, showModeShape: true },
'Sec. 9.6',
));
}
}
// 8. PONTES VARREDURA
const bridgeParams = [
{ lp: 80, width: 10, m: 12000, fv: 0.4, z: 10 },
{ lp: 120, width: 14, m: 18000, fv: 0.6, z: 15 },
{ lp: 200, width: 20, m: 30000, fv: 0.3, z: 20 },
{ lp: 60, width: 8, m: 8000, fv: 0.8, z: 8 },
];
const bridgeCats: TerrainCategory[] = ['I', 'II'];
let bi = 0;
for (const bp of bridgeParams) {
for (const cat of bridgeCats) {
const input: BridgeClassificationInput = {
lp: bp.lp, width: bp.width, massPerLength: bp.m, fv: bp.fv,
v0: 40, s1: 1.0, deckHeight: bp.z, category: cat,
};
const result = classifyBridge(input);
scenarios.push(makeScenario(
`ponte-varredura-${bi++}`, 'bridge', `Ponte Lp=${bp.lp}m Cat ${cat}`,
{ ...input },
{ pse: result.pse, vit: result.vit },
{ pse: result.pse, vit: result.vit, bridgeClass: result.bridgeClass },
{ pse: { min: 0.001, max: 3.0 }, vit: { min: 10, max: 60 } },
'bridge',
{ lp: bp.lp, width: bp.width, deckHeight: bp.z, heg: 1.5, cx: 1.2, fxPerLength: 14.1, fzPerLength: -1.8 },
'Sec. 11.2',
));
}
}
// 9. SIGN VARREDURA
const signInputs: SignInput[] = [
{ length: 6, height: 2, groundClearance: 3, alpha: 90, hasEndPlates: false },
{ length: 10, height: 4, groundClearance: 5, alpha: 90, hasEndPlates: false },
{ length: 3, height: 6, groundClearance: 2, alpha: 90, hasEndPlates: false },
{ length: 8, height: 3, groundClearance: 4, alpha: 50, hasEndPlates: false },
{ length: 15, height: 5, groundClearance: 8, alpha: 90, hasEndPlates: false },
{ length: 4, height: 8, groundClearance: 1, alpha: 90, hasEndPlates: false },
];
let si = 0;
for (const sp of signInputs) {
const q = 0.981;
const result = calculateSign(sp, q);
scenarios.push(makeScenario(
`sign-varredura-${si++}`, 'sign', `Sign: ${sp.length}x${sp.height}m α=${sp.alpha}°`,
{ ...sp, q },
{ cf: result.cf },
{ cf: result.cf, forceKN: result.forceKN },
{ cf: { min: 0.8, max: 2.5 }, forceKN: { min: 1, max: 50 } },
'sign',
{ length: sp.length, height: sp.height, groundClearance: sp.groundClearance, alpha: sp.alpha, cf: result.cf, forceKN: result.forceKN },
'Sec. 7.1',
));
}
// 10. TOWERS VARREDURA
const towerParams = [
{ section: 'square' as const, phi: 0.2, alphaWind: 0 as const },
{ section: 'square' as const, phi: 0.4, alphaWind: 45 as const },
{ section: 'triangular' as const, phi: 0.3, alphaWind: 0 as const },
{ section: 'square' as const, phi: 0.5, alphaWind: 45 as const },
{ section: 'triangular' as const, phi: 0.25, alphaWind: 0 as const },
{ section: 'square' as const, phi: 0.6, alphaWind: 90 as const },
{ section: 'triangular' as const, phi: 0.35, alphaWind: 45 as const },
{ section: 'square' as const, phi: 0.45, alphaWind: 0 as const },
];
let ti = 0;
for (const tp of towerParams) {
const q = 0.981;
const aFace = 5 * 30; // baseWidth = 5, height = 30
const tr = calculateTower({
section: tp.section,
barType: 'flat',
phi: tp.phi,
aFace,
alphaWind: tp.alphaWind,
q,
});
scenarios.push(makeScenario(
`tower-varredura-${ti++}`, 'tower', `Torre ${tp.section} phi=${tp.phi} α=${tp.alphaWind}°`,
{ ...tp, q },
{ phi: tp.phi },
{ ca: tr.caEff, forceKN: tr.forceKN },
{ phi: { min: 0.05, max: 1.0 }, ca: { min: 0.6, max: 3.6 }, forceKN: { min: 1, max: 1000 } },
'tower',
{ section: tp.section, baseWidth: 5, height: 30, panels: 6, phi: tp.phi, alphaWind: tp.alphaWind, forceKN: tr.forceKN },
'Sec. 8.5',
));
}
// 11. PIPERACK VARREDURA
const piperackParams: Array<{ name: string; input: Omit<PiperackInput, 'q'> }> = [
{ name: 'Pipe-1', input: { width: 10, height: 4, elevation: 6, spacing: 5, numFrames: 3, phiStruct: 0.15, pipes: [{ id: 'p1', diameter: 1.0, elevationOffset: 4 }] } },
{ name: 'Pipe-2', input: { width: 12, height: 5, elevation: 8, spacing: 6, numFrames: 4, phiStruct: 0.20, pipes: [{ id: 'p1', diameter: 0.5, elevationOffset: 5 }, { id: 'p2', diameter: 0.5, elevationOffset: 5 }] } },
{ name: 'Pipe-3', input: { width: 8, height: 3, elevation: 5, spacing: 4, numFrames: 2, phiStruct: 0.10, pipes: [] } },
{ name: 'Pipe-4', input: { width: 15, height: 6, elevation: 10, spacing: 6, numFrames: 5, phiStruct: 0.30, pipes: [{ id: 'p1', diameter: 1.5, elevationOffset: 6 }] } },
{ name: 'Pipe-5', input: { width: 20, height: 8, elevation: 12, spacing: 8, numFrames: 3, phiStruct: 0.25, pipes: [{ id: 'p1', diameter: 2.0, elevationOffset: 8 }, { id: 'p2', diameter: 1.0, elevationOffset: 6 }] } },
{ name: 'Pipe-6', input: { width: 10, height: 5, elevation: 7, spacing: 3, numFrames: 6, phiStruct: 0.40, pipes: [{ id: 'p1', diameter: 0.8, elevationOffset: 5 }] } },
{ name: 'Pipe-7', input: { width: 6, height: 4, elevation: 4, spacing: 4, numFrames: 2, phiStruct: 0.50, pipes: [{ id: 'p1', diameter: 1.0, elevationOffset: 4 }] } },
{ name: 'Pipe-8', input: { width: 25, height: 10, elevation: 15, spacing: 10, numFrames: 4, phiStruct: 0.20, pipes: [{ id: 'p1', diameter: 3.0, elevationOffset: 10 }] } },
];
let pi_idx = 0;
for (const pp of piperackParams) {
const q = 0.981;
const input: PiperackInput = { ...pp.input, q };
const tr = calculatePiperack(input);
scenarios.push(makeScenario(
`piperack-varredura-${pi_idx++}`, 'piperack', `Piperack ${pp.name}: w=${input.width} h=${input.height} n=${input.numFrames}`,
{ ...input },
{ phiTotal: tr.phiTotal, effectiveElevation: tr.effectiveElevation },
{ caFrontal: tr.caFrontal, eta: tr.eta, globalForce: tr.globalForce },
{ phiTotal: { min: 0.1, max: 1.0 }, caFrontal: { min: 1.6, max: 3.0 }, eta: { min: 0.3, max: 1.0 }, globalForce: { min: 1, max: 5000 } },
'warehouse', // Usamos warehouse como fallback visual ou você pode ter um piperack
{ width: input.width, height: input.height, length: input.spacing * input.numFrames }, // Props mínimas
'Sec. 8.4', // Estruturas reticuladas
));
}
return scenarios;
}
export function groupScenariosByModule(scenarios: AuditScenario[]): Record<ModuleType, AuditScenario[]> {
const groups: Partial<Record<ModuleType, AuditScenario[]>> = {};
for (const s of scenarios) {
if (!groups[s.module]) groups[s.module] = [];
groups[s.module]!.push(s);
}
return groups as Record<ModuleType, AuditScenario[]>;
}