Spaces:
Running
Running
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>
app.py
CHANGED
|
@@ -376,66 +376,71 @@ if selected_view == "Strategy":
|
|
| 376 |
)
|
| 377 |
|
| 378 |
# ============================================================
|
| 379 |
-
# TIER 1:
|
| 380 |
# ============================================================
|
| 381 |
-
|
| 382 |
|
| 383 |
-
#
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
|
| 391 |
-
|
| 392 |
-
|
|
|
|
| 393 |
st.markdown(f"""
|
| 394 |
-
<div style="text-align: center; padding: 1.
|
| 395 |
-
<p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.
|
| 396 |
-
<p style="color: {COLORS['
|
| 397 |
</div>
|
| 398 |
""", unsafe_allow_html=True)
|
| 399 |
-
with
|
| 400 |
st.markdown(f"""
|
| 401 |
-
<div style="text-align: center; padding: 1.
|
| 402 |
-
<p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.
|
| 403 |
-
<p style="color: {
|
| 404 |
</div>
|
| 405 |
""", unsafe_allow_html=True)
|
| 406 |
|
|
|
|
| 407 |
|
| 408 |
# ============================================================
|
| 409 |
-
# TIER 2:
|
| 410 |
# ============================================================
|
| 411 |
-
|
|
|
|
|
|
|
| 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.
|
| 417 |
-
<p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.
|
| 418 |
-
<p style="color: {COLORS['primary']}; margin: 0; font-size:
|
| 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.
|
| 428 |
-
<p style="color: {COLORS['text_muted']}; margin: 0; font-size: 0.
|
| 429 |
-
<p style="color: {COLORS['
|
| 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 |
-
#
|
| 450 |
-
chart_months = min(
|
| 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=
|
| 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,
|