ЁЯПл The SchoolтА║ЁЯЫдя╕П Platform EngineeringтА║ЁЯдЭ рдзрдбрд╛ 02 тАФ Platform as a product: рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдпрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рдЧреНрд░рд╛рд╣рдХ рдЖрд╣реЗрдд
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯдЭ рдзрдбрд╛ 02 тАФ Platform as a product: рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдпрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рдЧреНрд░рд╛рд╣рдХ рдЖрд╣реЗрдд

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 02 ┬╖ рдорд╛рдЧреЗ: lesson-01-why-platforms ┬╖ рдкреБрдвреЗ: lesson-03-golden-paths


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбрд╛ 01, рдЖрдгрд┐ platform рд╡рд╛рдкрд░рд▓рд╛ рдЬрд╛рдИрд▓ рдХреА рджреБрд░реНрд▓рдХреНрд╖рд┐рдд рд╣реЛрдИрд▓ рд╣реЗ рдард░рд╡рдгрд╛рд░реА рд╡рд┐рдЪрд╛рд░рд╕рд░рдгреА: рддреНрдпрд╛рд▓рд╛ рдПрдХ product рдорд╛рдирд╛. Teams рдпрд╛ users рдЖрд╣реЗрдд. рддреБрдореНрд╣реА рддреНрдпрд╛рдВрдЪреА рдореБрд▓рд╛рдЦрдд рдШреЗрддрд╛, рддреНрдпрд╛рдВрдЪреЗ рддреНрд░рд╛рд╕ рдХреНрд░рдорд╛рдиреЗ рд▓рд╛рд╡рддрд╛, рд╕рд░реНрд╡рд╛рдд рдореЛрдареНрдпрд╛ рддреНрд░рд╛рд╕рд╛рдкрд╛рд╕реВрди рдЦрд╛рд▓реА рдПрдХ roadmap рдмрдирд╡рддрд╛, рдЖрдгрд┐ adoption рдореЛрдЬрддрд╛ тАФ platform рд╕реНрд╡рддрдГ рдирд┐рд╡рдбрдгрд╛рд▒реНрдпрд╛ teams тАФ рд╕рдХреНрддреА рдХрд░реВрди рддреЛ рд▓рд╛рджрдгреНрдпрд╛рдРрд╡рдЬреА. idp/office.py рдордзрд▓реЗ rank_pains() рдЖрдгрд┐ adoption(), рдЖрдгрд┐ idp/demo.py рдордзрд▓реЗ product().

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рджреАрдкрд┐рдХрд╛ рд╕реБрд╡рд┐рдзрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдп рдЪрд╛рд▓рд╡рддреЗ. рдпрд╛ рдорд╣рд┐рдиреНрдпрд╛рдд рддрд┐рдЪреНрдпрд╛рдХрдбреЗ рдПрдХрд╛рдЪ рдирд╡реНрдпрд╛ рд╕рдВрдЪрд╛рдЪреЗ рдмрдЬреЗрдЯ рдЖрд╣реЗ.

рддрд┐рд▓рд╛ рд╕рдЧрд│реНрдпрд╛рдд рдЬрд╛рд╕реНрдд рдордЬреЗрджрд╛рд░ рд╡рд╛рдЯрдгрд╛рд░реА рдЧреЛрд╖реНрдЯ рддреА рдмрдирд╡реВ рд╢рдХрд▓реА рдЕрд╕рддреА: рдПрдХ рдЪрдХрдЪрдХреАрдд smart-board рд╕рдВрдЪ. ЁЯЦея╕П рддреНрдпрд╛рдРрд╡рдЬреА рддреА рдПрдХ рд╡рд╣реА рдШреЗрдКрди рд╢рд╛рд│реЗрдд рдлрд┐рд░рддреЗ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рднрд╛рдЧрд╛рд▓рд╛ рд╡рд┐рдЪрд╛рд░рддреЗ: "рддреБрдордЪрд╛ рд╡реЗрд│ рдХрд╢рд╛рдд рд╡рд╛рдпрд╛ рдЬрд╛рддреЛ? рдЖрдард╡рдбреНрдпрд╛рд▓рд╛ рдХрд┐рддреА рддрд╛рд╕?"

рддреА рд╕рдЧрд│реЗ рдмреЗрд░реАрдЬ рдХрд░рддреЗ. database рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рдгреНрдпрд╛рдд рд╕рдВрдкреВрд░реНрдг рд╢рд╛рд│реЗрдЪреЗ рдЖрдард╡рдбреНрдпрд╛рд▓рд╛ 15 рддрд╛рд╕ рдЬрд╛рддрд╛рдд. рддреЛрдЪ рдкрд╣рд┐рд▓рд╛ рд╕рдВрдЪ. smart board рдирд╛рд╣реА.

рдордЧ рддреА рдХреЛрдгрд╛рд▓рд╛рд╣реА рддреЛ рд╡рд╛рдкрд░рд╛рдпрдЪрд╛ рдЖрджреЗрд╢ рджреЗрдд рдирд╛рд╣реА. рддреА рддреЛ рдЗрддрдХрд╛ рдЪрд╛рдВрдЧрд▓рд╛ рдмрдирд╡рддреЗ рдХреА рд╡рд┐рднрд╛рдЧрд╛рдВрдирд╛ рддреЛ рд╣рд╡рд╛рд╕рд╛ рд╡рд╛рдЯрддреЛ. рдХрд┐рддреА рдЬрдг рддреЛ рдирд┐рд╡рдбрддрд╛рдд рддреЗ рддреА рдореЛрдЬрддреЗ: рдкрд╣рд┐рд▓реНрдпрд╛ рдорд╣рд┐рдиреНрдпрд╛рдд 5 рдкреИрдХреА 1, рддрд┐рд╕рд▒реНрдпрд╛ рдорд╣рд┐рдиреНрдпрд╛рдд 5 рдкреИрдХреА 3, рд╕рд╣рд╛рд╡реНрдпрд╛ рдорд╣рд┐рдиреНрдпрд╛рдд 5 рдкреИрдХреА 4. рдЬреЗрд╡реНрд╣рд╛ рдХрд▓рд╛ рд╡рд┐рднрд╛рдЧ "рдирдХреЛ, рдзрдиреНрдпрд╡рд╛рдж" рдореНрд╣рдгрддреЛ, рддреЗрд╡реНрд╣рд╛ рддреА рдХрд╛ рддреЗ рд╡рд┐рдЪрд╛рд░рддреЗ тАФ рддреЗ рдЙрддреНрддрд░рдЪ рддрд┐рдЪреЗ рдкреБрдврдЪреЗ рдХрд╛рдо рдЖрд╣реЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    i["ЁЯЧгя╕П interviews<br/>5 departments"] --> r["ЁЯУК rank by hours lost<br/>database 15 h/week<br/>CI YAML 7 ┬╖ owner lookup 5"]
    r --> m["ЁЯЧ║я╕П roadmap<br/>biggest pain first"]
    m --> b["ЁЯз░ build the kit"]
    b --> a["ЁЯУИ adoption<br/>20% тЖТ 60% тЖТ 80%"]
    a -->|"teams that leave: ask why"| i

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l02

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг platforms рдЕрдкрдпрд╢реА рд╣реЛрдгреНрдпрд╛рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдорд╛рд░реНрдЧ рд╢рд╛рдВрдд рдЕрд╕рддреЛ: рддреЗ рдмрдирд╡рд▓реЗ рдЬрд╛рддрд╛рдд, рдЬрд╛рд╣реАрд░ рд╣реЛрддрд╛рдд, рдЖрдгрд┐ рд╡рд╛рдкрд░рд▓реЗ рдЬрд╛рдд рдирд╛рд╣реАрдд. Teams рдЖрдкрд▓реНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ scripts рдареЗрд╡рддрд╛рдд рдХрд╛рд░рдг platform рдиреЗ рддреНрдпрд╛рдВрдЪреА рд╕рдорд╕реНрдпрд╛ рд╕реЛрдбрд╡рд▓реА рдирд╛рд╣реА, рдХрд┐рдВрд╡рд╛ рдЕрд╢рд╛ рдкрджреНрдзрддреАрдиреЗ рд╕реЛрдбрд╡рд▓реА рдХреА рддреНрдпрд╛рдВрдЪрд╛ рджрд┐рд╡рд╕ рдЬрд╛рд╕реНрдд рд╡рд╛рдИрдЯ рдЧреЗрд▓рд╛. рдЖрдзреА users рд╢реА рдмреЛрд▓рдгреЗ рдЪреБрдХреАрдЪреА рдЧреЛрд╖реНрдЯ рдмрдирд╡рдгреНрдпрд╛рдкреЗрдХреНрд╖рд╛ рд╕реНрд╡рд╕реНрдд рдЖрд╣реЗ. рдЖрдгрд┐ рдирд┐рд╡рдбрд▓реЗрд▓реЗ adoption рдореЛрдЬрд▓реНрдпрд╛рдиреЗ рдорд╣рд┐рдиреНрдпрд╛рдорд╛рдЧреВрди рдорд╣рд┐рдирд╛ рдХрд│рддреЗ рдХреА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪрд╛ рдЦрд░реЛрдЦрд░ рдЙрдкрдпреЛрдЧ рд╣реЛрддреЛ рдЖрд╣реЗ рдХрд╛.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

idp/office.py рдордзрд▓реЗ rank_pains(interviews) {team: {pain: hours lost per week}} рдШреЗрддреЗ, рдкреНрд░рддреНрдпреЗрдХ рддреНрд░рд╛рд╕рд╛рдЪреЗ рд╕рдЧрд│реНрдпрд╛ teams рдордзрд▓реЗ рддрд╛рд╕ рдмреЗрд░реАрдЬ рдХрд░рддреЗ, teams рдореЛрдЬрддреЗ, рдЖрдгрд┐ рдЖрдзреА рддрд╛рд╕рд╛рдВрдиреБрд╕рд╛рд░, рдордЧ teams рдЪреНрдпрд╛ рд╕рдВрдЦреНрдпреЗрдиреБрд╕рд╛рд░, рдордЧ рдирд╛рд╡рд╛рдиреБрд╕рд╛рд░ рдХреНрд░рдо рд▓рд╛рд╡рддреЗ (рдореНрд╣рдгрдЬреЗ рдХреНрд░рдо рд╕реНрдерд┐рд░ рд░рд╛рд╣рддреЛ). adoption(teams, on_path) рдореНрд╣рдгрдЬреЗ paved path рдирд┐рд╡рдбрд▓реЗрд▓реНрдпрд╛ teams рдЪреА рдЯрдХреНрдХреЗрд╡рд╛рд░реА. idp/demo.py рдордзрд▓реЗ product() рдкрд╛рдЪ рд╡рд┐рднрд╛рдЧрд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рдЦрддреА рдШреЗрддреЗ рдЖрдгрд┐ roadmap рдЖрдгрд┐ рдорд╣рд┐рдиреЗ 1, 3 рдЖрдгрд┐ 6 рдордзрд▓реЗ adoption рдЫрд╛рдкрддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 idp/demo.py product
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from office import rank_pains, adoption
interviews = {"science": {"waiting for a database": 6, "writing CI YAML": 3},
              "maths":   {"waiting for a database": 4, "finding a service's owner": 1},
              "library": {"writing CI YAML": 4},
              "sports":  {"finding a service's owner": 1},
              "arts":    {"finding a service's owner": 2}}
print("top pain:", rank_pains(interviews)[0])
interviews["music"] = {"writing CI YAML": 5}          # one more interview
for pain, hours, teams in rank_pains(interviews): print(f"{pain:<26} {hours:>2} h/week ┬╖ {teams} teams")
teams = list(interviews)
print("adoption with 3 of 6 on the path:", adoption(teams, {"science", "maths", "music"}), "%")
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

product рд╣реЗ рдЫрд╛рдкрддреЗ:

тФАтФА Dipika (the facilities office) interviews 5 departments: hours lost per week, per pain
   1. waiting for a database     15 h/week across 3 teams
   2. writing CI YAML             7 h/week across 2 teams
   3. finding a service's owner   5 h/week across 4 teams
   4. preview environments        5 h/week across 2 teams
   5. rotating secrets            3 h/week across 2 teams
   month 1:  20% of departments chose the paved path
   month 3:  60% of departments chose the paved path
   month 6:  80% of departments chose the paved path

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

top pain: ('waiting for a database', 10, 2)
writing CI YAML            12 h/week ┬╖ 3 teams
waiting for a database     10 h/week ┬╖ 2 teams
finding a service's owner   4 h/week ┬╖ 3 teams
adoption with 3 of 6 on the path: 50 %

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдЖрдгрдЦреА рдПрдХрд╛ рдореБрд▓рд╛рдЦрддреАрдиреЗ roadmap рдЪреЗ рд╢рд┐рдЦрд░ рдмрджрд▓рд▓реЗ: рд╕рдВрдЧреАрдд рд╡рд┐рднрд╛рдЧрд╛рдореБрд│реЗ CI YAML (12 h/week) database (10 h/week) рдЪреНрдпрд╛ рдкреБрдвреЗ рдЬрд╛рддреЗ. Roadmap рд╣рд╛ рдРрдХрдгреНрдпрд╛рдЪрд╛ рдкрд░рд┐рдгрд╛рдо рдЖрд╣реЗ, рдЖрдгрд┐ рддреБрдореНрд╣реА рдЬрд╛рд╕реНрдд рд▓реЛрдХрд╛рдВрдЪреЗ рдРрдХрд▓реЗ рдХреА рддреЛ рдмрджрд▓рддреЛ. "finding a service's owner" рдХрдбреЗрд╣реА рд▓рдХреНрд╖ рджреНрдпрд╛: рддреЛ 4 teams рдирд╛ рддреНрд░рд╛рд╕ рджреЗрддреЛ рдкрдг рдПрдХреВрдг рдлрдХреНрдд 5 рддрд╛рд╕ тАФ рдЕрдиреЗрдХрд╛рдВрдирд╛ рдереЛрдбрд╛рд╕рд╛ рдЬрд╛рдгрд╡рдгрд╛рд░рд╛ рддреНрд░рд╛рд╕. рдЖрдзреА рддрд╛рд╕рд╛рдВрдиреБрд╕рд╛рд░ рдЖрдгрд┐ рдордЧ teams рдЪреНрдпрд╛ рд╕рдВрдЦреНрдпреЗрдиреБрд╕рд╛рд░ рдХреНрд░рдо рд▓рд╛рд╡рдгреЗ рд╣рд╛ рдПрдХ рдирд┐рд░реНрдгрдп рдЖрд╣реЗ; рдЦрд░реЗ рдХрд╛рд░реНрдпрд╛рд▓рдп рддреНрдпрд╛рд▓рд╛ рд╡реЗрдЧрд│реЗ рд╡рдЬрди рджреЗрдК рд╢рдХрддреЗ, рдЖрдгрд┐ рддреЗ рдХрд╕рд╛ рдХреНрд░рдо рд▓рд╛рд╡рддреЗ рд╣реЗ рддреНрдпрд╛рдиреЗ рд╕рд╛рдВрдЧрд╛рдпрд▓рд╛ рд╣рд╡реЗ.

ЁЯУП рд╢рд┐рдХрд╡рдгреНрдпрд╛рдЪреЗ рдореЙрдбреЗрд▓: рдЦрд▒реНрдпрд╛ рдореБрд▓рд╛рдЦрддреА рдЧреЛрд╖реНрдЯреА рджреЗрддрд╛рдд, рд╕реНрд╡рдЪреНрдЫ рдЖрдХрдбреЗ рдирд╛рд╣реАрдд. рдЕрдВрджрд╛рдЬреЗ "рд╡рд╛рдпрд╛ рдЧреЗрд▓реЗрд▓реЗ рддрд╛рд╕" рдвреЛрдмрд│ рдЕрд╕рддрд╛рдд; рддреНрдпрд╛рдВрдЪрд╛ рд╡рд╛рдкрд░ рдХрд╛рдорд╛рдЪрд╛ рдХреНрд░рдо рд▓рд╛рд╡рд╛рдпрд▓рд╛ рдХрд░рд╛, рдЕрдЪреВрдХ рд╕рддреНрдп рдореНрд╣рдгреВрди рдирд╛рд╣реА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

Roadmap рдХрдВрдкрдиреАрдЪреНрдпрд╛ рдЖрдд рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ рдареЗрд╡рд╛, рдореНрд╣рдгрдЬреЗ рдкреНрд░рддреНрдпреЗрдХ team рд▓рд╛ рдХрд╛рдп рдпреЗрдд рдЖрд╣реЗ рдЖрдгрд┐ рдХрд╛ рддреЗ рджрд┐рд╕реЗрд▓. рдПрдХ рдЫреЛрдЯрд╛, рдкреНрд░рд╛рдорд╛рдгрд┐рдХ format тАФ рдкреНрд░рддреНрдпреЗрдХ item рд╕рд╛рдареА рдПрдХ issue, рдкреБрд░рд╛рд╡реНрдпрд╛рд╕рд╣ label рд▓рд╛рд╡рд▓реЗрд▓рд╛:

# on a real account, with the GitHub CLI, in the platform team's repo
gh issue create --title "Self-service Postgres (roadmap #1)" \
  --label roadmap --label "pain:15h-week" \
  --body "Interviews: science 6 h, sports 5 h, maths 4 h per week waiting for a database."
gh issue list --label roadmap

рдЕрдиреЗрдХ platform teams рджрд░ рддрд┐рдорд╛рд╣реАрд▓рд╛ рдПрдХ рдЫреЛрдЯреЗ survey рд╕реБрджреНрдзрд╛ рдШреЗрддрд╛рдд (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде: "рдорд╛рдЧрдЪреНрдпрд╛ рдорд╣рд┐рдиреНрдпрд╛рдд рдПрдХ рдмрджрд▓ ship рдХрд░рдгреЗ рдХрд┐рддреА рд╕реЛрдкреЗ рд╣реЛрддреЗ?" 1тАУ5 рдЪреНрдпрд╛ рдкреНрд░рдорд╛рдгрд╛рд╡рд░, рдЖрдгрд┐ рдПрдХ рдореЛрдХрд│рд╛ рдкреНрд░рд╢реНрди). рдзрдбрд╛ 11 рдпрд╛рдХрдбреЗ рдкрд░рдд рдпреЗрддреЛ: systems рдордзрд▓реЗ рдЖрдХрдбреЗ рдЖрдгрд┐ рд▓реЛрдХрд╛рдВрдЪреА рдЙрддреНрддрд░реЗ.

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: platform рдЪреА рд╕реНрдкрд░реНрдзрд╛ "рдореА рд╕реНрд╡рддрдГрдЪ рдХрд░реВрди рдЯрд╛рдХреЗрди" рд╢реА рдЕрд╕рддреЗ. рддреЛ рд╕реНрдкрд╖реНрдЯрдкрдгреЗ рд╕реЛрдкрд╛ рдорд╛рд░реНрдЧ рдЕрд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛рдЪ рдЬрд┐рдВрдХрддреЛ тАФ рдЖрдгрд┐ "рд╕реЛрдкрд╛" рдореНрд╣рдгрдЬреЗ рдХрд╛рдп рд╣реЗ рдлрдХреНрдд рд╡рд┐рдЪрд╛рд░реВрдирдЪ рдХрд│рддреЗ.

тПня╕П рдкреБрдвреЗ

рд╕рд░реНрд╡рд╛рдд рдореЛрдард╛ рддреНрд░рд╛рд╕ рдЖрд╣реЗ "рдкреНрд░рддреНрдпреЗрдХ team рдХреЛрд▒реНрдпрд╛ рдкрд╛рдирд╛рдкрд╛рд╕реВрди рд╕реБрд░реБрд╡рд╛рдд рдХрд░рддреЗ". рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪрд╛ рдкрд╣рд┐рд▓рд╛ рд╕рдВрдЪ: рдПрдХ template рдЬреЛ рдирд╡реНрдпрд╛ service рд▓рд╛ рдПрдХрд╛ рдкрд╛рдпрд░реАрдд рдЪрд╛рд▓рдгрд╛рд░рд╛ рд╕рд╛рдВрдЧрд╛рдбрд╛ рджреЗрддреЛ.

git checkout lesson-03-golden-paths

ЁЯдЭ Lesson 02 тАФ Platform as a product: teachers are the office's customers

ЁЯУН You are here: Lesson 02 of 12 ┬╖ Previous: lesson-01-why-platforms ┬╖ Next: lesson-03-golden-paths


ЁЯУж What's in this branch

Lesson 01, plus the mindset that decides whether a platform is used or ignored: treat it as a product. The teams are users. You interview them, rank their pains, build a roadmap from the biggest pain down, and measure adoption тАФ teams that choose the platform тАФ instead of forcing it with a mandate. rank_pains() and adoption() in idp/office.py, product() in idp/demo.py.

ЁЯзТ Explain like I'm 5

Dipika runs the facilities office. She has a budget for one new kit this month.

She could build the thing she finds most fun: a shiny smart-board kit. ЁЯЦея╕П Instead she walks around the school with a notebook and asks each department: "What wastes your time? How many hours a week?"

She adds it all up. Waiting for a database costs 15 hours a week across the school. That is the first kit. Not the smart board.

Then she does not order anyone to use it. She makes it so good that departments want it. She counts how many choose it: 1 of 5 in month 1, 3 of 5 in month 3, 4 of 5 in month 6. When the arts department says "no thanks", she asks why тАФ that answer is her next job.

ЁЯЧ║я╕П Diagram

flowchart LR
    i["ЁЯЧгя╕П interviews<br/>5 departments"] --> r["ЁЯУК rank by hours lost<br/>database 15 h/week<br/>CI YAML 7 ┬╖ owner lookup 5"]
    r --> m["ЁЯЧ║я╕П roadmap<br/>biggest pain first"]
    m --> b["ЁЯз░ build the kit"]
    b --> a["ЁЯУИ adoption<br/>20% тЖТ 60% тЖТ 80%"]
    a -->|"teams that leave: ask why"| i

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l02

тЭУ What

ЁЯдФ Why

Because the most common way platforms fail is quietly: they are built, announced, and not used. Teams keep their own scripts because the platform did not solve their problem, or solved it in a way that made their day worse. Talking to users first is cheaper than building the wrong thing. And measuring chosen adoption tells you, month by month, whether the office is actually helping.

ЁЯФз How (in this repo)

rank_pains(interviews) in idp/office.py takes {team: {pain: hours lost per week}}, adds the hours per pain across teams, counts the teams, and sorts by hours, then by team count, then by name (so the order is stable). adoption(teams, on_path) is the percentage of teams that chose the paved path. product() in idp/demo.py interviews five departments and prints the roadmap and adoption at months 1, 3 and 6.

ЁЯзк Try it

python3 idp/demo.py product
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from office import rank_pains, adoption
interviews = {"science": {"waiting for a database": 6, "writing CI YAML": 3},
              "maths":   {"waiting for a database": 4, "finding a service's owner": 1},
              "library": {"writing CI YAML": 4},
              "sports":  {"finding a service's owner": 1},
              "arts":    {"finding a service's owner": 2}}
print("top pain:", rank_pains(interviews)[0])
interviews["music"] = {"writing CI YAML": 5}          # one more interview
for pain, hours, teams in rank_pains(interviews): print(f"{pain:<26} {hours:>2} h/week ┬╖ {teams} teams")
teams = list(interviews)
print("adoption with 3 of 6 on the path:", adoption(teams, {"science", "maths", "music"}), "%")
EOF

тЬЕ Verify тАФ what you should see

product prints:

тФАтФА Dipika (the facilities office) interviews 5 departments: hours lost per week, per pain
   1. waiting for a database     15 h/week across 3 teams
   2. writing CI YAML             7 h/week across 2 teams
   3. finding a service's owner   5 h/week across 4 teams
   4. preview environments        5 h/week across 2 teams
   5. rotating secrets            3 h/week across 2 teams
   month 1:  20% of departments chose the paved path
   month 3:  60% of departments chose the paved path
   month 6:  80% of departments chose the paved path

Your snippet prints:

top pain: ('waiting for a database', 10, 2)
writing CI YAML            12 h/week ┬╖ 3 teams
waiting for a database     10 h/week ┬╖ 2 teams
finding a service's owner   4 h/week ┬╖ 3 teams
adoption with 3 of 6 on the path: 50 %

ЁЯПБ What you just proved

One more interview changed the top of the roadmap: with the music department, CI YAML (12 h/week) passes the database (10 h/week). The roadmap is a result of listening, and it moves when you listen to more people. Note also "finding a service's owner": it hurts 4 teams but only 5 hours in total тАФ a pain many people feel a little. Ranking by hours first and team count second is one choice; a real office might weigh it differently, and should say how it ranks.

ЁЯУП Teaching model: real interviews give stories, not clean numbers. Estimated "hours lost" are rough; use them to order the work, not as exact truth.

тЪая╕П Common mistakes

ЁЯПн In production

Keep the roadmap public inside the company, so every team sees what is coming and why. A minimal, honest format тАФ one issue per item, labelled with the evidence:

# on a real account, with the GitHub CLI, in the platform team's repo
gh issue create --title "Self-service Postgres (roadmap #1)" \
  --label roadmap --label "pain:15h-week" \
  --body "Interviews: science 6 h, sports 5 h, maths 4 h per week waiting for a database."
gh issue list --label roadmap

Many platform teams also run a short survey every quarter (for example: "How easy was it to ship a change last month?" on a 1тАУ5 scale, plus one free-text question). Lesson 11 comes back to this: numbers from systems and answers from people.

ЁЯПн Why this matters in production: the platform competes with "I'll just do it myself". It wins only when it is clearly the easier path тАФ and you only learn what "easier" means by asking.

тПня╕П Next

The top pain is "every team starts from a blank page". The office's first kit: a template that gives a new service a working skeleton in one step.

git checkout lesson-03-golden-paths
тЖР Previouswhy platformsNext тЖТgolden paths

This page is the lesson's README from the lesson-02-platform-as-product branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.