Files
btpxpress-frontend/services/calculsTechniquesService.ts
2025-10-01 01:39:07 +00:00

484 lines
14 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 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<number, number>; // 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<ResultatCalculBriques> {
const response = await ApiService.post<ResultatCalculBriques>(
`${this.BASE_PATH}/briques-mur`,
params
);
return response;
}
/**
* Calcul mortier pour maçonnerie traditionnelle
*/
static async calculerMortierMaconnerie(params: ParametresCalculMortier): Promise<ResultatCalculMortier> {
const response = await ApiService.post<ResultatCalculMortier>(
`${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}× ${briquesParM2Moyen} briques/m² × ${facteurPerteMoyen} (pertes)`
};
}
// =================== CALCULS BÉTON ARMÉ ===================
/**
* Calcul béton armé avec adaptation climatique africaine
*/
static async calculerBetonArme(params: ParametresCalculBetonArme): Promise<ResultatCalculBetonArme> {
const response = await ApiService.post<ResultatCalculBetonArme>(
`${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<string, DosageBetonInfo>;
notes: string[];
}> {
const response = await ApiService.get<{
dosages: Record<string, DosageBetonInfo>;
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<string, Record<string, number>> = {
'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
};
}
}