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'; 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, intermediates: Record, outputs: Record, ranges: Record, diagramType: string, diagramProps: Record, nbrSection: 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, }; } 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, ): 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 }); const pressures: Record = {}; 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', ); } 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, ): AuditScenario { const { s2, vk, q } = calcWind(v0, s1, s3, cat, d, 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', ); } 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, )); { 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', )); } 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, )); 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 = { '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', )); } 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, )); { 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', )); } // 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', )); 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, q }, { vk, 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', phi: 0.2, alphaWind: 0 }, { section: 'square', phi: 0.4, alphaWind: 45 }, { section: 'triangular', phi: 0.3, alphaWind: 0 }, { section: 'square', phi: 0.5, alphaWind: 30 }, { section: 'triangular', phi: 0.25, alphaWind: 0 }, { section: 'square', phi: 0.6, alphaWind: 90 }, { section: 'triangular', phi: 0.35, alphaWind: 45 }, { section: 'square', phi: 0.45, alphaWind: 0 }, ]; let ti = 0; for (const tp of towerParams) { scenarios.push(makeScenario( `tower-varredura-${ti++}`, 'tower', `Torre ${tp.section} phi=${tp.phi} α=${tp.alphaWind}°`, { ...tp, q: 0.981 }, { phi: tp.phi }, { ca: 1.8 }, { phi: { min: 0.05, max: 1.0 }, ca: { min: 0.6, max: 3.6 } }, 'tower', { section: tp.section, baseWidth: 5, height: 30, panels: 6, phi: tp.phi, alphaWind: tp.alphaWind, forceKN: 25.0 }, 'Sec. 8.5', )); } return scenarios; } export function groupScenariosByModule(scenarios: AuditScenario[]): Record { const groups: Partial> = {}; for (const s of scenarios) { if (!groups[s.module]) groups[s.module] = []; groups[s.module]!.push(s); } return groups as Record; }