Created
September 14, 2022 16:31
-
-
Save dylan-sessler/a7c89a2d7be67e38a5f19be1144bbb61 to your computer and use it in GitHub Desktop.
write tests evan
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const db = require ('../../../db') | |
module.exports = async function supplierLeadTime(sourcingOption) { | |
const LOWER_LEAD_TIME_CONTRIBUTION_RATE = 0.2 // Maybe set this in the config table so it's easy for Evan to adjust? | |
const ptgsWithIsTool = sourcingOption.partGroups.map((ptg) => { | |
ptg.isTool = isTool(ptg) | |
return ptg | |
}) | |
const supplierParams = await db.getSupplierParams(sourcingOption.supplier) | |
const uniqueMachineTypes = getUniqueMachineTypes(sourcingOption.partGroups) | |
const uniqueMachineTypesWithParams = await decorateUniqueMachineTypesWithParams(uniqueMachineTypes, sourcingOption.supplier) | |
const uniqueMachineTypesWithBufferParams = decorateUniqueMachineTypesWithBufferParams(uniqueMachineTypesWithParams, sourcingOption.parts) | |
const ptgsWithBufferParams = await decoratePtgsWithBufferParams(sourcingOption.partGroups, sourcingOption.supplier) | |
const totalMachineAndLaborTimes = getTotalMachineAndLaborTimes(sourcingOption.partGroups, false) | |
const machineDays = getLongestMachineDays(uniqueMachineTypesWithParams, totalMachineAndLaborTimes) | |
const laborDays = getLaborDays(supplierParams, totalMachineAndLaborTimes) | |
const toolBaseLeadTime = baseLeadTime(machineDays.maxToolMachineDays, laborDays.toolLaborDays) | |
const partBaseLeadTime = baseLeadTime(machineDays.maxPartMachineDays, laborDays.partLaborDays) | |
const bottleneckBaseLeadTimes = bottleneckBaseLeadTime(ptgsWithBufferParams, uniqueMachineTypesWithBufferParams, supplierParams) | |
// HELPERS | |
function baseLeadTime(machineDays, laborDays) { | |
let baseLeadTime | |
if(firstIsLarger(machineDays, laborDays)){baseLeadTime = machineDays + LOWER_LEAD_TIME_CONTRIBUTION_RATE * laborDays} | |
else {baseLeadTime = laborDays + LOWER_LEAD_TIME_CONTRIBUTION_RATE * machineDays} | |
} | |
function firstIsLarger(a, b) { | |
return a > b | |
} | |
} | |
function bottleneckBaseLeadTime(ptgs, machineTypesWithParams, supplierParams) { | |
const ptgsWithBottleneckLeadTimes = ptgs.map((ptg) => { | |
const machineAndLaborTimes = getTotalMachineAndLaborTimes([ptg]) | |
const machineTypeForThisPtg = machineTypesWithParams.filter((mt) => mt.name === ptg.machineType) | |
const ptgWithBottleneckLeadTimes = singleBottleneckBaseLeadTime(ptg, machineTypeForThisPtg, supplierParams, machineAndLaborTimes) | |
if(isTool(ptg)){ | |
const ptpcsWithBottleneckLeadTimes = ptg.productionToolPartConfigurations.map((ptpc) => { | |
const machineAndLaborTimes = getTotalMachineAndLaborTimes([ptpc]) | |
const machineTypeForThisPtpc = machineTypesWithParams.filter((mt) => mt.name === ptpc.machineType) | |
const ptpcWithBottleneckLeadTimes = singleBottleneckBaseLeadTime(ptpc, machineTypeForThisPtpc, supplierParams, machineAndLaborTimes) | |
return ptpcWithBottleneckLeadTimes | |
}) | |
ptg.productionToolPartConfigurations = ptpcsWithBottleneckLeadTimes | |
} | |
return ptgWithBottleneckLeadTimes | |
}) | |
const maxBottleneckLeadTime = ptgsWithBottleneckLeadTimes.reduce((acc, ptg) => { | |
if(isTool(ptg)){ | |
acc.maxToolLeadTime = Math.max(acc.maxToolLeadTime, ptg.toolBottleneckLeadTime) | |
acc.maxPartLeadTime = ptg.productionToolPartConfigurations.reduce((accPtpc, ptpc) => { | |
return Math.max(accPtpc, ptpc.partBottleneckLeadTime) | |
}, 0) | |
return acc | |
} else { | |
acc.maxPartLeadTime = Math.max(acc.maxPartLeadTime, ptg.partBottleneckLeadTime) | |
return acc | |
} | |
}, {maxToolLeadTime: 0, maxPartLeadTime: 0}) // init at 0 because not all projects have tools and we want later math to not have to test if this is the case | |
return maxBottleneckLeadTime | |
} | |
function singleBottleneckBaseLeadTime(ptc, machineTypeWithParams, supplierParams, machineAndLaborTimes) { | |
const machineDays = getLongestMachineDays(machineTypeWithParams, machineAndLaborTimes) | |
const laborDays = getLaborDays(supplierParams, machineAndLaborTimes) | |
ptc.toolBottleneckLeadTime = machineDays.maxToolMachineDays + laborDays.toolLaborDays | |
ptc.partBottleneckLeadTime = machineDays.maxPartMachineDays + laborDays.partLaborDays | |
return ptc | |
} | |
function getLaborDays(params, laborTimes) { | |
// we assume that all the tools are made before the parts. This simplifies the | |
// labor allocation problem and we never have tools and non-tool ptgs in the | |
// same sourcingOption | |
const totalLaborHours = laborTimes.toolLaborTime + laborTimes.ptgPartsLaborTime + laborTimes.ptpcLaborTime | |
// The labor hours per day is dependent on the total amount of labor needed on | |
// the job. Larger projects means more labor and more machines get allocated | |
// to the job, speeding up production. | |
const calculatedLaborHoursPerDay = params.minLaborHoursPerDay + params.rateOfLaborHoursIncreasing * totalLaborHours | |
const laborHoursPerDay = Math.min(calculatedLaborHoursPerDay, params.maxLaborHoursPerDay) | |
const partLaborHours = laborTimes.ptgPartsLaborTime + laborTimes.ptpcLaborTime | |
const toolLaborHours = laborTimes.toolLaborTime | |
const laborDays = { | |
partLaborDays: partLaborHours / laborHoursPerDay, | |
toolLaborDays: toolLaborHours / laborHoursPerDay | |
} | |
return laborDays | |
} | |
function getLongestMachineDays(machineTypes, machineTimes) { | |
const toolMachineTypes = machineTypes.filter((mt) => mt.isTool) | |
const partMachineTypes = machineTypes.filter((mt) => !mt.isTool) | |
const toolMachineDaysForEachMachineType = toolMachineTypes.map((mt) => { | |
const machineDays = getMachineDaysSingleMachineType(mt, machineTimes) | |
return machineDays | |
}) | |
const maxToolMachineDays = toolMachineDaysForEachMachineType.reduce((acc, num) => {return Math.max(acc, num)}, -Infinity) | |
const partMachineDaysForEachMachineType = partMachineTypes.map((mt) => { | |
const machineDays = getMachineDaysSingleMachineType(mt, machineTimes) | |
return machineDays | |
}) | |
const maxPartMachineDays = partMachineDaysForEachMachineType.reduce((acc, num) => {return Math.max(acc, num)}, -Infinity) | |
return { | |
maxToolMachineDays: maxToolMachineDays, | |
maxPartMachineDays: maxPartMachineDays | |
} | |
} | |
function getMachineDaysSingleMachineType(machineType, machineTimes) { | |
const totalMachineHours = getTotalHours() | |
const calculatedMachineHoursPerDay = machineType.params.minMachineHoursPerDay + machineType.params.rateOfMachineHoursIncreasing * totalMachineHours | |
const machineHoursPerDay = Math.min( calculatedMachineHoursPerDay, machineType.params.maxMachineHoursPerDay ) | |
// only need to wait for the larger of the two startup buffers | |
const startupBuffer = Math.max(machineType.params.rareMachineLeadTimeDelay, machineType.minLeadTimeToSourceMaterial) | |
const machineDays = totalMachineHours / machineHoursPerDay + startupBuffer | |
return machineDays | |
// HELPERS | |
function getTotalHours() { | |
const toolMachineTime = machineTimes.toolMachineTime[machineType] ? machineTimes.toolMachineTime[machineType] : 0 | |
const ptgPartsMachineTime = machineTimes.ptgPartsMachineTime[machineType] ? machineTimes.ptgPartsMachineTime[machineType] : 0 | |
const ptpcMachineTime = machineTimes.ptpcMachineTime[machineType] ? machineTimes.ptpcMachineTime[machineType] : 0 | |
return toolMachineTime + ptgPartsMachineTime + ptpcMachineTime | |
} | |
} | |
function getUniqueMachineTypes(ptgs) { | |
const allMachineTypes = ptgs.flatMap((ptg) => { | |
if(ptg.isTool){ | |
const allPtpcMachineTypes = ptg.productionToolPartConfigurations.map((ptpc) => { | |
return {name: ptpc.machineType, isTool: false} | |
}) | |
return [{name: ptg.machineType, isTool: true}].concat(allPtpcMachineTypes) | |
} else { | |
return {name: ptg.machineType, isTool: false} | |
} | |
}) | |
const uniqueMachineTypes = Array.from(new Set(allMachineTypes)) | |
return uniqueMachineTypes | |
} | |
async function decorateUniqueMachineTypesWithParams(uniqueMachineTypes, supplier){ | |
const uniqueMachineTypesWithParams = await Promise.all(uniqueMachineTypes.map(async (mt) => { | |
mt.params = await db.getSupplierMachineTypeParams(supplier, mt.name) | |
return mt | |
})) | |
return uniqueMachineTypesWithParams | |
} | |
async function decoratePtgsWithBufferParams(ptgs, supplier) { | |
const ptgsWithBufferParams = await Promise.all(ptgs.map(async (ptg) => { | |
const params = await db.getSupplierMaterialMachineTypeParams(supplier, ptg.material, ptg.machineType) | |
ptg.leadTimeToSourceMaterial = params.leadTimeToSourceMaterial | |
if(isTool(ptg)){ | |
ptg.productionToolPartConfigurations = await Promise.all(ptg.productionToolPartConfigurations.map(async (ptpc) => { | |
const params = await db.getSupplierMaterialMachineTypeParams(supplier, ptpc.material, ptpc.machineType) | |
ptpc.leadTimeToSourceMaterial = params.leadTimeToSourceMaterial | |
return ptpc | |
})) | |
} | |
return ptg | |
})) | |
return ptgsWithBufferParams | |
} | |
function decorateUniqueMachineTypesWithBufferParams(uniqueMachineTypes, ptcs) { | |
const uniqueMachineTypesWithBufferParams = uniqueMachineTypes.map((mt) => { | |
const ptcsWithThisMachineType = ptcs.filter((ptc) => mt.name === ptc.machineType) | |
const minLeadTimeToSourceMaterial = ptcsWithThisMachineType.reduce((acc, ptc) => {return Math.min(acc, ptc.leadTimeToSourceMaterial)}, +Infinity) | |
mt.minLeadTimeToSourceMaterial = minLeadTimeToSourceMaterial | |
return mt | |
}) | |
return uniqueMachineTypesWithBufferParams | |
} | |
function getTotalMachineAndLaborTimes(ptgs, isBottleneckCalculation) { | |
// TODO break this fxn down. It's gotten pretty bloated and has some re-usable stuff internally | |
// labor gets added into one big pile that is shared across all machineTypes. | |
// machineTime needs to be kept separate by machineType. | |
const totalMachineAndLaborTimes = ptgs.reduce((acc, ptg) => { | |
if(isTool(ptg)){ | |
const ptpcTime = ptg.productionToolPartConfigurations.reduce((accPtpc, ptpc) => { | |
if(accPtpc.machineTime[ptpc.machineType]){accPtpc.machineTime[ptpc.machineType] += ptpc.machineTime} | |
else {accPtpc.machineTime[ptpc.machineType] = ptpc.machineTime} | |
if(isBottleneckCalculation){accPtpc.laborTime += ptpc.labor} | |
else {accPtpc.laborTime += ptpc.labor + ptpc.duringMachineTimeLabor} | |
return accPtpc | |
}, {machineTime: {}, laborTime: 0}) | |
if(acc.toolMachineTime[ptg.machineType]){acc.toolMachineTime[ptg.machineType] += ptg.machineTime} | |
else {acc.toolMachineTime[ptg.machineType] = ptg.machineTime} | |
if(isBottleneckCalculation){acc.toolLaborTime += ptg.labor} | |
else{acc.toolLaborTime += ptg.labor + ptg.duringMachineTimeLabor} | |
Object.keys(ptpcTime.machineTime).forEach((machineType) => { | |
if(acc.ptpcMachineTime[machineType]){acc.ptpcMachineTime[machineType] += ptpcTime.machineTime[machineType]} | |
else {acc.ptpcMachineTime[machineType] = ptpcTime.machineTime[machineType]} | |
}) | |
acc.ptpcLaborTime += ptpcTime.laborTime | |
return acc | |
} | |
else { | |
if(acc.ptgPartsMachineTime[ptg.machineType]){acc.ptgPartsMachineTime[ptg.machineType] += ptg.machineTime} | |
else {acc.ptgPartsMachineTime[ptg.machineType] = ptg.machineTime} | |
if(isBottleneckCalculation){acc.ptgPartsLaborTime += ptg.labor} | |
else{acc.ptgPartsLaborTime += ptg.labor + ptg.duringMachineTimeLabor} | |
return acc | |
} | |
}, {toolMachineTime: {}, toolLaborTime: 0, ptgPartsMachineTime: {}, ptgPartsLaborTime: 0, ptpcMachineTime: {}, ptpcLaborTime: 0}) | |
return totalMachineAndLaborTimes | |
} | |
function isTool(ptg){ | |
return !(ptg.productionToolPartConfigurations === undefined) && ptg.productionToolPartConfigurations.length > 0 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment