484 lines
14 KiB
TypeScript
Executable File
484 lines
14 KiB
TypeScript
Executable File
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}m² × ${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
|
||
};
|
||
}
|
||
} |