add simulation module
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FeatureSteps from '@/modules/feature/FeatureSteps.vue'
|
import FeatureSteps from '@/modules/feature/FeatureSteps.vue'
|
||||||
|
import SimulationControls from '@/modules/simulation/SimulationControls.vue'
|
||||||
|
import SimulationDashboard from '@/modules/simulation/SimulationDashboard.vue'
|
||||||
import { StarportCarrier } from 'vue-starport'
|
import { StarportCarrier } from 'vue-starport'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -8,6 +10,8 @@ import { StarportCarrier } from 'vue-starport'
|
|||||||
<StarportCarrier>
|
<StarportCarrier>
|
||||||
<FeatureSteps />
|
<FeatureSteps />
|
||||||
</StarportCarrier>
|
</StarportCarrier>
|
||||||
|
<SimulationControls />
|
||||||
|
<SimulationDashboard />
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import { featureSteps } from '@/modules/feature/feature-steps'
|
|
||||||
import { Strategy } from '@/modules/lean/strategy'
|
|
||||||
import { Dashboard, Meta } from '@/store-type'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
dashboards: Dashboard[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const instance = new ComlinkWorker<typeof import('../feature/feature-board')>(
|
|
||||||
new URL('../feature/feature-board', import.meta.url)
|
|
||||||
)
|
|
||||||
|
|
||||||
const resetMeta = (): Meta => ({
|
|
||||||
totalDays: 0,
|
|
||||||
daysWithProblemSolving: 0,
|
|
||||||
strategy: {
|
|
||||||
push: 0,
|
|
||||||
pull: 0,
|
|
||||||
'problem-solving': 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const useDashboardStore = defineStore('dashboard', {
|
|
||||||
state: (): State => {
|
|
||||||
return {
|
|
||||||
dashboards: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
newDashboard(dashboard: Dashboard) {
|
|
||||||
this.dashboards.push(dashboard)
|
|
||||||
},
|
|
||||||
async simulate(strategy: Strategy) {
|
|
||||||
const backlog = await instance.newBacklog()
|
|
||||||
const steps = featureSteps
|
|
||||||
const features = await instance.initBoard(steps, backlog)
|
|
||||||
|
|
||||||
const newState = await instance.simulate(
|
|
||||||
{
|
|
||||||
backlog,
|
|
||||||
steps,
|
|
||||||
features,
|
|
||||||
meta: resetMeta()
|
|
||||||
},
|
|
||||||
strategy
|
|
||||||
)
|
|
||||||
|
|
||||||
const [worstFeature] = newState.features.sort((a, b) =>
|
|
||||||
a.qualityIssue > b.qualityIssue ? -1 : 1
|
|
||||||
)
|
|
||||||
|
|
||||||
this.newDashboard({
|
|
||||||
uuid: new Date().getTime().toString(),
|
|
||||||
meta: newState.meta,
|
|
||||||
analysis: {
|
|
||||||
meanComplexity: await instance.meanComplexity(newState.features),
|
|
||||||
meanLeadTime: await instance.meanLeadTime(newState.features),
|
|
||||||
meanQualityIssue: await instance.meanQualityIssue(newState.features),
|
|
||||||
worstFeature,
|
|
||||||
mainStrategy: Object.entries(newState.meta.strategy).sort((a, b) =>
|
|
||||||
a[1] > b[1] ? -1 : 1
|
|
||||||
)[0][0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async simulate100(strategy: Strategy) {
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
await this.simulate(strategy)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearDashboard() {
|
|
||||||
this.dashboards = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useDashboardStore } from '@/modules/dashboard/dashboard-store'
|
|
||||||
|
|
||||||
const dashboardStore = useDashboardStore()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="feature-dashboard">
|
|
||||||
Dashboard
|
|
||||||
<ul>
|
|
||||||
<li v-for="dashboard in dashboardStore.dashboards" :key="dashboard.uuid">
|
|
||||||
{{ dashboard.analysis.mainStrategy }}: mean lead time
|
|
||||||
{{ dashboard.analysis.meanLeadTime }} | worst feature for quality
|
|
||||||
{{ dashboard.analysis.worstFeature.qualityIssue }} [{{
|
|
||||||
dashboard.analysis.worstFeature.complexity
|
|
||||||
}}]
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.feature-dashboard {
|
|
||||||
color: var(--background-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDashboardStore } from '@/modules/dashboard/dashboard-store'
|
|
||||||
import FeatureDashboard from '@/modules/feature/FeatureDashboard.vue'
|
|
||||||
import FeatureStep from '@/modules/feature/FeatureStep.vue'
|
import FeatureStep from '@/modules/feature/FeatureStep.vue'
|
||||||
import { featureSteps } from '@/modules/feature/feature-steps'
|
import { featureSteps } from '@/modules/feature/feature-steps'
|
||||||
import { useFeatureStore } from '@/modules/feature/feature-store'
|
import { useFeatureStore } from '@/modules/feature/feature-store'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
const featureStore = useFeatureStore()
|
const featureStore = useFeatureStore()
|
||||||
const dashboardStore = useDashboardStore()
|
|
||||||
|
|
||||||
onMounted(() => featureStore.initBoard())
|
onMounted(() => featureStore.initBoard())
|
||||||
|
|
||||||
@@ -47,19 +44,6 @@ const pushAndProblemSolving20Percent = () => {
|
|||||||
<button @click="featureStore.nextDay('problem-solving')">
|
<button @click="featureStore.nextDay('problem-solving')">
|
||||||
problem solving
|
problem solving
|
||||||
</button>
|
</button>
|
||||||
<button @click="dashboardStore.simulate('push')">
|
|
||||||
simulate push system
|
|
||||||
</button>
|
|
||||||
<button @click="dashboardStore.simulate('pull')">
|
|
||||||
simulate pull system
|
|
||||||
</button>
|
|
||||||
<button @click="dashboardStore.simulate100('pull')">
|
|
||||||
simulate 100 pull system
|
|
||||||
</button>
|
|
||||||
<button @click="dashboardStore.simulate('problem-solving')">
|
|
||||||
simulate pull and problem solving
|
|
||||||
</button>
|
|
||||||
<button @click="dashboardStore.clearDashboard()">clear dashboard</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="features-steps">
|
<ul class="features-steps">
|
||||||
@@ -70,7 +54,6 @@ const pushAndProblemSolving20Percent = () => {
|
|||||||
:features="featureStore.featuresGroupedByStep[step.stepIndex] ?? []"
|
:features="featureStore.featuresGroupedByStep[step.stepIndex] ?? []"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
<FeatureDashboard />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import { FeatureStep } from '@/modules/feature/feature-steps'
|
|||||||
import { features as initialFeatures } from '@/modules/feature/feature.fixture'
|
import { features as initialFeatures } from '@/modules/feature/feature.fixture'
|
||||||
import { Strategy } from '@/modules/lean/strategy'
|
import { Strategy } from '@/modules/lean/strategy'
|
||||||
import { FeatureState } from '@/store-type'
|
import { FeatureState } from '@/store-type'
|
||||||
import {
|
import { getMean, pickRandomElement, popNElement, shuffleArray } from '@/utils'
|
||||||
pickRandomElement,
|
|
||||||
popNElement,
|
|
||||||
shuffleArray,
|
|
||||||
sumElements
|
|
||||||
} from '@/utils'
|
|
||||||
|
|
||||||
const MAX_FEATURES = 30
|
const MAX_FEATURES = 30
|
||||||
const HARD_STOP = 5000
|
const HARD_STOP = 5000
|
||||||
@@ -241,34 +236,16 @@ export const nextDay = (
|
|||||||
export const isProjectFinished = (features: Feature[]) =>
|
export const isProjectFinished = (features: Feature[]) =>
|
||||||
features.every((feature) => feature.step === 0 && feature.status === 'done')
|
features.every((feature) => feature.step === 0 && feature.status === 'done')
|
||||||
|
|
||||||
export const meanComplexity = (features: Feature[]) => {
|
export const getMeanComplexity = (features: Feature[]) => {
|
||||||
return (
|
return getMean(features.map((feature) => feature.complexity))
|
||||||
Math.round(
|
|
||||||
100 *
|
|
||||||
(sumElements(features.map((feature) => feature.complexity)) /
|
|
||||||
features.length)
|
|
||||||
) / 100
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const meanLeadTime = (features: Feature[]) => {
|
export const getMeanLeadTime = (features: Feature[]) => {
|
||||||
return (
|
return getMean(features.map((feature) => feature.leadTime))
|
||||||
Math.round(
|
|
||||||
100 *
|
|
||||||
(sumElements(features.map((feature) => feature.leadTime)) /
|
|
||||||
features.length)
|
|
||||||
) / 100
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const meanQualityIssue = (features: Feature[]) => {
|
export const getMeanQualityIssue = (features: Feature[]) => {
|
||||||
return (
|
return getMean(features.map((feature) => feature.qualityIssue))
|
||||||
Math.round(
|
|
||||||
100 *
|
|
||||||
(sumElements(features.map((feature) => feature.qualityIssue)) /
|
|
||||||
features.length)
|
|
||||||
) / 100
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const simulate = (
|
export const simulate = (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Feature } from '@/modules/feature/feature'
|
import { Feature } from '@/modules/feature/feature'
|
||||||
import {
|
import {
|
||||||
|
getMeanComplexity,
|
||||||
|
getMeanLeadTime,
|
||||||
|
getMeanQualityIssue,
|
||||||
initBoard,
|
initBoard,
|
||||||
meanComplexity,
|
|
||||||
meanLeadTime,
|
|
||||||
meanQualityIssue,
|
|
||||||
newBacklog,
|
newBacklog,
|
||||||
nextDay
|
nextDay
|
||||||
} from '@/modules/feature/feature-board'
|
} from '@/modules/feature/feature-board'
|
||||||
@@ -50,9 +50,9 @@ export const useFeatureStore = defineStore('feature', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
meanComplexity: (state) => meanComplexity(state.features),
|
meanComplexity: (state) => getMeanComplexity(state.features),
|
||||||
meanLeadTime: (state) => meanLeadTime(state.features),
|
meanLeadTime: (state) => getMeanLeadTime(state.features),
|
||||||
meanQualityIssue: (state) => meanQualityIssue(state.features),
|
meanQualityIssue: (state) => getMeanQualityIssue(state.features),
|
||||||
featuresGroupedByStep: (state) => {
|
featuresGroupedByStep: (state) => {
|
||||||
const groupedByStep: Record<number, Feature[]> = {}
|
const groupedByStep: Record<number, Feature[]> = {}
|
||||||
|
|
||||||
|
|||||||
37
src/modules/simulation/SimulationControls.vue
Normal file
37
src/modules/simulation/SimulationControls.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSimulationStore } from '@/modules/simulation/simulation-store'
|
||||||
|
const simulationStore = useSimulationStore()
|
||||||
|
const NUMBER_OF_SIMULATION = 300
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="simulation-controls">
|
||||||
|
<button @click="simulationStore.simulate('push')">
|
||||||
|
simulate push system
|
||||||
|
</button>
|
||||||
|
<button @click="simulationStore.simulate('pull')">
|
||||||
|
simulate pull system
|
||||||
|
</button>
|
||||||
|
<button @click="simulationStore.simulate('problem-solving')">
|
||||||
|
simulate pull and problem solving
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="simulationStore.multiSimulation(NUMBER_OF_SIMULATION, 'push')"
|
||||||
|
>
|
||||||
|
simulate {{ NUMBER_OF_SIMULATION }} push system
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="simulationStore.multiSimulation(NUMBER_OF_SIMULATION, 'pull')"
|
||||||
|
>
|
||||||
|
simulate {{ NUMBER_OF_SIMULATION }} pull system
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
simulationStore.multiSimulation(NUMBER_OF_SIMULATION, 'problem-solving')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
simulate {{ NUMBER_OF_SIMULATION }} pull and problem solving
|
||||||
|
</button>
|
||||||
|
<button @click="simulationStore.clearDashboard()">clear dashboard</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
43
src/modules/simulation/SimulationDashboard.vue
Normal file
43
src/modules/simulation/SimulationDashboard.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useSimulationStore } from '@/modules/simulation/simulation-store'
|
||||||
|
|
||||||
|
const simulationStore = useSimulationStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="simulation-dashboard">
|
||||||
|
Dashboard ({{ simulationStore.simulationsDone }} /
|
||||||
|
{{ simulationStore.requestedSimulation }}
|
||||||
|
simulations)
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>push</th>
|
||||||
|
<th>pull</th>
|
||||||
|
<th>pull and DPS</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>lead time</td>
|
||||||
|
<td>
|
||||||
|
{{ simulationStore.meanPushLeadTime }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ simulationStore.meanPullLeadTime }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ simulationStore.meanPullDPSLeadTime }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.simulation-dashboard {
|
||||||
|
color: var(--background-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
108
src/modules/simulation/simulation-store.ts
Normal file
108
src/modules/simulation/simulation-store.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { featureSteps } from '@/modules/feature/feature-steps'
|
||||||
|
import { Strategy } from '@/modules/lean/strategy'
|
||||||
|
import { Dashboard, Meta } from '@/store-type'
|
||||||
|
import { getMean } from '@/utils'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
dashboards: Dashboard[]
|
||||||
|
requestedSimulation: number
|
||||||
|
simulationsDone: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = new ComlinkWorker<typeof import('../feature/feature-board')>(
|
||||||
|
new URL('../feature/feature-board', import.meta.url)
|
||||||
|
)
|
||||||
|
|
||||||
|
const resetMeta = (): Meta => ({
|
||||||
|
totalDays: 0,
|
||||||
|
daysWithProblemSolving: 0,
|
||||||
|
strategy: {
|
||||||
|
push: 0,
|
||||||
|
pull: 0,
|
||||||
|
'problem-solving': 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useSimulationStore = defineStore('dashboard', {
|
||||||
|
state: (): State => {
|
||||||
|
return {
|
||||||
|
dashboards: [],
|
||||||
|
requestedSimulation: 0,
|
||||||
|
simulationsDone: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
newDashboard(dashboard: Dashboard) {
|
||||||
|
this.dashboards.push(dashboard)
|
||||||
|
},
|
||||||
|
async simulate(strategy: Strategy) {
|
||||||
|
const steps = featureSteps
|
||||||
|
const backlog = await instance.newBacklog()
|
||||||
|
const features = await instance.initBoard(steps, backlog)
|
||||||
|
|
||||||
|
const newState = await instance.simulate(
|
||||||
|
{
|
||||||
|
backlog,
|
||||||
|
steps,
|
||||||
|
features,
|
||||||
|
meta: resetMeta()
|
||||||
|
},
|
||||||
|
strategy
|
||||||
|
)
|
||||||
|
|
||||||
|
const [worstFeature] = newState.features.sort((a, b) =>
|
||||||
|
a.qualityIssue > b.qualityIssue ? -1 : 1
|
||||||
|
)
|
||||||
|
|
||||||
|
this.newDashboard({
|
||||||
|
uuid: new Date().getTime().toString(),
|
||||||
|
meta: newState.meta,
|
||||||
|
analysis: {
|
||||||
|
meanComplexity: await instance.getMeanComplexity(newState.features),
|
||||||
|
meanLeadTime: await instance.getMeanLeadTime(newState.features),
|
||||||
|
meanQualityIssue: await instance.getMeanQualityIssue(
|
||||||
|
newState.features
|
||||||
|
),
|
||||||
|
worstFeature,
|
||||||
|
strategy
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async multiSimulation(simulations: number, strategy: Strategy) {
|
||||||
|
this.requestedSimulation += simulations
|
||||||
|
for (let i = 0; i < simulations; i++) {
|
||||||
|
await this.simulate(strategy)
|
||||||
|
this.simulationsDone++
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clearDashboard() {
|
||||||
|
this.dashboards = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
meanPushLeadTime: (state) => {
|
||||||
|
return getMean(
|
||||||
|
state.dashboards
|
||||||
|
.filter((dashboard) => dashboard.analysis.strategy === 'push')
|
||||||
|
.map((dashboard) => dashboard.analysis.meanLeadTime)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
meanPullLeadTime: (state) => {
|
||||||
|
return getMean(
|
||||||
|
state.dashboards
|
||||||
|
.filter((dashboard) => dashboard.analysis.strategy === 'pull')
|
||||||
|
.map((dashboard) => dashboard.analysis.meanLeadTime)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
meanPullDPSLeadTime: (state) => {
|
||||||
|
return getMean(
|
||||||
|
state.dashboards
|
||||||
|
.filter(
|
||||||
|
(dashboard) => dashboard.analysis.strategy === 'problem-solving'
|
||||||
|
)
|
||||||
|
.map((dashboard) => dashboard.analysis.meanLeadTime)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -13,7 +13,7 @@ export type Analysis = {
|
|||||||
meanQualityIssue: number
|
meanQualityIssue: number
|
||||||
meanComplexity: number
|
meanComplexity: number
|
||||||
meanLeadTime: number
|
meanLeadTime: number
|
||||||
mainStrategy: Strategy | string
|
strategy: Strategy | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Dashboard = {
|
export type Dashboard = {
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
export const getMean = (data: number[]) =>
|
||||||
|
Math.round(100 * (sumElements(data) / data.length)) / 100
|
||||||
|
|
||||||
export const clone = (data: any) => JSON.parse(JSON.stringify(data))
|
export const clone = (data: any) => JSON.parse(JSON.stringify(data))
|
||||||
|
|
||||||
export const shuffleArray = <T>(array: T[]) => {
|
export const shuffleArray = <T>(array: T[]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user