Merge branch 'main' of github.com:jcalixte/tps into main

This commit is contained in:
Julien Calixte
2023-07-31 22:58:33 +02:00
13 changed files with 128 additions and 55 deletions

View File

@@ -1,15 +1,3 @@
# Thinking People System # Thinking People System
A interactive website to better understand the Thinking People System. A interactive website to better understand the Thinking People System.
## Feature flow
### The project
- 0 defect policy,
- has no external dependancies,
- the team has a strong opinion on Jidoka:
- teams spot 100% of the defects,
- teams do not take a feature who has defects,
- the team who introduced the defect needs to rework on the feature.
- there is no limit on how many defects a feature can have.

View File

@@ -6,7 +6,7 @@
--color: white; --color: white;
} }
* { *:not(td):not(th) {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@@ -29,3 +29,17 @@ main {
margin: 1rem 1rem 0; margin: 1rem 1rem 0;
color: var(--color); color: var(--color);
} }
.meaning {
color: #9f9a9a;
font-weight: 100;
font-size: 1rem;
}
.meaning::before {
content: '(';
}
.meaning::after {
content: ')';
}

51
main.ts
View File

@@ -1,28 +1,28 @@
type TPSSearchParams = { type TPSSearchParams = {
display?: "house-only" | "full" display?: 'house-only' | 'full'
} }
const params = new URL(document.location.href).searchParams const params = new URL(document.location.href).searchParams
const display = params.get("display") const display = params.get('display')
if (display === "house-only") { if (display === 'house-only') {
document.querySelector("header")?.remove() document.querySelector('header')?.remove()
} }
const size = params.get("size") const size = params.get('size')
if (size === "small") { if (size === 'small') {
const body = document.querySelector("body") const body = document.querySelector('body')
if (body) { if (body) {
body.style.fontSize = "15px" body.style.fontSize = '15px'
} }
} }
const focusElements = params.getAll("focus") const focusElements = params.getAll('focus')
if (focusElements.length > 0) { if (focusElements.length > 0) {
const focusables = document.querySelectorAll(".focusable") const focusables = document.querySelectorAll('.focusable')
focusables.forEach((focusable) => { focusables.forEach((focusable) => {
const elementToFocus = focusElements.some((element) => const elementToFocus = focusElements.some((element) =>
@@ -30,20 +30,35 @@ if (focusElements.length > 0) {
) )
if (!elementToFocus) { if (!elementToFocus) {
focusable.classList.add("no-focus") focusable.classList.add('no-focus')
} }
}) })
} }
const textHide = params.get("text") const textHide = params.get('text')
if (textHide === "hide") { if (textHide === 'hide') {
const focusables = document.querySelectorAll(".focusable") const focusables = document.querySelectorAll('.focusable')
focusables.forEach((focusable) => focusable.classList.add("text-hide")) focusables.forEach((focusable) => focusable.classList.add('text-hide'))
} }
const scrollParam = params.get("scroll") const scrollParam = params.get('scroll')
if (scrollParam === "end") { if (scrollParam === 'end') {
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }) window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
}
const primaryColorParam = params.get('primary')
if (primaryColorParam) {
document.documentElement.style.setProperty(
'--primary-color',
`#${primaryColorParam}`
)
}
const colorParam = params.get('color')
if (colorParam) {
document.documentElement.style.setProperty('--color', `#${colorParam}`)
} }

View File

@@ -17,6 +17,7 @@
"chart.xkcd": "^1.1.13", "chart.xkcd": "^1.1.13",
"comlink": "^4.4.1", "comlink": "^4.4.1",
"pinia": "^2.1.4", "pinia": "^2.1.4",
"random-js": "^2.1.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-starport": "^0.3.0" "vue-starport": "^0.3.0"
}, },

7
pnpm-lock.yaml generated
View File

@@ -17,6 +17,9 @@ dependencies:
pinia: pinia:
specifier: ^2.1.4 specifier: ^2.1.4
version: 2.1.4(typescript@5.1.6)(vue@3.3.4) version: 2.1.4(typescript@5.1.6)(vue@3.3.4)
random-js:
specifier: ^2.1.0
version: 2.1.0
vue: vue:
specifier: ^3.3.4 specifier: ^3.3.4
version: 3.3.4 version: 3.3.4
@@ -897,6 +900,10 @@ packages:
react-is: 18.2.0 react-is: 18.2.0
dev: true dev: true
/random-js@2.1.0:
resolution: {integrity: sha512-CRUyWmnzmZBA7RZSVGq0xMqmgCyPPxbiKNLFA5ud7KenojVX2s7Gv+V7eB52beKTPGxWRnVZ7D/tCIgYJJ8vNQ==}
dev: false
/react-is@18.2.0: /react-is@18.2.0:
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
dev: true dev: true

View File

@@ -9,7 +9,10 @@ withDefaults(defineProps<{ color?: string }>(), {
<template> <template>
<BaseIcon :color="color"> <BaseIcon :color="color">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" /> <path d="M3 12h1m8 -9v1m8 8h1m-15.4 -6.4l.7 .7m12.1 -.7l-.7 .7" />
<path d="M21 21l-6 -6" /> <path
d="M9 16a5 5 0 1 1 6 0a3.5 3.5 0 0 0 -1 3a2 2 0 0 1 -4 0a3.5 3.5 0 0 0 -1 -3"
/>
<path d="M9.7 17l4.6 0" />
</BaseIcon> </BaseIcon>
</template> </template>

View File

@@ -4,13 +4,21 @@
<div class="flow-hypothesis"> <div class="flow-hypothesis">
<p>Here our hypothesis:</p> <p>Here our hypothesis:</p>
<ol> <ol>
<li>it takes the same amount of time for each team to complete a task</li> <li>
it takes the same amount of time for each team to complete a task
<span class="meaning">same task time</span>
</li>
<li>teams have no other external dependencies</li>
<li> <li>
teams know exactly what they need to produce their part, they will tag teams know exactly what they need to produce their part, they will tag
any defects they found when verifying the feature is good. any defects they found when verifying the feature is good.
</li> </li>
<li>the team where the defect appears must rework the feature.</li> <li>
0 defect policy: the team where the defect appears must rework the
feature.
</li>
<li>release team never fails</li> <li>release team never fails</li>
<li>there is no limit on how many defects a feature can have.</li>
</ol> </ol>
</div> </div>
</template> </template>

View File

@@ -4,13 +4,14 @@ import PullSystemIcon from '@/icons/PullSystemIcon.vue'
import PushSystemIcon from '@/icons/PushSystemIcon.vue' import PushSystemIcon from '@/icons/PushSystemIcon.vue'
import FeatureItem from '@/modules/feature/FeatureItem.vue' import FeatureItem from '@/modules/feature/FeatureItem.vue'
import FlowControls from '@/modules/feature/FlowControls.vue' import FlowControls from '@/modules/feature/FlowControls.vue'
import QualityIssue from '@/modules/feature/QualityIssue.vue'
import { Feature } from '@/modules/feature/feature' import { Feature } from '@/modules/feature/feature'
const feature: Feature = { const feature: Feature = {
name: 'As a user, in the homepage, I can login', name: 'As a user, in the homepage, I can login',
complexity: 3, complexity: 3,
leadTime: 2, leadTime: 2,
qualityIssue: 2, qualityIssue: 4,
status: 'doing', status: 'doing',
step: 2 step: 2
} }
@@ -36,8 +37,8 @@ const feature: Feature = {
number and deliver as fast as possible. number and deliver as fast as possible.
</p> </p>
<p> <p>
<span class="numeric">{{ feature.qualityIssue }}</span> are the number of <QualityIssue class="inline" :quality-issue="feature.qualityIssue" />
defects the feature had during the flow. are the number of defects the feature had during the flow.
</p> </p>
<p> <p>
You have 20 features to deliver, and each day you can choose between 3 You have 20 features to deliver, and each day you can choose between 3
@@ -85,15 +86,22 @@ const feature: Feature = {
understand and limit rework. The more the team investigate, the more the understand and limit rework. The more the team investigate, the more the
team learn and start to be extremely good at problem solving. team learn and start to be extremely good at problem solving.
</p> </p>
<h3>Blue bin: the security stocks</h3> <h3>Blue bin: the security stock</h3>
<p> <p>
Blue bins are your security stock, to make sure teams can work without any Blue bins are your security stock, to make sure teams can work without any
blockers. It's to make sure the next team will always have material to blockers. It's to make sure the next team will always have material to
transform. But it comes with a cost: overburden, stagnation (increase lead transform. But it comes with a cost: overburden, stagnation (increase lead
time) and duplicated mistakes (not simulated here). The less you have, the time) and duplicated mistakes
<span class="meaning">not simulated here</span>. The less you have, the
less your team has mental charge. The more you have, the more secure you less your team has mental charge. The more you have, the more secure you
are to make teams work. One solution: simplify your flow and lower the are to make teams work. One solution: simplify your flow and lower the
number of operation the teams have to do to deliver a feature. number of operation the teams have to do to deliver a feature.
</p> </p>
</div> </div>
</template> </template>
<style scoped>
.inline {
display: inline;
}
</style>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import QualityIssue from '@/modules/feature/QualityIssue.vue'
import { Feature } from '@/modules/feature/feature' import { Feature } from '@/modules/feature/feature'
import { computed } from 'vue' import { computed } from 'vue'
@@ -16,9 +17,10 @@ const hasQualityIssues = computed(() => props.feature.qualityIssue > 0)
</div> </div>
<div class="numeric"> <div class="numeric">
{{ feature.leadTime }}d {{ feature.leadTime }}d
<div v-if="hasQualityIssues" class="red-bin"> <QualityIssue
{{ feature.qualityIssue }} v-if="hasQualityIssues"
</div> :quality-issue="feature.qualityIssue"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -39,12 +41,5 @@ const hasQualityIssues = computed(() => props.feature.qualityIssue > 0)
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} }
.red-bin {
--warning-color: #ca0e0e;
border: 2px solid var(--warning-color);
padding: 0 0.5rem 0.1rem;
color: var(--warning-color);
}
} }
</style> </style>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
defineProps<{
qualityIssue: number
}>()
</script>
<template>
<div class="quality-issue red-bin numeric">{{ qualityIssue }}</div>
</template>
<style scoped lang="scss">
.quality-issue {
&.red-bin {
--warning-color: #ca0e0e;
border: 2px solid var(--warning-color);
padding: 0 0.5rem 0.1rem;
color: var(--warning-color);
}
}
</style>

View File

@@ -7,6 +7,7 @@ import {
getMean, getMean,
pickRandomElement, pickRandomElement,
popNElement, popNElement,
randomFloat,
shuffleArray, shuffleArray,
sumElements sumElements
} from '@/utils' } from '@/utils'
@@ -28,7 +29,7 @@ const hasQualityIssue = ({
) )
const multiplicator = getOverburdenMultiplicator(tasksInParallel) const multiplicator = getOverburdenMultiplicator(tasksInParallel)
const quality = Math.random() const quality = randomFloat(0, 1)
return quality > qualityProbability / multiplicator return quality > qualityProbability / multiplicator
} }
@@ -230,7 +231,7 @@ export const nextDay = (
state.meta.teamWorkExperience += 0.01 state.meta.teamWorkExperience += 0.01
if (strategy === 'problem-solving') { if (strategy === 'problem-solving') {
const hasTeamLearned = Math.random() > 0.25 const hasTeamLearned = randomFloat(0, 1) > 0.25
if (hasTeamLearned) { if (hasTeamLearned) {
state.meta.teamWorkExperience += 1.2 state.meta.teamWorkExperience += 1.2
} }

View File

@@ -1,9 +1,10 @@
import { birds } from '@/data/bird' import { birds } from '@/data/bird'
import { Feature } from '@/modules/feature/feature' import { Feature } from '@/modules/feature/feature'
import { randomInteger } from '@/utils'
export const features: Feature[] = birds.map((name) => ({ export const features: Feature[] = birds.map((name) => ({
name, name,
complexity: Math.floor(Math.random() * 5) + 1, complexity: randomInteger(1, 5),
leadTime: 0, leadTime: 0,
status: 'doing', status: 'doing',
step: Infinity, step: Infinity,

View File

@@ -1,3 +1,15 @@
import { Random } from 'random-js'
const random = new Random()
export const randomInteger = (min: number, max: number) => {
return random.integer(min, max)
}
export const randomFloat = (min: number, max: number) => {
return random.real(min, max)
}
export const getMean = (data: number[]) => export const getMean = (data: number[]) =>
Math.round(100 * (sumElements(data) / data.length)) / 100 Math.round(100 * (sumElements(data) / data.length)) / 100
@@ -11,7 +23,7 @@ export const shuffleArray = <T>(array: T[]) => {
randomIndex randomIndex
while (currentIndex !== 0) { while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex) randomIndex = randomInteger(0, currentIndex - 1)
currentIndex-- currentIndex--
;[array[currentIndex], array[randomIndex]] = [ ;[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[randomIndex],
@@ -37,7 +49,7 @@ export const popNElement = <T>(array: T[], numberOfElements: number) => {
} }
export const pickRandomIndex = <T>(array: T[]) => export const pickRandomIndex = <T>(array: T[]) =>
Math.floor(Math.random() * array.length) randomInteger(0, array.length - 1)
export const pickRandomElement = <T>(array: T[]) => export const pickRandomElement = <T>(array: T[]) =>
array[pickRandomIndex(array)] array[pickRandomIndex(array)]