extract and fix to be able to have a web worker

This commit is contained in:
Julien Calixte
2023-07-24 22:46:31 +02:00
parent bf33bf5b2f
commit 734c17fd29
3 changed files with 182 additions and 158 deletions

View File

@@ -1,10 +1,17 @@
import { Feature } from '@/modules/feature/feature' import { Feature } from '@/modules/feature/feature'
import { FeatureStep } from '@/modules/feature/feature-steps' import { FeatureStep } from '@/modules/feature/feature-steps'
import { features } from '@/modules/feature/feature.fixture' import { features as initialFeatures } from '@/modules/feature/feature.fixture'
import { State } from '@/modules/feature/store-type'
import { Strategy } from '@/modules/lean/strategy' import { Strategy } from '@/modules/lean/strategy'
import { pickRandomElement, popNElement, shuffleArray } from '@/utils' import {
pickRandomElement,
popNElement,
shuffleArray,
sumElements
} from '@/utils'
const MAX_FEATURES = 30 const MAX_FEATURES = 30
const HARD_STOP = 5000
const hasQualityIssue = ({ const hasQualityIssue = ({
complexity, complexity,
@@ -26,7 +33,7 @@ const hasQualityIssue = ({
return quality > qualityProbability / multiplicator return quality > qualityProbability / multiplicator
} }
export const newBoard = () => shuffleArray(features) export const newBoard = () => shuffleArray(initialFeatures)
export const initBoard = ( export const initBoard = (
steps: FeatureStep[], steps: FeatureStep[],
@@ -43,7 +50,7 @@ export const initBoard = (
return initialFeatures return initialFeatures
} }
export const nextDay = ({ export const getFeaturesForNextDay = ({
backlog, backlog,
features, features,
initialStep, initialStep,
@@ -57,65 +64,62 @@ export const nextDay = ({
initialStep: number initialStep: number
strategy: Strategy strategy: Strategy
daysWithProblemSolving: number daysWithProblemSolving: number
}): Feature[] => { }): [Feature[], Feature[]] => {
features.forEach((feature) => { features
const isFeatureLive = feature.step === 0 && feature.status === 'done' .filter((feature) => feature.step > 0 || feature.status === 'doing')
if (isFeatureLive) { .forEach((feature) => {
return feature.leadTime++
}
feature.leadTime++ if (strategy === 'problem-solving') {
return
}
if (strategy === 'problem-solving') { switch (feature.status) {
return case 'doing':
} feature.status = 'done'
break
case 'done':
if (strategy === 'pull') {
const nextStep = steps.find(
(step) => step.stepIndex === feature.step - 1
)
if (!nextStep) {
break
}
switch (feature.status) { const hasBlueBinAvailableNextStep =
case 'doing': nextStep.blueBins -
feature.status = 'done' features.filter(
break (f) => f.step === feature.step - 1 && f.status === 'done'
case 'done': ).length >
if (strategy === 'pull') { 0
const nextStep = steps.find(
(step) => step.stepIndex === feature.step - 1
)
if (!nextStep) {
break
}
const hasBlueBinAvailableNextStep = if (hasBlueBinAvailableNextStep) {
nextStep.blueBins - feature.status = 'doing'
features.filter( }
(f) => f.step === feature.step - 1 && f.status === 'done' } else {
).length >
0
if (hasBlueBinAvailableNextStep) {
feature.status = 'doing' feature.status = 'doing'
} }
} else {
feature.status = 'doing'
}
if (feature.status === 'doing') { if (feature.status === 'doing') {
if ( if (
hasQualityIssue({ hasQualityIssue({
complexity: feature.complexity, complexity: feature.complexity,
tasksInParallel: features.filter( tasksInParallel: features.filter(
(f) => f.status === 'doing' && f.step === feature.step (f) => f.status === 'doing' && f.step === feature.step
).length, ).length,
daysWithProblemSolving daysWithProblemSolving
}) })
) { ) {
feature.step = Math.min(4, feature.step + 1) feature.step = Math.min(4, feature.step + 1)
feature.qualityIssue++ feature.qualityIssue++
} else { } else {
feature.step-- feature.step--
}
} }
} break
break }
} })
})
if (features.length < MAX_FEATURES) { if (features.length < MAX_FEATURES) {
switch (strategy) { switch (strategy) {
@@ -151,7 +155,7 @@ export const nextDay = ({
} }
} }
return features return [backlog, features]
} }
const getOverburdenMultiplicator = (tasksInParallel: number) => { const getOverburdenMultiplicator = (tasksInParallel: number) => {
@@ -201,9 +205,85 @@ const getQualityProbability = (
break break
} }
// learnings // team learning
probabilityOfGoodQuality = probabilityOfGoodQuality =
probabilityOfGoodQuality + (1.2 * daysWithProblemSolving) / 100 probabilityOfGoodQuality + (1.2 * daysWithProblemSolving) / 100
return probabilityOfGoodQuality return probabilityOfGoodQuality
} }
export const nextDay = (state: State, strategy: Strategy): State => {
state.meta.totalDays++
state.meta.strategy[strategy]++
if (strategy === 'problem-solving') {
state.meta.daysWithProblemSolving++
}
const [backlog, features] = getFeaturesForNextDay({
backlog: state.backlog,
features: state.features,
steps: state.steps,
initialStep: state.steps[0].stepIndex,
strategy,
daysWithProblemSolving: state.meta.daysWithProblemSolving
})
state.backlog = backlog
state.features = features
console.log(state.features)
return state
}
export const isProjectFinished = (features: Feature[]) =>
features.every((feature) => feature.step === 0 && feature.status === 'done')
export const meanComplexity = (features: Feature[]) => {
return (
Math.round(
100 *
(sumElements(features.map((feature) => feature.complexity)) /
features.length)
) / 100
)
}
export const meanLeadTime = (features: Feature[]) => {
return (
Math.round(
100 *
(sumElements(features.map((feature) => feature.leadTime)) /
features.length)
) / 100
)
}
export const meanQualityIssue = (features: Feature[]) => {
return (
Math.round(
100 *
(sumElements(features.map((feature) => feature.qualityIssue)) /
features.length)
) / 100
)
}
export const simulate = (state: State, strategy: Strategy): State => {
let i = 0
while (!isProjectFinished(state.features) && i++ < HARD_STOP) {
if (strategy === 'problem-solving') {
if (state.meta.totalDays % 5 === 0) {
state = nextDay(state, 'problem-solving')
} else {
state = nextDay(state, 'pull')
}
} else {
state = nextDay(state, strategy)
}
}
return state
}

View File

@@ -30,6 +30,6 @@ export const featureSteps: FeatureStep[] = [
{ {
title: 'Release', title: 'Release',
stepIndex: 0, stepIndex: 0,
blueBins: Infinity blueBins: 99999
} }
] ]

View File

@@ -1,37 +1,19 @@
import { Feature } from '@/modules/feature/feature' import { Feature } from '@/modules/feature/feature'
import { initBoard, newBoard, nextDay } from '@/modules/feature/feature-board' import {
import { FeatureStep, featureSteps } from '@/modules/feature/feature-steps' meanComplexity,
meanLeadTime,
meanQualityIssue
} from '@/modules/feature/feature-board'
import { featureSteps } from '@/modules/feature/feature-steps'
import { Meta, State } from '@/modules/feature/store-type'
import { Strategy } from '@/modules/lean/strategy' import { Strategy } from '@/modules/lean/strategy'
import { sumElements } from '@/utils'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
type Meta = { const clone = (data: any) => JSON.parse(JSON.stringify(data))
totalDays: number
daysWithProblemSolving: number
strategy: Record<Strategy, number>
}
type Analysis = { const instance = new ComlinkWorker<typeof import('./feature-board')>(
worstFeature: Feature new URL('./feature-board', import.meta.url)
meanQualityIssue: number )
meanComplexity: number
meanLeadTime: number
mainStrategy: Strategy | string
}
type Dashboard = Array<{
uuid: string
meta: Meta
analysis: Analysis
}>
type State = {
steps: FeatureStep[]
features: Feature[]
backlog: Feature[]
meta: Meta
dashboards: Dashboard
}
const resetMeta = (): Meta => ({ const resetMeta = (): Meta => ({
totalDays: 0, totalDays: 0,
@@ -52,94 +34,60 @@ export const useFeatureStore = defineStore('feature', {
dashboards: [] dashboards: []
}), }),
actions: { actions: {
initBoard() { async initBoard() {
this.backlog = newBoard() const newBoard = await instance.newBoard()
this.backlog = newBoard
this.steps = featureSteps this.steps = featureSteps
this.features = initBoard(this.steps, this.backlog) this.features = await instance.initBoard(
clone(this.steps),
clone(this.backlog)
)
this.backlog = this.backlog.filter(
(l) => !this.features.find((f) => f.name === l.name)
)
this.meta = resetMeta() this.meta = resetMeta()
}, },
nextDay(strategy: Strategy) { async nextDay(strategy: Strategy) {
this.meta.totalDays++ const newState = await instance.nextDay(clone(this.$state), strategy)
this.meta.strategy[strategy]++
if (strategy === 'problem-solving') { this.backlog = newState.backlog
this.meta.daysWithProblemSolving++ this.meta = newState.meta
} this.features = newState.features
this.features = nextDay({
backlog: this.backlog,
features: this.features,
steps: this.steps,
initialStep: this.steps[0].stepIndex,
strategy,
daysWithProblemSolving: this.meta.daysWithProblemSolving
})
}, },
simulate(strategy: Strategy) { async simulate(strategy: Strategy) {
this.initBoard() await this.initBoard()
while (!this.isProjectFinished) {
if (strategy === 'problem-solving') { const newState = await instance.simulate(clone(this.$state), strategy)
if (this.meta.totalDays % 5 === 0) {
this.nextDay('problem-solving') const [worstFeature] = newState.features.sort((a, b) =>
} else {
this.nextDay('pull')
}
} else {
this.nextDay(strategy)
}
}
const [worstFeature] = this.features.sort((a, b) =>
a.qualityIssue > b.qualityIssue ? -1 : 1 a.qualityIssue > b.qualityIssue ? -1 : 1
) )
this.dashboards.push({ this.dashboards.push({
uuid: new Date().getTime().toString(), uuid: new Date().getTime().toString(),
meta: this.meta, meta: newState.meta,
analysis: { analysis: {
meanComplexity: this.meanComplexity, meanComplexity: await instance.meanComplexity(newState.features),
meanLeadTime: this.meanLeadTime, meanLeadTime: await instance.meanLeadTime(newState.features),
meanQualityIssue: this.meanQualityIssue, meanQualityIssue: await instance.meanQualityIssue(newState.features),
worstFeature, worstFeature,
mainStrategy: Object.entries(this.meta.strategy).sort((a, b) => mainStrategy: Object.entries(newState.meta.strategy).sort((a, b) =>
a[1] > b[1] ? -1 : 1 a[1] > b[1] ? -1 : 1
)[0][0] )[0][0]
} }
}) })
await this.initBoard()
}, },
clearDashboard() { clearDashboard() {
this.dashboards = [] this.dashboards = []
} }
}, },
getters: { getters: {
meanComplexity: (state) => { meanComplexity: (state) => meanComplexity(state.features),
return ( meanLeadTime: (state) => meanLeadTime(state.features),
Math.round( meanQualityIssue: (state) => meanQualityIssue(state.features),
100 *
(sumElements(state.features.map((feature) => feature.complexity)) /
state.features.length)
) / 100
)
},
meanLeadTime: (state) => {
return (
Math.round(
100 *
(sumElements(state.features.map((feature) => feature.leadTime)) /
state.features.length)
) / 100
)
},
meanQualityIssue: (state) => {
return (
Math.round(
100 *
(sumElements(
state.features.map((feature) => feature.qualityIssue)
) /
state.features.length)
) / 100
)
},
featuresGroupedByStep: (state) => { featuresGroupedByStep: (state) => {
const groupedByStep: Record<number, Feature[]> = {} const groupedByStep: Record<number, Feature[]> = {}
@@ -152,10 +100,6 @@ export const useFeatureStore = defineStore('feature', {
}) })
return groupedByStep return groupedByStep
}, }
isProjectFinished: (state) =>
state.features.every(
(feature) => feature.step === 0 && feature.status === 'done'
)
} }
}) })