mathiassandnes Claude Opus 4.5 commited on
Commit
ee3e76f
·
1 Parent(s): cbed23f

Clean up Strategy card layout and scale break-even chart

Browse files

- Revenue cards on same row, blended cost cards on same row
- Bold headers on all cards
- Break even and margin use primary blue instead of color-coded
- Remove colored top borders from cards
- Break even timeline scales with reuse cycles

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

Files changed (1) hide show
  1. app.py +44 -39
app.py CHANGED
@@ -376,66 +376,71 @@ if selected_view == "Strategy":
376
  )
377
 
378
  # ============================================================
379
- # TIER 1: Break Even + Operating Margin
380
  # ============================================================
381
- be_display = f"{breakeven_month}" if breakeven_month else "> 120"
382
 
383
- # Color-code margin card border
384
- if operating_margin_strat >= 30:
385
- margin_border = COLORS["positive"]
386
- elif operating_margin_strat >= 15:
387
- margin_border = COLORS["warning"]
388
- else:
389
- margin_border = COLORS["negative"]
 
 
 
 
 
 
 
 
 
 
 
390
 
391
- t1_left, t1_right = st.columns(2)
392
- with t1_left:
 
393
  st.markdown(f"""
394
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {COLORS['warning']};">
395
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.95rem;">Break Even Period</p>
396
- <p style="color: {COLORS['warning']}; margin: 0; font-size: 2.2rem; font-weight: 700;">{be_display} <span style="font-size: 1rem; font-weight: 400; color: {COLORS['text_subtle']};">months</span></p>
397
  </div>
398
  """, unsafe_allow_html=True)
399
- with t1_right:
400
  st.markdown(f"""
401
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {margin_border};">
402
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.95rem;">Operating Margin</p>
403
- <p style="color: {margin_border}; margin: 0; font-size: 2.2rem; font-weight: 700;">{operating_margin_strat:.0f}%</p>
404
  </div>
405
  """, unsafe_allow_html=True)
406
 
 
407
 
408
  # ============================================================
409
- # TIER 2: Revenue + Blended Cost (per product & portfolio)
410
  # ============================================================
411
- blended_cost_portfolio = portfolio_size * blended_cost_per_product
 
 
412
 
413
  t2_left, t2_right = st.columns(2)
414
  with t2_left:
415
  st.markdown(f"""
416
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {COLORS['primary']}; margin-bottom: 0.5rem;">
417
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.85rem;">Revenue / Product</p>
418
- <p style="color: {COLORS['primary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(revenue_per_product)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
419
- </div>
420
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {COLORS['primary']};">
421
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.85rem;">Portfolio Revenue</p>
422
- <p style="color: {COLORS['primary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(revenue_portfolio)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
423
  </div>
424
  """, unsafe_allow_html=True)
425
  with t2_right:
426
  st.markdown(f"""
427
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {COLORS['secondary']}; margin-bottom: 0.5rem;">
428
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.85rem;">Blended Cost / Product</p>
429
- <p style="color: {COLORS['secondary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(blended_cost_per_product)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
430
- </div>
431
- <div style="text-align: center; padding: 1.2rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']}; border-top: 3px solid {COLORS['secondary']};">
432
- <p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.85rem;">Portfolio Blended Cost</p>
433
- <p style="color: {COLORS['secondary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(blended_cost_portfolio)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
434
  </div>
435
  """, unsafe_allow_html=True)
436
 
437
- st.markdown(f'<p style="text-align: center; color: {COLORS["text_muted"]}; font-size: 0.95rem; margin-top: 1rem;">Assuming {lease_term_months}-month cycle length.</p>', unsafe_allow_html=True)
438
-
439
  st.markdown("---")
440
 
441
  # ============================================================
@@ -446,8 +451,8 @@ if selected_view == "Strategy":
446
  if monthly_lease == 0:
447
  st.warning("Set a lease price above 0 NOK to see the break-even timeline.")
448
  else:
449
- # Use first 36 months of cumulative data for this chart
450
- chart_months = min(36, len(breakeven_cumulative) - 1)
451
  be_months = list(range(chart_months + 1))
452
  be_values = breakeven_cumulative[:chart_months + 1]
453
 
@@ -532,7 +537,7 @@ if selected_view == "Strategy":
532
  fig_rc.add_annotation(
533
  x=0.5, y=max(rev_per, cost_per) * 1.18, xref="paper",
534
  text=f"Margin: {margin_pct:.0f}%", showarrow=False,
535
- font=dict(size=18, color=margin_border),
536
  )
537
  fig_rc.update_layout(
538
  yaxis_title="NOK", showlegend=False,
 
376
  )
377
 
378
  # ============================================================
379
+ # TIER 1: Revenue + Blended Cost (per product & portfolio)
380
  # ============================================================
381
+ blended_cost_portfolio = portfolio_size * blended_cost_per_product
382
 
383
+ # Row 1: Revenue
384
+ r1_left, r1_right = st.columns(2)
385
+ with r1_left:
386
+ st.markdown(f"""
387
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
388
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.85rem; font-weight: 700;">Revenue / Product</p>
389
+ <p style="color: {COLORS['primary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(revenue_per_product)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
390
+ </div>
391
+ """, unsafe_allow_html=True)
392
+ with r1_right:
393
+ st.markdown(f"""
394
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
395
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.85rem; font-weight: 700;">Portfolio Revenue</p>
396
+ <p style="color: {COLORS['primary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(revenue_portfolio)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
397
+ </div>
398
+ """, unsafe_allow_html=True)
399
+
400
+ st.markdown('<div style="margin-top: 0.75rem;"></div>', unsafe_allow_html=True)
401
 
402
+ # Row 2: Blended Cost
403
+ r2_left, r2_right = st.columns(2)
404
+ with r2_left:
405
  st.markdown(f"""
406
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
407
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.85rem; font-weight: 700;">Blended Cost / Product</p>
408
+ <p style="color: {COLORS['secondary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(blended_cost_per_product)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
409
  </div>
410
  """, unsafe_allow_html=True)
411
+ with r2_right:
412
  st.markdown(f"""
413
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
414
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.85rem; font-weight: 700;">Portfolio Blended Cost</p>
415
+ <p style="color: {COLORS['secondary']}; margin: 0; font-size: 1.8rem; font-weight: 600;">{sfmt(blended_cost_portfolio)} <span style="font-size: 0.85rem; font-weight: 400; color: {COLORS['text_subtle']};">NOK</span></p>
416
  </div>
417
  """, unsafe_allow_html=True)
418
 
419
+ st.markdown(f'<p style="text-align: center; color: {COLORS["text_muted"]}; font-size: 0.95rem; margin-top: 1rem;">Assuming {lease_term_months}-month cycle length.</p>', unsafe_allow_html=True)
420
 
421
  # ============================================================
422
+ # TIER 2: Break Even + Operating Margin
423
  # ============================================================
424
+ be_display = f"{breakeven_month}" if breakeven_month else "> 120"
425
+
426
+ st.markdown('<div style="margin-top: 0.75rem;"></div>', unsafe_allow_html=True)
427
 
428
  t2_left, t2_right = st.columns(2)
429
  with t2_left:
430
  st.markdown(f"""
431
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
432
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.95rem; font-weight: 700;">Break Even Period</p>
433
+ <p style="color: {COLORS['primary']}; margin: 0; font-size: 2.2rem; font-weight: 700;">{be_display} <span style="font-size: 1rem; font-weight: 400; color: {COLORS['text_subtle']};">months</span></p>
 
 
 
 
434
  </div>
435
  """, unsafe_allow_html=True)
436
  with t2_right:
437
  st.markdown(f"""
438
+ <div style="text-align: center; padding: 1.5rem; background: {COLORS['surface']}; border-radius: 10px; border: 1px solid {COLORS['border']};">
439
+ <p style="color: {COLORS['text_muted']}; margin: 0 0 0.3rem 0; font-size: 0.95rem; font-weight: 700;">Operating Margin</p>
440
+ <p style="color: {COLORS['primary']}; margin: 0; font-size: 2.2rem; font-weight: 700;">{operating_margin_strat:.0f}%</p>
 
 
 
 
441
  </div>
442
  """, unsafe_allow_html=True)
443
 
 
 
444
  st.markdown("---")
445
 
446
  # ============================================================
 
451
  if monthly_lease == 0:
452
  st.warning("Set a lease price above 0 NOK to see the break-even timeline.")
453
  else:
454
+ # Scale chart length with reuse cycles (each cycle = 12 months)
455
+ chart_months = min(reuse_cycles * lease_term_months, len(breakeven_cumulative) - 1)
456
  be_months = list(range(chart_months + 1))
457
  be_values = breakeven_cumulative[:chart_months + 1]
458
 
 
537
  fig_rc.add_annotation(
538
  x=0.5, y=max(rev_per, cost_per) * 1.18, xref="paper",
539
  text=f"Margin: {margin_pct:.0f}%", showarrow=False,
540
+ font=dict(size=18, color=COLORS["primary"]),
541
  )
542
  fig_rc.update_layout(
543
  yaxis_title="NOK", showlegend=False,