import math import logging from .db_manager import get_inventory_details_from_db from setting.db_manager import get_all_settings def _safe_float_conversion(value, default_value=None): try: if value is not None and value != '': return float(value) except (ValueError, TypeError): pass return default_value def calculate_cost_analysis(inputs: dict, target_production_quantity: float = None) -> dict: """ 원가 분석 계산 엔진 (순수 연산 비즈니스 로직) """ yarn_inventory_id = inputs.get("yarn_inventory_id") bond_inventory_id = inputs.get("bond_inventory_id") if not yarn_inventory_id: raise ValueError("분석할 원사 재고가 선택되지 않았습니다.") yarn_data_wrapper = get_inventory_details_from_db(yarn_inventory_id) if not yarn_data_wrapper or "inventory" not in yarn_data_wrapper: raise ValueError("선택된 원사의 물성 또는 단가 정보를 찾을 수 없습니다.") yarn_data = yarn_data_wrapper["inventory"] yarn_density_g_cm3 = _safe_float_conversion(yarn_data.get("density"), 1.0) yarn_unit_cost = _safe_float_conversion(yarn_data.get("unit_cost"), 0.0) bond_unit_cost = 0.0 if bond_inventory_id and bond_inventory_id != 'null': bond_data_wrapper = get_inventory_details_from_db(bond_inventory_id) if bond_data_wrapper and "inventory" in bond_data_wrapper: bond_data = bond_data_wrapper["inventory"] bond_unit_cost = _safe_float_conversion(bond_data.get("unit_cost"), 0.0) yarn_diameter_micron = _safe_float_conversion(inputs.get("yarn_diameter_micron"), 7) yarn_k = _safe_float_conversion(inputs.get("yarn_k"), 12) * 1000 ports = _safe_float_conversion(inputs.get("ports"), 40) cycle_time_hz = _safe_float_conversion(inputs.get("cycle_time_hz"), 8) cut_length_mm = _safe_float_conversion(inputs.get("cut_length_mm"), 6) work_hours_day = _safe_float_conversion(inputs.get("work_hours_day"), 16) work_days_month = _safe_float_conversion(inputs.get("work_days_month"), 20) num_machines = _safe_float_conversion(inputs.get("num_machines"), 2) bond_percentage = _safe_float_conversion(inputs.get("bond_percentage"), 3.0) / 100.0 yarn_radius_cm = (yarn_diameter_micron / 2) * 1e-4 yarn_cross_section_cm2 = math.pi * (yarn_radius_cm ** 2) pure_yarn_weight_kg_per_m = (yarn_cross_section_cm2 * 100 * yarn_density_g_cm3) / 1000 cut_length_m = cut_length_mm / 1000 production_length_m_per_hour_per_mc = cycle_time_hz * 3600 * cut_length_m pure_yarn_kg_per_hour_per_mc = production_length_m_per_hour_per_mc * pure_yarn_weight_kg_per_m * ports * yarn_k if 0 < bond_percentage < 1.0: total_kg_per_hour_per_mc = pure_yarn_kg_per_hour_per_mc / (1.0 - bond_percentage) else: total_kg_per_hour_per_mc = pure_yarn_kg_per_hour_per_mc if total_kg_per_hour_per_mc <= 0: raise ValueError("시간당 생산량이 0 이하여서 계산할 수 없습니다.") settings = get_all_settings() settings_dict = {f"{s['setting_group']}_{s['setting_key']}": s['setting_value'] for s in settings} def get_s(key, default): return _safe_float_conversion(settings_dict.get(key, default), default) avg_power_per_machine_kw = get_s("CostAnalysis_AvgPowerPerMachine", 5000) / 1000 electricity_price_kwh = get_s("CostAnalysis_ElectricityUnitPricePerKWH", 94.4) hourly_power_cost = avg_power_per_machine_kw * electricity_price_kwh max_machines_per_person = get_s("CostAnalysis_MaxMachinesPerPerson", 10) num_people_needed = math.ceil(num_machines / max_machines_per_person) monthly_labor_cost_person = get_s("CostAnalysis_MonthlyLaborCostPerPerson", 6000000) meal_cost_day = get_s("CostAnalysis_MealCostPerDay", 15000) hourly_labor_cost = (num_people_needed * monthly_labor_cost_person + num_people_needed * meal_cost_day * work_days_month) / (work_days_month * work_hours_day * num_machines) packaging_cost_kg = get_s("CostAnalysis_PackagingCostPerKg", 500) packaging_weight_kg = get_s("CostAnalysis_PackagingBaseWeightKg", 25) hourly_packaging_cost = (total_kg_per_hour_per_mc / packaging_weight_kg) * packaging_cost_kg if packaging_weight_kg > 0 else 0 monthly_rent = get_s("CostAnalysis_MonthlyRentCost", 0) monthly_admin = get_s("CostAnalysis_MonthlyGeneralAdminCost", 50000) monthly_delivery = get_s("CostAnalysis_MonthlyDeliveryCost", 300000) hourly_overhead_cost = (monthly_rent + monthly_admin + monthly_delivery) / (work_days_month * work_hours_day * num_machines) mass_mc_cost = get_s("Investment_MassProductionEquipmentCost", 10000000) dep_mass_mc = get_s("Investment_DepreciationPeriodMassProduction", 3) * 12 office_cost = get_s("Investment_OfficeSuppliesCost", 5000000) dep_office = get_s("Investment_DepreciationPeriodOfficeSupplies", 3) * 12 proto_cost = get_s("Investment_ProtoEquipmentCost", 5000000) monthly_depreciation = (mass_mc_cost / dep_mass_mc if dep_mass_mc > 0 else 0) + \ (office_cost / dep_office if dep_office > 0 else 0) + \ (proto_cost / dep_office if dep_office > 0 else 0) hourly_depreciation = monthly_depreciation / (work_days_month * work_hours_day) hourly_net_cost_per_machine = hourly_power_cost + hourly_labor_cost + hourly_packaging_cost + hourly_overhead_cost + hourly_depreciation company_margin_rate = get_s("CostAnalysis_CompanyMarginRate", 30) / 100.0 standard_processing_cost_kg = get_s("CostAnalysis_StandardProcessingCost", 0) pure_processing_cost_kg = hourly_net_cost_per_machine / total_kg_per_hour_per_mc if total_kg_per_hour_per_mc > 0 else 0 calculated_profit_margin_kg = 0 if pure_processing_cost_kg > 0: if pure_processing_cost_kg * (1 + company_margin_rate) < standard_processing_cost_kg: calculated_profit_margin_kg = standard_processing_cost_kg - pure_processing_cost_kg else: calculated_profit_margin_kg = pure_processing_cost_kg * company_margin_rate hourly_profit_per_machine = calculated_profit_margin_kg * total_kg_per_hour_per_mc analysis_result = {} total_kg_hour_all_mc = total_kg_per_hour_per_mc * num_machines total_kg_day_all_mc = total_kg_hour_all_mc * work_hours_day total_kg_month = total_kg_day_all_mc * work_days_month monthly_power_cost = hourly_power_cost * work_hours_day * work_days_month * num_machines monthly_labor_cost_total = hourly_labor_cost * work_hours_day * work_days_month * num_machines monthly_packaging_cost = hourly_packaging_cost * work_hours_day * work_days_month * num_machines monthly_overhead_cost = hourly_overhead_cost * work_hours_day * work_days_month * num_machines monthly_depreciation_cost_total = hourly_depreciation * work_hours_day * work_days_month * num_machines monthly_total_expenses = monthly_power_cost + monthly_labor_cost_total + monthly_packaging_cost + monthly_overhead_cost + monthly_depreciation_cost_total pure_yarn_kg_per_hour_all_mc = pure_yarn_kg_per_hour_per_mc * num_machines required_yarn_kg_month = (pure_yarn_kg_per_hour_all_mc / total_kg_hour_all_mc) * total_kg_month if total_kg_hour_all_mc > 0 else 0 bond_kg_per_hour_all_mc = (total_kg_hour_all_mc - pure_yarn_kg_per_hour_all_mc) required_bond_kg_month = (bond_kg_per_hour_all_mc / total_kg_hour_all_mc) * total_kg_month if total_kg_hour_all_mc > 0 else 0 current_yarn_stock = _safe_float_conversion(yarn_data.get("stock"), 0.0) purchase_order_yarn_kg = max(0, required_yarn_kg_month - current_yarn_stock) current_bond_stock = 0.0 purchase_order_bond_kg = 0.0 if bond_inventory_id and bond_inventory_id != 'null': bond_data_wrapper = get_inventory_details_from_db(bond_inventory_id) if bond_data_wrapper and "inventory" in bond_data_wrapper: current_bond_stock = _safe_float_conversion(bond_data_wrapper["inventory"].get("stock"), 0.0) purchase_order_bond_kg = max(0, required_bond_kg_month - current_bond_stock) total_yarn_purchase_cost = required_yarn_kg_month * yarn_unit_cost total_bond_purchase_cost = required_bond_kg_month * bond_unit_cost total_raw_material_purchase_cost = total_yarn_purchase_cost + total_bond_purchase_cost analysis_result.update({ "production_kg_per_hour": total_kg_hour_all_mc, "production_kg_per_day": total_kg_day_all_mc, "production_ton_per_month": total_kg_month / 1000, "total_product_kg_per_month": total_kg_month, "pure_processing_cost_per_kg": pure_processing_cost_kg, "calculated_profit_margin_per_kg": calculated_profit_margin_kg, "processing_cost_per_kg_total": pure_processing_cost_kg + calculated_profit_margin_kg, "current_yarn_stock": current_yarn_stock, "required_yarn_kg": required_yarn_kg_month, "purchase_order_kg": purchase_order_yarn_kg, "yarn_unit_cost": yarn_unit_cost, "current_bond_stock": current_bond_stock, "required_bond_kg": required_bond_kg_month, "purchase_order_bond_kg": purchase_order_bond_kg, "bond_unit_cost": bond_unit_cost, "total_monthly_expenses": monthly_total_expenses, "monthly_power_cost": monthly_power_cost, "total_monthly_kwh": (avg_power_per_machine_kw * num_machines * work_hours_day * work_days_month), "monthly_labor_cost": monthly_labor_cost_total, "monthly_meal_cost": (num_people_needed * meal_cost_day * work_days_month), "monthly_packaging_cost": monthly_packaging_cost, "monthly_depreciation_cost": monthly_depreciation_cost_total, "monthly_rent_cost": monthly_rent, "monthly_general_admin_cost_setting": monthly_admin, "monthly_delivery_cost": monthly_delivery, "total_revenue": total_raw_material_purchase_cost + ((pure_processing_cost_kg + calculated_profit_margin_kg) * total_kg_month) }) # 2. 목표 생산량 분석 (요청 시 추가 수행) if target_production_quantity and target_production_quantity > 0: total_work_hours_target = target_production_quantity / (total_kg_hour_all_mc if total_kg_hour_all_mc > 0 else 1) work_days_target = total_work_hours_target / work_hours_day required_yarn_kg_target = (pure_yarn_kg_per_hour_all_mc / total_kg_hour_all_mc) * target_production_quantity if total_kg_hour_all_mc > 0 else 0 purchase_order_yarn_kg_target = max(0, required_yarn_kg_target - current_yarn_stock) required_bond_kg_target = (bond_kg_per_hour_all_mc / total_kg_hour_all_mc) * target_production_quantity if total_kg_hour_all_mc > 0 else 0 purchase_order_bond_kg_target = max(0, required_bond_kg_target - current_bond_stock) plan_yarn_cost = required_yarn_kg_target * yarn_unit_cost plan_bond_cost = required_bond_kg_target * bond_unit_cost plan_total_material_cost = plan_yarn_cost + plan_bond_cost plan_net_processing_cost = hourly_net_cost_per_machine * num_machines * total_work_hours_target plan_company_margin = hourly_profit_per_machine * num_machines * total_work_hours_target plan_processing_fee = plan_net_processing_cost + plan_company_margin plan_estimated_sales = plan_processing_fee + plan_total_material_cost analysis_result.update({ "required_days_target": work_days_target, "current_yarn_stock_target": current_yarn_stock, "required_yarn_kg_target": required_yarn_kg_target, "purchase_order_kg_target": purchase_order_yarn_kg_target, "current_bond_stock_target": current_bond_stock, "required_bond_kg_target": required_bond_kg_target, "purchase_order_bond_kg_target": purchase_order_bond_kg_target, "plan_yarn_cost": plan_yarn_cost, "plan_bond_cost": plan_bond_cost, "plan_total_material_cost": plan_total_material_cost, "plan_net_processing_cost": plan_net_processing_cost, "plan_company_margin": plan_company_margin, "plan_processing_fee": plan_processing_fee, "plan_estimated_sales": plan_estimated_sales }) analysis_result.update({ "hourly_net_cost_per_machine": hourly_net_cost_per_machine, "hourly_profit_per_machine": hourly_profit_per_machine }) return analysis_result