Skip to content

Instantly share code, notes, and snippets.

@dylan-sessler
Created September 14, 2022 16:31
Show Gist options
  • Save dylan-sessler/a7c89a2d7be67e38a5f19be1144bbb61 to your computer and use it in GitHub Desktop.
Save dylan-sessler/a7c89a2d7be67e38a5f19be1144bbb61 to your computer and use it in GitHub Desktop.
write tests evan
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