// ChemiFactory MES - Productivity Cost Simulator Frontend Logic const API_INVENTORY = "/inventory"; let allInventories = []; document.addEventListener("DOMContentLoaded", () => { fetchInventoryDropdowns(); document.getElementById("simulator-form").addEventListener("submit", runSimulation); document.getElementById("adjustment-form").addEventListener("submit", runAdjustmentCalculator); document.getElementById("dryer-form").addEventListener("submit", runDryingCalculator); }); async function fetchInventoryDropdowns() { try { const res = await fetch(`${API_INVENTORY}/`); if (!res.ok) throw new Error("Failed to fetch inventory dropdowns"); allInventories = await res.json(); const yarns = allInventories.filter(i => i.material_type && (i.material_type.toLowerCase() === 'yarn' || i.material_type === '원사') && i.is_depleted !== 1); const bonds = allInventories.filter(i => i.material_type && (i.material_type.toLowerCase() === 'bond' || i.material_type === '본드') && i.is_depleted !== 1); const yarnSelect = document.getElementById("sim-yarn"); yarnSelect.innerHTML = ''; yarns.forEach(y => { yarnSelect.innerHTML += ``; }); const bondSelect = document.getElementById("sim-bond"); bondSelect.innerHTML = ''; bonds.forEach(b => { bondSelect.innerHTML += ``; }); } catch (err) { console.error(err); } } async function runSimulation(e) { e.preventDefault(); const yarnId = document.getElementById("sim-yarn").value; const qty = document.getElementById("sim-qty").value; if (!yarnId || !qty) { alert("원사 자재와 목표 수량을 올바르게 지정하십시오."); return; } const payload = { inputs: { yarn_inventory_id: parseInt(yarnId), bond_inventory_id: parseInt(document.getElementById("sim-bond").value) || null, yarn_diameter_micron: parseFloat(document.getElementById("sim-dia").value), yarn_k: parseInt(document.getElementById("sim-k").value), ports: parseInt(document.getElementById("sim-ports").value), cycle_time_hz: parseFloat(document.getElementById("sim-hz").value), cut_length_mm: parseFloat(document.getElementById("sim-cut").value), work_hours_day: parseInt(document.getElementById("sim-hours").value), work_days_month: 20.0, num_machines: parseFloat(document.getElementById("sim-machines").value), bond_percentage: parseFloat(document.getElementById("sim-bond-pct").value) || 3.0 }, targetProductionQuantity: parseFloat(qty) }; try { const res = await fetch(`${API_INVENTORY}/analysis/calculate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (!res.ok) { const errData = await res.json(); throw new Error(errData.detail || "시뮬레이션 연산 실패"); } const data = await res.json(); // Bind 결과 데이터 document.getElementById("res-days").innerText = `${data.required_days_target.toFixed(1)} 일`; document.getElementById("res-fee").innerText = `${Math.round(data.plan_processing_fee).toLocaleString()} 원`; document.getElementById("res-sales").innerText = `${Math.round(data.plan_estimated_sales).toLocaleString()} 원`; document.getElementById("res-margin").innerText = `${Math.round(data.plan_company_margin).toLocaleString()} 원`; document.getElementById("res-yarn-cost").innerText = `${Math.round(data.plan_yarn_cost).toLocaleString()} 원`; document.getElementById("res-bond-cost").innerText = `${Math.round(data.plan_bond_cost).toLocaleString()} 원`; } catch (err) { console.error(err); alert(`시뮬레이션 가동 중 오류 발생: ${err.message}`); } } function runAdjustmentCalculator(e) { e.preventDefault(); const resultBox = document.getElementById("adj-result-box"); const resultsContainer = document.getElementById("adj-results"); resultBox.style.display = "none"; resultsContainer.innerHTML = ""; const cRaw = parseFloat(document.getElementById("adj-raw-conc").value); const vCurr = parseFloat(document.getElementById("adj-curr-vol").value); const cCurr = parseFloat(document.getElementById("adj-curr-conc").value); const vTarget = parseFloat(document.getElementById("adj-tgt-vol").value); const cTarget = parseFloat(document.getElementById("adj-tgt-conc").value); if ([cRaw, vCurr, cCurr, vTarget, cTarget].some(val => isNaN(val))) { alert("모든 필드에 유효한 숫자를 입력해 주십시오."); return; } if (vTarget < vCurr) { alert("목표 부피는 현재 부피보다 작을 수 없습니다."); return; } if (cRaw <= 0 || cTarget < 0 || cCurr < 0 || cRaw > 100 || cTarget > 100 || cCurr > 100) { alert("농도 값은 0%에서 100% 사이여야 합니다."); return; } if (cTarget > cRaw) { alert("목표 농도는 원자재 본드 농도보다 높을 수 없습니다."); return; } const cRawDec = cRaw / 100; const cCurrDec = cCurr / 100; const cTargetDec = cTarget / 100; const currentBond = vCurr * cCurrDec; const targetBond = vTarget * cTargetDec; if (targetBond < currentBond - 0.001) { alert("계산 불가: 목표 본드량이 현재 본드량보다 적습니다."); return; } let rawMaterialToAdd = 0; if (cRawDec > 0) { rawMaterialToAdd = (targetBond - currentBond) / cRawDec; } if (rawMaterialToAdd < 0) rawMaterialToAdd = 0; const waterToAdd = vTarget - vCurr - rawMaterialToAdd; if (waterToAdd < -0.001) { if (cRaw - cTarget > 0) { const suggestedMinVolume = vCurr * (cRaw - cCurr) / (cRaw - cTarget); alert(`목표 부피(${vTarget.toFixed(1)}L)로는 목표 농도(${cTarget}%)를 맞출 수 없습니다.\n해당 농도를 맞추기 위한 최소 목표 부피는 ${suggestedMinVolume.toFixed(2)}L 입니다.`); } else { alert("계산 불가: 목표 농도가 원자재 농도와 같거나 높아 도달할 수 없습니다."); } return; } const finalPureBond = targetBond; const finalTotalWater = vTarget - finalPureBond; resultsContainer.innerHTML = `

• 추가할 물: ${waterToAdd.toFixed(2)} L

• 추가할 원자재: ${rawMaterialToAdd.toFixed(2)} L


• 최종 용액 총 부피: ${vTarget.toFixed(1)} L

- 순수 본드 성분: ${finalPureBond.toFixed(2)} L

- 포함된 총 물: ${finalTotalWater.toFixed(2)} L

`; resultBox.style.display = "block"; } function runDryingCalculator(e) { e.preventDefault(); const resultBox = document.getElementById("dry-result-box"); const resultsContainer = document.getElementById("dry-results"); resultBox.style.display = "none"; resultsContainer.innerHTML = ""; const speed = parseFloat(document.getElementById("dry-speed").value); const concentration = parseFloat(document.getElementById("dry-conc").value); const usage = parseFloat(document.getElementById("dry-usage").value); const loss = parseFloat(document.getElementById("dry-loss").value); if ([speed, concentration, usage, loss].some(val => isNaN(val))) { alert("모든 필드에 유효한 숫자를 입력해 주십시오."); return; } const usageMlPerSec = (usage * 1000) / 86400; // L/day -> ml/s const waterFraction = 1 - (concentration / 100); const waterRateGps = usageMlPerSec * waterFraction; const latentHeat = 2260; // J/g const powerW = waterRateGps * latentHeat; const recommendedPowerW = powerW * (1 + (loss / 100)); resultsContainer.innerHTML = `

• 이론상 최소 필요 전력: ${powerW.toFixed(2)} W

• 권장 히터 동작 전력 (Loss 포함): ${recommendedPowerW.toFixed(2)} W

* 물의 잠열 ${latentHeat} J/g 및 입력 건조 손실률 ${loss}%를 대입한 추산치입니다.

`; resultBox.style.display = "block"; }