diff --git a/app/src/components/ui/analysis/ProductionCapacity.tsx b/app/src/components/ui/analysis/ProductionCapacity.tsx index fd6150d..0bc8b91 100644 --- a/app/src/components/ui/analysis/ProductionCapacity.tsx +++ b/app/src/components/ui/analysis/ProductionCapacity.tsx @@ -9,7 +9,7 @@ import { } from "chart.js"; import { PowerIcon, ProductionCapacityIcon } from "../../icons/analysis"; import SkeletonUI from "../../templates/SkeletonUI"; -import { useMachineUptime, useProductionCapacityData } from "../../../store/builder/store"; +import { useInputValues, useMachineUptime, useProductionCapacityData } from "../../../store/builder/store"; ChartJS.register(LineElement, CategoryScale, LinearScale, PointElement); @@ -73,6 +73,7 @@ const ThroughputSummary: React.FC = () => { assetUsage: assetUsage, }; + const { inputValues } = useInputValues(); // Chart data configuration const chartData = { labels: throughputData.labels, @@ -91,10 +92,10 @@ const ThroughputSummary: React.FC = () => { useEffect(() => { if (productionCapacityData >= 0) { - setIsLoading(false); + setIsLoading(false); console.log("productionCapacityData: ", productionCapacityData); } else { - setIsLoading(true); + setIsLoading(true); } }, [productionCapacityData]); @@ -123,7 +124,7 @@ const ThroughputSummary: React.FC = () => {
Asset usage
-
{throughputData.assetUsage}%
+
{parseFloat(inputValues["Yield rate"])}%
diff --git a/app/src/components/ui/analysis/ROISummary.tsx b/app/src/components/ui/analysis/ROISummary.tsx index deaf87a..287d631 100644 --- a/app/src/components/ui/analysis/ROISummary.tsx +++ b/app/src/components/ui/analysis/ROISummary.tsx @@ -86,7 +86,6 @@ const ROISummary = ({ useEffect(() => { if (roiSummary && typeof roiSummary === "object") { - console.log('roiSummary: ', roiSummary); setIsLoading(false); // Data loaded } else { setIsLoading(true); // Show skeleton while loading @@ -147,7 +146,7 @@ const ROISummary = ({
0 ? "profit" : "loss"}`} + className={`metric-item net-profit ${roiSummary.netProfit > 0 ? "profit" : "loss"}`} >
diff --git a/app/src/components/ui/analysis/ThroughputSummary.tsx b/app/src/components/ui/analysis/ThroughputSummary.tsx index e87027b..bf570f9 100644 --- a/app/src/components/ui/analysis/ThroughputSummary.tsx +++ b/app/src/components/ui/analysis/ThroughputSummary.tsx @@ -17,26 +17,24 @@ const ProductionCapacity = ({ const { machineActiveTime } = useMachineUptime(); const { materialCycleTime } = useMaterialCycle(); const { throughputData } = useThroughPutData() - const { productionCapacityData } = useProductionCapacityData() - - const progressPercent = machineActiveTime; - - - const totalBars = 6; - const barsToFill = Math.floor((progressPercent / 100) * totalBars); - const partialFillPercent = + const { productionCapacityData } = useProductionCapacityData() + + const progressPercent = machineActiveTime; + + + const totalBars = 6; + const barsToFill = Math.floor((progressPercent / 100) * totalBars); + const partialFillPercent = ((progressPercent / 100) * totalBars - barsToFill) * 100; - - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - if (throughputData >= 0) { - console.log('machineActiveTime: ', machineActiveTime); - console.log('materialCycleTime: ', materialCycleTime); - console.log('throughputData: ', throughputData); - console.log('productionCapacityData: ', productionCapacityData); + const [isLoading, setIsLoading] = useState(false); + useEffect(() => { + if (throughputData >= 0) { + // console.log('machineActiveTime: ', machineActiveTime); + // console.log('materialCycleTime: ', materialCycleTime); + // console.log('throughputData: ', throughputData); + // console.log('productionCapacityData: ', productionCapacityData); setIsLoading(true); } diff --git a/app/src/modules/simulation/analysis/ROI/roiData.tsx b/app/src/modules/simulation/analysis/ROI/roiData.tsx index fd77098..f1ddda5 100644 --- a/app/src/modules/simulation/analysis/ROI/roiData.tsx +++ b/app/src/modules/simulation/analysis/ROI/roiData.tsx @@ -1,15 +1,28 @@ import React, { useEffect, useState } from 'react' import { useInputValues, useProductionCapacityData, useROISummaryData } from '../../../../store/builder/store'; import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; export default function ROIData() { const { inputValues } = useInputValues(); const { productionCapacityData } = useProductionCapacityData() const { selectedProduct } = useSelectedProduct(); - - + const { isPlaying } = usePlayButtonStore(); const { setRoiSummaryData } = useROISummaryData(); useEffect(() => { + if (!isPlaying) { + setRoiSummaryData({ + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }) + return; + } + if (inputValues === undefined) return; const electricityCost = parseFloat(inputValues["Electricity cost"]); @@ -29,69 +42,77 @@ export default function ROIData() { !isNaN(materialCost) && !isNaN(productionPeriod) && !isNaN(salvageValue) && !isNaN(sellingPrice) && !isNaN(shiftLength) && !isNaN(shiftsPerDay) && !isNaN(workingDaysPerYear) && productionCapacityData > 0) { - console.log('sellingPrice: ', sellingPrice); - console.log('salvageValue: ', salvageValue); - console.log('productionPeriod: ', productionPeriod); - console.log('materialCost: ', materialCost); - console.log('maintenanceCost: ', maintenanceCost); - console.log('laborCost: ', laborCost); - console.log('fixedCost: ', fixedCost); - console.log('electricityCost: ', electricityCost); - // Revenue - const RevenueForYear = productionCapacityData * sellingPrice; - console.log('RevenueForYear: ', RevenueForYear); - //Costs + const totalHoursPerYear = shiftLength * shiftsPerDay * workingDaysPerYear; - let materialCount = 1200; + // Total good units produced per year + const annualProductionUnits = productionCapacityData * totalHoursPerYear; - //Material Cost + // Revenue for a year + const annualRevenue = annualProductionUnits * sellingPrice; - let MaterialCost = productionCapacityData * materialCost - console.log('MaterialCost: ', MaterialCost); - let LaborCost = laborCost * shiftLength * shiftsPerDay * workingDaysPerYear; - console.log('LaborCost: ', LaborCost); - let EnergyCost = electricityCost * shiftLength * shiftsPerDay * workingDaysPerYear; - console.log('EnergyCost: ', EnergyCost); - let MaintenceCost = maintenanceCost + fixedCost; - console.log('MaintenceCost: ', MaintenceCost); + // Costs + const totalMaterialCost = annualProductionUnits * materialCost; + const totalLaborCost = laborCost * totalHoursPerYear; + const totalEnergyCost = electricityCost * totalHoursPerYear; + const totalMaintenanceCost = maintenanceCost + fixedCost; - //Total Anuual Cost - let TotalAnnualCost = (MaterialCost * materialCount) + LaborCost + EnergyCost + MaintenceCost; - console.log('TotalAnnualCost: ', TotalAnnualCost); + const totalAnnualCost = totalMaterialCost + totalLaborCost + totalEnergyCost + totalMaintenanceCost; + // Annual Profit + const annualProfit = annualRevenue - totalAnnualCost; + console.log('annualProfit: ', annualProfit); - //Profit for Year - let ProfitforYear = RevenueForYear - TotalAnnualCost; - console.log('ProfitforYear: ', ProfitforYear); - - //Net Profit - let NetProfit = ProfitforYear * productionPeriod; - console.log('NetProfit: ', NetProfit); - - - //Final ROI - const ROIData = ((NetProfit + salvageValue - initialInvestment) / TotalAnnualCost) * 100; - console.log('ROIData: ', ROIData); + // Net Profit over production period + const netProfit = annualProfit * productionPeriod; + // ROI + const roiPercentage = ((netProfit + salvageValue - initialInvestment) / initialInvestment) * 100; // Payback Period - const paybackPeriod = initialInvestment / ProfitforYear; + const paybackPeriod = initialInvestment / (annualProfit || 1); // Avoid division by 0 console.log('paybackPeriod: ', paybackPeriod); + // console.log("--- ROI Breakdown ---"); + // console.log("Annual Production Units:", annualProductionUnits.toFixed(2)); + // console.log("Annual Revenue:", annualRevenue.toFixed(2)); + // console.log("Total Annual Cost:", totalAnnualCost.toFixed(2)); + // console.log("Annual Profit:", annualProfit.toFixed(2)); + // console.log("Net Profit:", netProfit.toFixed(2)); + // console.log("ROI %:", roiPercentage.toFixed(2)); + // console.log("Payback Period (years):", paybackPeriod.toFixed(2)); setRoiSummaryData({ productName: selectedProduct.productName, - roiPercentage: parseFloat((ROIData / 100).toFixed(2)), + roiPercentage: parseFloat((roiPercentage / 100).toFixed(2)), // normalized to 0.x format paybackPeriod: parseFloat(paybackPeriod.toFixed(2)), - totalCost: TotalAnnualCost, - revenueGenerated: RevenueForYear, - netProfit: NetProfit > 0 ? NetProfit : 0, - netLoss: NetProfit < 0 ? -NetProfit : 0 + totalCost: parseFloat(totalAnnualCost.toFixed(2)), + revenueGenerated: parseFloat(annualRevenue.toFixed(2)), + netProfit: netProfit > 0 ? parseFloat(netProfit.toFixed(2)) : 0, + netLoss: netProfit < 0 ? -netProfit : 0 }); + const productCount = 1000; + + // Cost per unit (based on full annual cost) + const costPerUnit = totalAnnualCost / annualProductionUnits; + + const costForTargetUnits = productCount * costPerUnit; + const revenueForTargetUnits = productCount * sellingPrice; + const profitForTargetUnits = revenueForTargetUnits - costForTargetUnits; + + const netProfitForTarget = profitForTargetUnits > 0 ? profitForTargetUnits : 0; + const netLossForTarget = profitForTargetUnits < 0 ? -profitForTargetUnits : 0; + + // console.log("--- Fixed Product Count (" + productCount + ") ---"); + // console.log("Cost per Unit:", costPerUnit.toFixed(2)); + // console.log("Total Cost for " + productCount + " Units:", costForTargetUnits.toFixed(2)); + // console.log("Revenue for " + productCount + " Units:", revenueForTargetUnits.toFixed(2)); + // console.log("Profit:", netProfitForTarget.toFixed(2)); + // console.log("Loss:", netLossForTarget.toFixed(2)); + } }, [inputValues, productionCapacityData]); diff --git a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx index a77e221..5c376a7 100644 --- a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx +++ b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx @@ -1,37 +1,44 @@ import React, { useEffect } from 'react' import { useInputValues, useProductionCapacityData, useThroughPutData } from '../../../../store/builder/store' +import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; export default function ProductionCapacityData() { const { throughputData } = useThroughPutData() const { productionCapacityData, setProductionCapacityData } = useProductionCapacityData() const { inputValues } = useInputValues(); + const { isPlaying } = usePlayButtonStore(); useEffect(() => { - if (inputValues === undefined || throughputData === undefined) return; + if (!isPlaying) { + setProductionCapacityData(0); + return; + } + if (!inputValues || throughputData === undefined) return; const shiftLength = parseFloat(inputValues["Shift length"]); - // console.log('shiftLength: ', shiftLength); const shiftsPerDay = parseFloat(inputValues["Shifts / day"]); - // console.log('shiftsPerDay: ', shiftsPerDay); const workingDaysPerYear = parseFloat(inputValues["Working days / year"]); - // console.log('workingDaysPerYear: ', workingDaysPerYear); const yieldRate = parseFloat(inputValues["Yield rate"]); - // console.log('yieldRate: ', yieldRate); - - if (!isNaN(shiftLength) && !isNaN(shiftsPerDay) && !isNaN(workingDaysPerYear) && !isNaN(yieldRate) && throughputData >= 0) { - //Daily Output + if (!isNaN(shiftLength) && !isNaN(shiftsPerDay) && !isNaN(workingDaysPerYear) && + !isNaN(yieldRate) && throughputData >= 0) { + // Total units produced per day before yield const dailyProduction = throughputData * shiftLength * shiftsPerDay; - console.log("DailyProduction: ", dailyProduction.toFixed(2)); - // Good units (after Yield) - const afterYield = dailyProduction * (yieldRate / 100); - console.log('afterYield: ', afterYield.toFixed(2)); - //Annual Output - const annualProduction = afterYield * workingDaysPerYear; - console.log('annualProduction: ', Number(annualProduction.toFixed(2))); - //Production per Hour + + + // Units after applying yield rate + const goodUnitsPerDay = dailyProduction * (yieldRate / 100); + + + // Annual output + const annualProduction = goodUnitsPerDay * workingDaysPerYear; + + + // Final production capacity per hour (after yield) const productionPerHour = throughputData * (yieldRate / 100); - console.log('productionPerHour: ', productionPerHour); + + + // Set the final capacity (units/hour) setProductionCapacityData(Number(productionPerHour.toFixed(2))); } }, [throughputData, inputValues]); diff --git a/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx b/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx index 401d730..340e4e0 100644 --- a/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx +++ b/app/src/modules/simulation/analysis/throughPut/throughPutData.tsx @@ -10,101 +10,107 @@ import { useConveyorStore } from '../../../../store/simulation/useConveyorStore' import { useStorageUnitStore } from '../../../../store/simulation/useStorageUnitStore'; import { useMaterialStore } from '../../../../store/simulation/useMaterialStore'; import { usePauseButtonStore, usePlayButtonStore } from '../../../../store/usePlayButtonStore'; +import { is, set } from 'immer/dist/internal'; export default function ThroughPutData() { const { selectedProduct } = useSelectedProduct(); const { products, getProductById } = useProductStore(); - const { armBots, incrementActiveTime, incrementIdleTime } = useArmBotStore(); + const { armBots } = useArmBotStore(); const { vehicles } = useVehicleStore(); const { machines } = useMachineStore(); const { conveyors } = useConveyorStore(); const { storageUnits } = useStorageUnitStore(); const { materialHistory } = useMaterialStore(); - const { machineCount, setMachineCount } = useMachineCount(); const { machineActiveTime, setMachineActiveTime } = useMachineUptime(); - const { materialCycleTime, setMaterialCycleTime } = useMaterialCycle(); - const { processBar, setProcessBar } = useProcessBar(); - - // const [totalActiveTime, setTotalActiveTime] = useState(0); - const { setThroughputData } = useThroughPutData() // <=== ADD THIS + const { setProcessBar } = useProcessBar(); + const { setThroughputData } = useThroughPutData() const { isPlaying } = usePlayButtonStore(); // Setting machine count + let totalItems = 0; + let totalActiveTime = 0; useEffect(() => { - if (materialCycleTime <= 0) return - let process: any = []; - const fetchProductSequenceData = async () => { - const productData = getProductById(selectedProduct.productId); - if (productData) { - const productSequenceData = await determineExecutionMachineSequences([productData]) - if (productSequenceData?.length > 0) { - let totalItems = 0; - let totalActiveTime = 0; - productSequenceData.forEach((sequence) => { - sequence.forEach((item) => { + if (!isPlaying) { + totalActiveTime = 0; + totalItems = 0; + setMachineCount(0); + setMachineActiveTime(0); + setMaterialCycleTime(0); + setProcessBar([]); + setThroughputData(0); + return; + } else { + let process: any = []; + const fetchProductSequenceData = async () => { + const productData = getProductById(selectedProduct.productId); + if (productData) { + const productSequenceData = await determineExecutionMachineSequences([productData]) + if (productSequenceData?.length > 0) { + productSequenceData.forEach((sequence) => { + sequence.forEach((item) => { + if (item.type === "roboticArm") { + armBots.filter(arm => arm.modelUuid === item.modelUuid) + .forEach(arm => { + if (arm.activeTime >= 0) { + process.push({ modelid: arm.modelUuid, modelName: arm.modelName, activeTime: arm?.activeTime }) + totalActiveTime += arm.activeTime; - if (item.type === "roboticArm") { - armBots.filter(arm => arm.modelUuid === item.modelUuid) - .forEach(arm => { - if (arm.activeTime >= 0) { - process.push({ modelid: arm.modelUuid, modelName: arm.modelName, activeTime: arm?.activeTime }) - totalActiveTime += arm.activeTime; - } - }); - } else if (item.type === "vehicle") { - vehicles.filter(vehicle => vehicle.modelUuid === item.modelUuid) - .forEach(vehicle => { - if (vehicle.activeTime >= 0) { - process.push({ modelid: vehicle.modelUuid, modelName: vehicle.modelName, activeTime: vehicle?.activeTime }) + } + }); + } else if (item.type === "vehicle") { + vehicles.filter(vehicle => vehicle.modelUuid === item.modelUuid) + .forEach(vehicle => { + if (vehicle.activeTime >= 0) { + process.push({ modelid: vehicle.modelUuid, modelName: vehicle.modelName, activeTime: vehicle?.activeTime }) - totalActiveTime += vehicle.activeTime; - } - }); - } else if (item.type === "machine") { - machines.filter(machine => machine.modelUuid === item.modelUuid) - .forEach(machine => { - if (machine.activeTime >= 0) { - process.push({ modelid: machine.modelUuid, modelName: machine.modelName, activeTime: machine?.activeTime }) - totalActiveTime += machine.activeTime; - } - }); - } else if (item.type === "transfer") { - conveyors.filter(conveyor => conveyor.modelUuid === item.modelUuid) - .forEach(conveyor => { - if (conveyor.activeTime >= 0) { - totalActiveTime += conveyor.activeTime; - } - }); - } else if (item.type === "storageUnit") { - storageUnits.filter(storage => storage.modelUuid === item.modelUuid) - .forEach(storage => { - if (storage.activeTime >= 0) { - totalActiveTime += storage.activeTime; + totalActiveTime += vehicle.activeTime; + } + }); + } else if (item.type === "machine") { + machines.filter(machine => machine.modelUuid === item.modelUuid) + .forEach(machine => { + if (machine.activeTime >= 0) { + process.push({ modelid: machine.modelUuid, modelName: machine.modelName, activeTime: machine?.activeTime }) + totalActiveTime += machine.activeTime; + } + }); + } else if (item.type === "transfer") { + conveyors.filter(conveyor => conveyor.modelUuid === item.modelUuid) + .forEach(conveyor => { + if (conveyor.activeTime >= 0) { + totalActiveTime += conveyor.activeTime; + } + }); + } else if (item.type === "storageUnit") { + storageUnits.filter(storage => storage.modelUuid === item.modelUuid) + .forEach(storage => { + if (storage.activeTime >= 0) { + totalActiveTime += storage.activeTime; - } - }); - } + } + }); + } + }); + + totalItems += sequence.length; }); - totalItems += sequence.length; - }); - - setMachineCount(totalItems); - setMachineActiveTime(totalActiveTime); - let arr = process.map((item: any) => ({ - name: item.modelName, - completed: Math.round((item.activeTime / totalActiveTime) * 100) - })); - setProcessBar(arr); - - + setMachineCount(totalItems); + setMachineActiveTime(totalActiveTime); + let arr = process.map((item: any) => ({ + name: item.modelName, + completed: Math.round((item.activeTime / totalActiveTime) * 100) + })); + setProcessBar(arr); + } } - } - }; + }; - fetchProductSequenceData(); + fetchProductSequenceData(); + } + // if (materialCycleTime <= 0) return }, [products, selectedProduct, getProductById, setMachineCount, materialCycleTime, armBots, vehicles, machines]); // Setting material cycle time @@ -112,9 +118,7 @@ export default function ThroughPutData() { materialHistory.forEach((material) => { const start = material.material.startTime ?? 0; const end = material.material.endTime ?? 0; - if (start === 0 || end === 0) return; - const totalCycleTime = (end - start) / 1000; // Convert milliseconds to seconds setMaterialCycleTime(Number(totalCycleTime.toFixed(2))); // Set the material cycle time in the store }); @@ -124,26 +128,24 @@ export default function ThroughPutData() { useEffect(() => { if (machineActiveTime > 0 && materialCycleTime > 0 && machineCount > 0) { - const avgProcessTime = (machineActiveTime / materialCycleTime) * 100; - const throughput = (3600 / materialCycleTime) * machineCount * (avgProcessTime / 100); - setThroughputData(throughput.toFixed(2)); // Set the throughput data in the store - - - console.log('---Throughput Results---'); - console.log('Total Active Time:', machineActiveTime); - console.log('Material Cycle Time:', materialCycleTime); - console.log('Machine Count:', machineCount); - console.log('Average Process Time (%):', avgProcessTime); - console.log('Calculated Throughput:', throughput); - - + const utilization = machineActiveTime / 3600; // Active time per hour + const unitsPerMachinePerHour = 3600 / materialCycleTime; + const throughput = unitsPerMachinePerHour * machineCount * utilization; + setThroughputData(throughput.toFixed(2)); // Set throughput to state/store + // console.log('---Throughput Results---'); + // console.log('Machine Active Time (s):', machineActiveTime); + // console.log('Material Cycle Time (s):', materialCycleTime); + // console.log('Machine Count:', machineCount); + // console.log('Utilization:', utilization); + // console.log('Throughput (units/hr):', throughput); } }, [machineActiveTime, materialCycleTime, machineCount]); + return ( <> diff --git a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx index a985248..ae8c97f 100644 --- a/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx +++ b/app/src/modules/simulation/machine/instances/machineInstance/machineInstance.tsx @@ -32,7 +32,6 @@ function MachineInstance({ machineDetail }: { machineDetail: MachineStatus }) { const reset = () => { setCurrentPhase("idle"); - console.log("exit"); setMachineState(machineDetail.modelUuid, 'idle'); setMachineActive(machineDetail.modelUuid, false); isIncrememtable.current = true; diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 4b0829e..7be9002 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -190,6 +190,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) { activeSecondsElapsed.current = 0; idleSecondsElapsed.current = 0; previousTimeRef.current = null; + if (animationFrameIdRef.current !== null) { cancelAnimationFrame(animationFrameIdRef.current); animationFrameIdRef.current = null;