import logging from dataclasses import dataclass from app.services.file_parser import FileInfo logger = logging.getLogger("app.services.price_engine") TIME_RATE_PER_HOUR = 200.0 # руб/час SETUP_TIME_MIN = 15.0 # минуты TRAVEL_TIME_PER_LAYER_MIN = 0.3 POST_PROCESSING_COSTS = { "sanding": 300.0, "painting": 500.0, "threading": 200.0, "acetone_smoothing": 400.0, } QUANTITY_DISCOUNTS = [ (1, 0), (2, 5), (6, 10), (21, 15), (101, 20), ] @dataclass class PriceResult: weight_grams: float material_cost_rub: float print_time_hours: float time_cost_rub: float post_processing_cost_rub: float subtotal_rub: float quantity: int quantity_discount_percent: int total_rub: float estimated_days: int def get_quantity_discount(quantity: int) -> int: discount = 0 for min_qty, disc in QUANTITY_DISCOUNTS: if quantity >= min_qty: discount = disc logger.debug("Quantity discount for %d pcs: %d%%", quantity, discount) return discount def estimate_print_time(file_info: FileInfo, layer_height_mm: float, flow_rate_mm3_s: float) -> float: """Estimate print time in hours.""" z_height = file_info.bounding_box_mm.get("z", 10.0) layers = max(z_height / layer_height_mm, 1) volume_mm3 = file_info.volume_cm3 * 1000.0 volume_per_layer = volume_mm3 / layers time_per_layer_min = volume_per_layer / flow_rate_mm3_s / 60.0 total_min = layers * (time_per_layer_min + TRAVEL_TIME_PER_LAYER_MIN) + SETUP_TIME_MIN hours = round(total_min / 60.0, 1) logger.debug("Print time estimate: z=%.1fmm, layers=%.0f, vol_per_layer=%.1fmm3, " "time_per_layer=%.2fmin, total=%.1fmin (%.1fh)", z_height, layers, volume_per_layer, time_per_layer_min, total_min, hours) return hours def calculate_price( file_info: FileInfo, density_g_cm3: float, price_per_gram: float, flow_rate_mm3_s: float, infill_percent: int = 30, layer_height_mm: float = 0.2, quantity: int = 1, post_processing: list[str] | None = None, ) -> PriceResult: post_processing = post_processing or [] logger.info("=== Price calculation start ===") logger.info("Input: volume=%.2f cm3, density=%.2f g/cm3, price_per_gram=%.1f RUB", file_info.volume_cm3, density_g_cm3, price_per_gram) logger.info("Params: infill=%d%%, layer=%.2fmm, qty=%d, post_processing=%s", infill_percent, layer_height_mm, quantity, post_processing) effective_volume = file_info.volume_cm3 * (infill_percent / 100.0) * 0.7 + file_info.volume_cm3 * 0.3 logger.debug("Effective volume: %.2f cm3 (infill-scaled: %.2f + walls: %.2f)", effective_volume, file_info.volume_cm3 * (infill_percent / 100.0) * 0.7, file_info.volume_cm3 * 0.3) weight_g = round(effective_volume * density_g_cm3, 1) material_cost = round(weight_g * price_per_gram, 2) logger.debug("Weight: %.1f g, material cost: %.2f RUB", weight_g, material_cost) print_time_h = estimate_print_time(file_info, layer_height_mm, flow_rate_mm3_s) time_cost = round(print_time_h * TIME_RATE_PER_HOUR, 2) logger.debug("Print time: %.1f h, time cost: %.2f RUB (rate: %.0f RUB/h)", print_time_h, time_cost, TIME_RATE_PER_HOUR) pp_cost = 0.0 for pp in post_processing: cost = POST_PROCESSING_COSTS.get(pp, 0) logger.debug("Post-processing '%s': %.0f RUB", pp, cost) pp_cost += cost pp_cost = round(pp_cost, 2) logger.debug("Total post-processing cost: %.2f RUB", pp_cost) subtotal = round(material_cost + time_cost + pp_cost, 2) logger.debug("Subtotal (1 pc): %.2f RUB = material(%.2f) + time(%.2f) + pp(%.2f)", subtotal, material_cost, time_cost, pp_cost) discount_pct = get_quantity_discount(quantity) total = round(subtotal * quantity * (1 - discount_pct / 100.0), 2) logger.info("Total: %.2f RUB (qty=%d, discount=%d%%, subtotal_per_unit=%.2f)", total, quantity, discount_pct, subtotal) if print_time_h <= 2: estimated_days = 2 elif print_time_h <= 8: estimated_days = 3 else: estimated_days = 5 if quantity > 10: estimated_days += 2 if quantity > 50: estimated_days += 3 logger.info("Estimated days: %d", estimated_days) logger.info("=== Price calculation complete ===") return PriceResult( weight_grams=weight_g, material_cost_rub=material_cost, print_time_hours=print_time_h, time_cost_rub=time_cost, post_processing_cost_rub=pp_cost, subtotal_rub=subtotal, quantity=quantity, quantity_discount_percent=discount_pct, total_rub=total, estimated_days=estimated_days, )