import ApiService from './ApiService'; /** * Service pour les calculs techniques ultra-détaillés BTP * Le plus ambitieux système de calculs BTP d'Afrique */ // =================== INTERFACES PARAMÈTRES =================== export interface ParametresCalculBriques { surface: number; epaisseurMur: number; codeBrique: string; zoneClimatique: string; typeAppareillage: 'DROIT' | 'QUINCONCE' | 'FLAMAND' | 'ANGLAIS'; jointHorizontal: number; jointVertical: number; ouvertures: Ouverture[]; } export interface Ouverture { largeur: number; hauteur: number; } export interface ParametresCalculMortier { volumeMaconnerie: number; typeMortier: 'POSE_BRIQUES' | 'JOINTOIEMENT' | 'ENDUIT_BASE' | 'ENDUIT_FINITION' | 'STANDARD'; zoneClimatique: string; } export interface ParametresCalculBetonArme { volume: number; classeBeton: 'C20/25' | 'C25/30' | 'C30/37' | 'C35/45'; classeExposition: 'XC1' | 'XC3' | 'XC4' | 'XS1' | 'XS3'; typeOuvrage: 'DALLE' | 'POUTRE' | 'POTEAU' | 'VOILE'; epaisseur: number; zoneClimatique: string; } // =================== INTERFACES RÉSULTATS =================== export interface ResultatCalculBriques { nombreBriques: number; nombrePalettes: number; briquesParM2: number; surfaceNette: number; mortier: ResultatCalculMortier; facteurPerte: number; facteurClimatique: number; nombreCouches: number; recommendationsZone: string[]; } export interface ResultatCalculMortier { volumeTotal: number; cimentKg: number; sableLitres: number; eauLitres: number; sacs50kg: number; } export interface ResultatCalculBetonArme { volume: number; cimentKg: number; cimentSacs50kg: number; sableKg: number; sableM3: number; graviersKg: number; graviersM3: number; eauLitres: number; acierKgTotal: number; repartitionAcier: Record; // diamètre -> poids en kg enrobage: number; dosageAdapte: DosageBeton; adaptationsClimatiques: string[]; } export interface DosageBeton { ciment: number; // kg/m³ eau: number; // L/m³ graviers: number; // kg/m³ sable: number; // kg/m³ } export interface DosageBetonInfo { usage: string; ciment: string; resistance: string; exposition: string; } // =================== CLASSES DE SERVICE =================== export class CalculsTechniquesService { private static readonly BASE_PATH = '/api/v1/calculs-techniques'; // =================== CALCULS MAÇONNERIE =================== /** * Calcul ultra-précis quantité briques pour mur * Prend en compte dimensions exactes, joints, appareillage, pertes, zone climatique */ static async calculerBriquesMur(params: ParametresCalculBriques): Promise { const response = await ApiService.post( `${this.BASE_PATH}/briques-mur`, params ); return response; } /** * Calcul mortier pour maçonnerie traditionnelle */ static async calculerMortierMaconnerie(params: ParametresCalculMortier): Promise { const response = await ApiService.post( `${this.BASE_PATH}/mortier-maconnerie`, params ); return response; } /** * Estimation rapide briques pour surface donnée */ static async estimationRapideBriques(surface: number, typeBrique: string = 'brique-rouge-15x10x5'): Promise<{ estimationBasse: number; estimationHaute: number; estimationMoyenne: number; baseCalcul: string; }> { // Calcul côté client pour estimation rapide const briquesParM2Moyen = 67; // Moyenne pour brique 15x10x5cm const facteurPerteMoyen = 1.08; // 8% de perte moyenne const estimationMoyenne = Math.ceil(surface * briquesParM2Moyen * facteurPerteMoyen); const estimationBasse = Math.ceil(estimationMoyenne * 0.85); const estimationHaute = Math.ceil(estimationMoyenne * 1.25); return { estimationBasse, estimationHaute, estimationMoyenne, baseCalcul: `Surface: ${surface}m² × ${briquesParM2Moyen} briques/m² × ${facteurPerteMoyen} (pertes)` }; } // =================== CALCULS BÉTON ARMÉ =================== /** * Calcul béton armé avec adaptation climatique africaine */ static async calculerBetonArme(params: ParametresCalculBetonArme): Promise { const response = await ApiService.post( `${this.BASE_PATH}/beton-arme`, params ); return response; } /** * Récupère les dosages béton standard avec adaptations climatiques */ static async getDosagesBeton(): Promise<{ dosages: Record; notes: string[]; }> { const response = await ApiService.get<{ dosages: Record; notes: string[]; }>(`${this.BASE_PATH}/dosages-beton`); return response; } /** * Estimation rapide béton pour volume donné */ static async estimationRapideBeton(volume: number, classeBeton: string = 'C25/30'): Promise<{ cimentSacs: number; sableM3: number; graviersM3: number; eauLitres: number; coutEstime: number; }> { // Dosages moyens selon classe const dosages = { 'C20/25': { ciment: 300, sable: 1100, graviers: 650, eau: 165 }, 'C25/30': { ciment: 350, sable: 1050, graviers: 600, eau: 175 }, 'C30/37': { ciment: 385, sable: 1000, graviers: 580, eau: 180 }, 'C35/45': { ciment: 420, sable: 950, graviers: 550, eau: 185 } }; const dosage = dosages[classeBeton] || dosages['C25/30']; const cimentKg = volume * dosage.ciment; const cimentSacs = Math.ceil(cimentKg / 50); const sableKg = volume * dosage.sable; const sableM3 = sableKg / 1600; // densité sable const graviersKg = volume * dosage.graviers; const graviersM3 = graviersKg / 1500; // densité graviers const eauLitres = volume * dosage.eau; // Estimation coût (prix moyens Afrique de l'Ouest) const coutEstime = (cimentSacs * 8000) + // 8000 FCFA/sac (sableM3 * 25000) + // 25000 FCFA/m³ (graviersM3 * 30000) + // 30000 FCFA/m³ (eauLitres * 2); // 2 FCFA/L return { cimentSacs, sableM3: Math.ceil(sableM3 * 100) / 100, // 2 décimales graviersM3: Math.ceil(graviersM3 * 100) / 100, eauLitres, coutEstime }; } // =================== CALCULS COMPLEXES =================== /** * Calcul complet d'un mur (briques + mortier + enduit) */ static async calculerMurComplet(params: { surface: number; epaisseurMur: number; codeBrique: string; zoneClimatique: string; typeAppareillage: string; avecEnduit: boolean; typeEnduit?: 'CIMENT' | 'CHAUX' | 'PLATRE'; }): Promise<{ briques: ResultatCalculBriques; enduit?: { mortierM3: number; cimentKg: number; sableKg: number; eauLitres: number; }; coutTotal: number; tempsTotal: number; // en heures }> { // Calcul briques const paramsB: ParametresCalculBriques = { surface: params.surface, epaisseurMur: params.epaisseurMur, codeBrique: params.codeBrique, zoneClimatique: params.zoneClimatique, typeAppareillage: params.typeAppareillage as any, jointHorizontal: 10, // défaut 10mm jointVertical: 10, // défaut 10mm ouvertures: [] }; const briques = await this.calculerBriquesMur(paramsB); let enduit; if (params.avecEnduit) { // Calcul enduit (15mm d'épaisseur moyenne) const volumeEnduit = params.surface * 0.015; // 1.5cm const dosageEnduit = params.typeEnduit === 'CHAUX' ? 250 : 350; // kg/m³ enduit = { mortierM3: volumeEnduit, cimentKg: volumeEnduit * dosageEnduit, sableKg: volumeEnduit * 800, // 800kg sable/m³ mortier eauLitres: volumeEnduit * 200 // 200L eau/m³ mortier }; } // Estimation coûts et temps const coutBriques = briques.nombreBriques * 250; // 250 FCFA/brique const coutMortier = briques.mortier.cimentKg * 160; // 160 FCFA/kg ciment const coutEnduit = enduit ? enduit.cimentKg * 160 : 0; const coutTotal = coutBriques + coutMortier + coutEnduit; const tempsBriques = briques.nombreBriques * 3 / 60; // 3min/brique const tempsEnduit = params.avecEnduit ? params.surface * 45 / 60 : 0; // 45min/m² const tempsTotal = tempsBriques + tempsEnduit; return { briques, enduit, coutTotal, tempsTotal }; } /** * Calcul dalle béton complète (béton + armatures + coffrages) */ static async calculerDalleComplete(params: { surface: number; epaisseur: number; classeBeton: string; zoneClimatique: string; typeArmature: 'LEGERE' | 'NORMALE' | 'RENFORCEE'; avecCoffrage: boolean; }): Promise<{ beton: ResultatCalculBetonArme; coffrage?: { planchesM2: number; etaisNombre: number; }; coutTotal: number; tempsTotal: number; }> { const volume = params.surface * (params.epaisseur / 100); // épaisseur en cm -> m const paramsBA: ParametresCalculBetonArme = { volume, classeBeton: params.classeBeton as any, classeExposition: 'XC3', // défaut intérieur humide typeOuvrage: 'DALLE', epaisseur: params.epaisseur, zoneClimatique: params.zoneClimatique }; const beton = await this.calculerBetonArme(paramsBA); let coffrage; if (params.avecCoffrage) { // Surface coffrante = surface dalle + rives const perimetre = 2 * Math.sqrt(params.surface * 4); // approximation carré const surfaceCoffrante = params.surface + (perimetre * params.epaisseur / 100); coffrage = { planchesM2: surfaceCoffrante * 1.15, // 15% majoration etaisNombre: Math.ceil(params.surface / 2) // 1 étai par 2m² }; } // Estimation coûts const coutBeton = (beton.cimentSacs50kg * 8000) + (beton.sableM3.valueOf() * 25000) + (beton.graviersM3.valueOf() * 30000); const coutAcier = beton.acierKgTotal * 1200; // 1200 FCFA/kg acier const coutCoffrage = coffrage ? (coffrage.planchesM2 * 5000) + (coffrage.etaisNombre * 15000) : 0; const coutTotal = coutBeton + coutAcier + coutCoffrage; // Estimation temps const tempsBeton = volume * 2; // 2h/m³ const tempsArmature = beton.acierKgTotal * 0.5; // 30min/kg const tempsCoffrage = coffrage ? coffrage.planchesM2 * 0.25 : 0; // 15min/m² const tempsTotal = tempsBeton + tempsArmature + tempsCoffrage; return { beton, coffrage, coutTotal, tempsTotal }; } // =================== OUTILS UTILITAIRES =================== /** * Conversion d'unités de mesure BTP */ static convertirUnites(valeur: number, uniteSource: string, uniteDestination: string): number { const conversions: Record> = { 'm': { 'cm': 100, 'mm': 1000, 'km': 0.001 }, 'm²': { 'cm²': 10000, 'mm²': 1000000, 'ha': 0.0001 }, 'm³': { 'l': 1000, 'cm³': 1000000, 'mm³': 1000000000 }, 'kg': { 'g': 1000, 't': 0.001, 'quintal': 0.01 }, 'MPa': { 'kPa': 1000, 'Pa': 1000000, 'bar': 10 } }; if (conversions[uniteSource]?.[uniteDestination]) { return valeur * conversions[uniteSource][uniteDestination]; } throw new Error(`Conversion non supportée: ${uniteSource} vers ${uniteDestination}`); } /** * Validation des paramètres de calcul */ static validerParametres(type: 'BRIQUES' | 'MORTIER' | 'BETON', params: any): { valide: boolean; erreurs: string[]; avertissements: string[]; } { const erreurs: string[] = []; const avertissements: string[] = []; switch (type) { case 'BRIQUES': if (!params.surface || params.surface <= 0) erreurs.push('Surface requise et > 0'); if (!params.epaisseurMur || params.epaisseurMur <= 0) erreurs.push('Épaisseur mur requise et > 0'); if (!params.codeBrique) erreurs.push('Code brique requis'); if (!params.zoneClimatique) erreurs.push('Zone climatique requise'); if (params.surface > 1000) avertissements.push('Surface très importante (>1000m²)'); if (params.epaisseurMur > 30) avertissements.push('Mur très épais (>30cm)'); break; case 'BETON': if (!params.volume || params.volume <= 0) erreurs.push('Volume requis et > 0'); if (!params.classeBeton) erreurs.push('Classe béton requise'); if (!params.typeOuvrage) erreurs.push('Type ouvrage requis'); if (params.volume > 500) avertissements.push('Volume très important (>500m³)'); if (params.epaisseur && params.epaisseur < 10) avertissements.push('Épaisseur faible (<10cm)'); break; } return { valide: erreurs.length === 0, erreurs, avertissements }; } /** * Génération de devis détaillé */ static async genererDevis(calculs: { briques?: ResultatCalculBriques; mortier?: ResultatCalculMortier; beton?: ResultatCalculBetonArme; }, options?: { margeEntreprise?: number; // % tva?: number; // % delaiExecution?: number; // jours }): Promise<{ lignesDevis: Array<{ designation: string; quantite: number; unite: string; prixUnitaire: number; montantHT: number; }>; totalHT: number; totalTTC: number; delaiExecution: number; }> { const lignesDevis: Array<{ designation: string; quantite: number; unite: string; prixUnitaire: number; montantHT: number; }> = []; // Ajout lignes selon calculs if (calculs.briques) { lignesDevis.push({ designation: 'Briques terre cuite', quantite: calculs.briques.nombreBriques, unite: 'unité', prixUnitaire: 250, montantHT: calculs.briques.nombreBriques * 250 }); } if (calculs.beton) { lignesDevis.push({ designation: 'Ciment Portland CEM I', quantite: calculs.beton.cimentSacs50kg, unite: 'sac 50kg', prixUnitaire: 8000, montantHT: calculs.beton.cimentSacs50kg * 8000 }); } const totalHT = lignesDevis.reduce((sum, ligne) => sum + ligne.montantHT, 0); const marge = (options?.margeEntreprise || 20) / 100; const tva = (options?.tva || 18) / 100; const totalAvecMarge = totalHT * (1 + marge); const totalTTC = totalAvecMarge * (1 + tva); return { lignesDevis, totalHT: totalAvecMarge, totalTTC, delaiExecution: options?.delaiExecution || 15 }; } }