Merge branch 'main' of github.com:jcalixte/tps into main
This commit is contained in:
12
README.md
12
README.md
@@ -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.
|
|
||||||
|
|||||||
16
core.css
16
core.css
@@ -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
51
main.ts
@@ -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}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
7
pnpm-lock.yaml
generated
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
20
src/modules/feature/QualityIssue.vue
Normal file
20
src/modules/feature/QualityIssue.vue
Normal 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>
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
16
src/utils.ts
16
src/utils.ts
@@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user