mathiassandnes Claude Opus 4.5 commited on
Commit
1c54d69
·
1 Parent(s): 138265a

Redesign sidebar and add LiiS logo

Browse files

- Add view intro headers (audience + insights) to all 5 views
- Move view selection above product, change to dropdown
- Remove sidebar dividers
- Add LiiS logo top-right of main view
- Remove Repair/Scrap Rate cards, simplify to Grade A + Repair Cost row
- Repairability score 0-10 scale
- Fix Return Grade chart y-axis to 0-100
- Bump view description text size

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +42 -56
  2. liis branding/pasted-image-1.png +3 -0
app.py CHANGED
@@ -72,7 +72,11 @@ st.markdown(f"""
72
  """, unsafe_allow_html=True)
73
 
74
  # Header
75
- st.title("LiiS Circular Economy Simulator")
 
 
 
 
76
 
77
 
78
  # Load product configs
@@ -92,6 +96,15 @@ products = load_products()
92
  # ============================================================
93
  # SIDEBAR - Product Selection & View Selection
94
  # ============================================================
 
 
 
 
 
 
 
 
 
95
  st.sidebar.header("Product Selection")
96
  selected_product = st.sidebar.selectbox(
97
  "Choose a product",
@@ -103,16 +116,6 @@ config = products[selected_product]
103
  if "image_url" in products[selected_product].get("product", {}):
104
  st.sidebar.image(products[selected_product]["product"]["image_url"], use_column_width=True)
105
 
106
- st.sidebar.markdown("---")
107
- st.sidebar.header("View")
108
- selected_view = st.sidebar.radio(
109
- "Select stakeholder view",
110
- options=["Strategy", "Design", "Logistics", "Sourcing", "Market"],
111
- index=0,
112
- help="Each view shows relevant controls and insights"
113
- )
114
-
115
- st.sidebar.markdown("---")
116
 
117
  # ============================================================
118
  # TAB-SPECIFIC SIDEBAR CONTROLS
@@ -191,7 +194,7 @@ elif selected_view == "Design":
191
  if "design_damage_rate" not in st.session_state:
192
  st.session_state.design_damage_rate = 10
193
  if "design_repairability" not in st.session_state:
194
- st.session_state.design_repairability = 4
195
  if "design_spare_parts" not in st.session_state:
196
  st.session_state.design_spare_parts = 75
197
 
@@ -201,9 +204,9 @@ elif selected_view == "Design":
201
  help="% of products needing repair per cycle"
202
  )
203
  st.sidebar.slider(
204
- "Repairability Score", 1, 6,
205
  key="design_repairability",
206
- help="How easy to repair returned products (1=difficult, 6=very easy)"
207
  )
208
  st.sidebar.slider(
209
  "Spare Parts Availability", 0, 100,
@@ -213,7 +216,7 @@ elif selected_view == "Design":
213
 
214
  # Sync back to shared state so other views stay consistent
215
  _damage = st.session_state.design_damage_rate / 100
216
- _repair_factor = st.session_state.design_repairability / 6
217
  _spare_factor = st.session_state.design_spare_parts / 100
218
  st.session_state.refurb_needed_pct = _damage * _repair_factor * _spare_factor + (_damage - _damage * _repair_factor * _spare_factor)
219
 
@@ -363,7 +366,11 @@ def calculate_breakeven_months(portfolio, monthly_lease, mfg_cost_nok, lease_ter
363
  # VIEW: STRATEGY (C-Level)
364
  # ============================================================
365
  if selected_view == "Strategy":
366
- st.header("Portfolio Economics")
 
 
 
 
367
 
368
  # Get Strategy slider values
369
  portfolio_size = st.session_state.strategy_portfolio
@@ -651,11 +658,13 @@ if selected_view == "Strategy":
651
  # VIEW: DESIGN
652
  # ============================================================
653
  elif selected_view == "Design":
654
- st.header("Product Design for Circularity")
 
 
655
 
656
  # ── Design-specific calculations from 3 sliders ──
657
  d_damage_rate = st.session_state.design_damage_rate / 100
658
- d_repairability_factor = st.session_state.design_repairability / 6
659
  d_spare_parts_factor = st.session_state.design_spare_parts / 100
660
 
661
  d_grade_a_pct = 1 - d_damage_rate
@@ -701,46 +710,22 @@ elif selected_view == "Design":
701
 
702
  st.markdown('<div style="margin-top: 1.5rem;"></div>', unsafe_allow_html=True)
703
 
704
- # ── 4c. Supporting metrics (2x2 grid) ──
 
705
  sup_left, sup_right = st.columns(2)
706
 
707
  with sup_left:
708
- st.markdown(f'<p style="color: {COLORS["text_muted"]}; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.5rem;">Repair & Recovery</p>', unsafe_allow_html=True)
709
- # Repair Rate
710
- st.markdown(f"""
711
- <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
712
- border-radius: 10px; border: 1px solid {COLORS['border']}; margin-bottom: 0.75rem;">
713
- <p style="color: {COLORS['text_muted']}; margin: 0 0 0.5rem 0;
714
- font-size: 1.2rem; font-weight: 700;">Repair Rate</p>
715
- <p style="color: {COLORS['warning']}; margin: 0;
716
- font-size: 2.4rem; font-weight: 700;">{d_grade_b_pct:.1%}</p>
717
- </div>
718
- """, unsafe_allow_html=True)
719
- # Scrap Rate
720
  st.markdown(f"""
721
  <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
722
  border-radius: 10px; border: 1px solid {COLORS['border']};">
723
- <p style="color: {COLORS['text_muted']}; margin: 0 0 0.5rem 0;
724
- font-size: 1.2rem; font-weight: 700;">Scrap Rate</p>
725
- <p style="color: {COLORS['negative']}; margin: 0;
726
- font-size: 2.4rem; font-weight: 700;">{d_grade_c_pct:.1%}</p>
727
- </div>
728
- """, unsafe_allow_html=True)
729
-
730
- with sup_right:
731
- st.markdown(f'<p style="color: {COLORS["text_muted"]}; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.5rem;">Waste & Quality</p>', unsafe_allow_html=True)
732
- # Grade A Rate
733
- st.markdown(f"""
734
- <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
735
- border-radius: 10px; border: 1px solid {COLORS['border']}; margin-bottom: 0.75rem;">
736
  <p style="color: {COLORS['text_muted']}; margin: 0 0 0.5rem 0;
737
  font-size: 1.2rem; font-weight: 700;">Grade A Rate</p>
738
  <p style="color: {COLORS['positive']}; margin: 0;
739
  font-size: 2.4rem; font-weight: 700;">{d_grade_a_pct:.1%}</p>
740
  </div>
741
  """, unsafe_allow_html=True)
742
- # Repair Cost per Unit
743
- repair_cost_nok = d_repair_cost * 10 # EUR to NOK approx
744
  st.markdown(f"""
745
  <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
746
  border-radius: 10px; border: 1px solid {COLORS['border']};">
@@ -748,8 +733,6 @@ elif selected_view == "Design":
748
  font-size: 1.2rem; font-weight: 700;">Repair Cost per Unit</p>
749
  <p style="color: {COLORS['secondary']}; margin: 0;
750
  font-size: 2.4rem; font-weight: 700;">{sfmt(int(repair_cost_nok))} <span style="font-size: 1.1rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
751
- <p style="color: {COLORS['text_subtle']}; margin: 0.3rem 0 0 0;
752
- font-size: 0.8rem;">(Affected by repairability score)</p>
753
  </div>
754
  """, unsafe_allow_html=True)
755
 
@@ -771,11 +754,11 @@ elif selected_view == "Design":
771
  title="Return Grade Distribution", text="Percentage",
772
  )
773
  fig_grades.update_traces(texttemplate='%{text:.1f}%', textposition='outside')
774
- fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns")
775
  st.plotly_chart(fig_grades)
776
 
777
  st.info(f"**Design insight:** At {st.session_state.design_damage_rate}% damage rate with repairability score "
778
- f"{st.session_state.design_repairability}/6, "
779
  f"**{d_grade_b_pct:.1%}** of products can be repaired while **{d_grade_c_pct:.1%}** are scrapped.")
780
 
781
  st.markdown("---")
@@ -886,8 +869,9 @@ elif selected_view == "Design":
886
  # VIEW: LOGISTICS
887
  # ============================================================
888
  elif selected_view == "Logistics":
889
- st.header("Logistics: Operations & Warehouse Planning")
890
- st.markdown("*For logistics managers and warehouse operations*")
 
891
 
892
  # Return assumptions summary
893
  st.subheader("Return Flow Assumptions")
@@ -1003,8 +987,9 @@ elif selected_view == "Logistics":
1003
  # VIEW: SOURCING
1004
  # ============================================================
1005
  elif selected_view == "Sourcing":
1006
- st.header("Sourcing: Material Costs & Supply")
1007
- st.markdown("*For procurement, supply chain, and finance*")
 
1008
 
1009
  has_materials = "materials" in config and "bom" in config.get("materials", {})
1010
  has_labor = "labor" in config
@@ -1133,8 +1118,9 @@ elif selected_view == "Sourcing":
1133
  # VIEW: MARKET
1134
  # ============================================================
1135
  elif selected_view == "Market":
1136
- st.header("Market: Customer Value & Positioning")
1137
- st.markdown("*For marketing, sales, and business development*")
 
1138
 
1139
  # Customer Value Proposition
1140
  st.subheader("Customer Value Proposition")
 
72
  """, unsafe_allow_html=True)
73
 
74
  # Header
75
+ header_left, header_right = st.columns([3, 1])
76
+ with header_left:
77
+ st.title("LiiS Circular Economy Simulator")
78
+ with header_right:
79
+ st.image("liis branding/pasted-image-1.png", width=120)
80
 
81
 
82
  # Load product configs
 
96
  # ============================================================
97
  # SIDEBAR - Product Selection & View Selection
98
  # ============================================================
99
+ st.sidebar.header("View")
100
+ selected_view = st.sidebar.selectbox(
101
+ "Select stakeholder view",
102
+ options=["Strategy", "Design", "Logistics", "Sourcing", "Market"],
103
+ index=0,
104
+ help="Each view shows relevant controls and insights"
105
+ )
106
+
107
+
108
  st.sidebar.header("Product Selection")
109
  selected_product = st.sidebar.selectbox(
110
  "Choose a product",
 
116
  if "image_url" in products[selected_product].get("product", {}):
117
  st.sidebar.image(products[selected_product]["product"]["image_url"], use_column_width=True)
118
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  # ============================================================
121
  # TAB-SPECIFIC SIDEBAR CONTROLS
 
194
  if "design_damage_rate" not in st.session_state:
195
  st.session_state.design_damage_rate = 10
196
  if "design_repairability" not in st.session_state:
197
+ st.session_state.design_repairability = 7
198
  if "design_spare_parts" not in st.session_state:
199
  st.session_state.design_spare_parts = 75
200
 
 
204
  help="% of products needing repair per cycle"
205
  )
206
  st.sidebar.slider(
207
+ "Repairability Score", 0, 10,
208
  key="design_repairability",
209
+ help="0 = everything scrapped, 10 = everything can be repaired"
210
  )
211
  st.sidebar.slider(
212
  "Spare Parts Availability", 0, 100,
 
216
 
217
  # Sync back to shared state so other views stay consistent
218
  _damage = st.session_state.design_damage_rate / 100
219
+ _repair_factor = st.session_state.design_repairability / 10
220
  _spare_factor = st.session_state.design_spare_parts / 100
221
  st.session_state.refurb_needed_pct = _damage * _repair_factor * _spare_factor + (_damage - _damage * _repair_factor * _spare_factor)
222
 
 
366
  # VIEW: STRATEGY (C-Level)
367
  # ============================================================
368
  if selected_view == "Strategy":
369
+ st.header("Strategy")
370
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0; font-size: 1.15rem;">For C-level executives and business owners</p>', unsafe_allow_html=True)
371
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0 0 1.5rem 0; font-size: 1.15rem;">Portfolio economics, break-even timeline, and operating margin</p>', unsafe_allow_html=True)
372
+
373
+ st.subheader("Portfolio Economics")
374
 
375
  # Get Strategy slider values
376
  portfolio_size = st.session_state.strategy_portfolio
 
658
  # VIEW: DESIGN
659
  # ============================================================
660
  elif selected_view == "Design":
661
+ st.header("Design")
662
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0; font-size: 1.15rem;">For product designers and sustainability officers</p>', unsafe_allow_html=True)
663
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0 0 1.5rem 0; font-size: 1.15rem;">Recovery rate, repairability impact, and component failure analysis</p>', unsafe_allow_html=True)
664
 
665
  # ── Design-specific calculations from 3 sliders ──
666
  d_damage_rate = st.session_state.design_damage_rate / 100
667
+ d_repairability_factor = st.session_state.design_repairability / 10
668
  d_spare_parts_factor = st.session_state.design_spare_parts / 100
669
 
670
  d_grade_a_pct = 1 - d_damage_rate
 
710
 
711
  st.markdown('<div style="margin-top: 1.5rem;"></div>', unsafe_allow_html=True)
712
 
713
+ # ── 4c. Supporting metrics (row) ──
714
+ repair_cost_nok = d_repair_cost * 10 # EUR to NOK approx
715
  sup_left, sup_right = st.columns(2)
716
 
717
  with sup_left:
 
 
 
 
 
 
 
 
 
 
 
 
718
  st.markdown(f"""
719
  <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
720
  border-radius: 10px; border: 1px solid {COLORS['border']};">
 
 
 
 
 
 
 
 
 
 
 
 
 
721
  <p style="color: {COLORS['text_muted']}; margin: 0 0 0.5rem 0;
722
  font-size: 1.2rem; font-weight: 700;">Grade A Rate</p>
723
  <p style="color: {COLORS['positive']}; margin: 0;
724
  font-size: 2.4rem; font-weight: 700;">{d_grade_a_pct:.1%}</p>
725
  </div>
726
  """, unsafe_allow_html=True)
727
+
728
+ with sup_right:
729
  st.markdown(f"""
730
  <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']};
731
  border-radius: 10px; border: 1px solid {COLORS['border']};">
 
733
  font-size: 1.2rem; font-weight: 700;">Repair Cost per Unit</p>
734
  <p style="color: {COLORS['secondary']}; margin: 0;
735
  font-size: 2.4rem; font-weight: 700;">{sfmt(int(repair_cost_nok))} <span style="font-size: 1.1rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
 
 
736
  </div>
737
  """, unsafe_allow_html=True)
738
 
 
754
  title="Return Grade Distribution", text="Percentage",
755
  )
756
  fig_grades.update_traces(texttemplate='%{text:.1f}%', textposition='outside')
757
+ fig_grades.update_layout(showlegend=False, yaxis_title="% of Returns", yaxis=dict(range=[0, 100]))
758
  st.plotly_chart(fig_grades)
759
 
760
  st.info(f"**Design insight:** At {st.session_state.design_damage_rate}% damage rate with repairability score "
761
+ f"{st.session_state.design_repairability}/10, "
762
  f"**{d_grade_b_pct:.1%}** of products can be repaired while **{d_grade_c_pct:.1%}** are scrapped.")
763
 
764
  st.markdown("---")
 
869
  # VIEW: LOGISTICS
870
  # ============================================================
871
  elif selected_view == "Logistics":
872
+ st.header("Logistics")
873
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0; font-size: 1.15rem;">For logistics managers and warehouse operations</p>', unsafe_allow_html=True)
874
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0 0 1.5rem 0; font-size: 1.15rem;">Return flow, grade distribution, cost per cycle, and fleet tracking</p>', unsafe_allow_html=True)
875
 
876
  # Return assumptions summary
877
  st.subheader("Return Flow Assumptions")
 
987
  # VIEW: SOURCING
988
  # ============================================================
989
  elif selected_view == "Sourcing":
990
+ st.header("Sourcing")
991
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0; font-size: 1.15rem;">For procurement, supply chain, and finance</p>', unsafe_allow_html=True)
992
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0 0 1.5rem 0; font-size: 1.15rem;">Bill of materials, recycled content impact, and manufacturing cost breakdown</p>', unsafe_allow_html=True)
993
 
994
  has_materials = "materials" in config and "bom" in config.get("materials", {})
995
  has_labor = "labor" in config
 
1118
  # VIEW: MARKET
1119
  # ============================================================
1120
  elif selected_view == "Market":
1121
+ st.header("Market")
1122
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0; font-size: 1.15rem;">For marketing, sales, and business development</p>', unsafe_allow_html=True)
1123
+ st.markdown(f'<p style="color: {COLORS["text_muted"]}; margin: 0 0 1.5rem 0; font-size: 1.15rem;">Customer value proposition, pricing analysis, and environmental impact story</p>', unsafe_allow_html=True)
1124
 
1125
  # Customer Value Proposition
1126
  st.subheader("Customer Value Proposition")
liis branding/pasted-image-1.png ADDED

Git LFS Details

  • SHA256: 49d76b0cd844cb4569732cad9168dd3d5903d3e3b45e7b888833f3088b895cd3
  • Pointer size: 130 Bytes
  • Size of remote file: 76.4 kB