File size: 4,807 Bytes
9253c67
 
 
 
 
2c0702f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e391f61
2c0702f
e391f61
2c0702f
 
 
 
 
 
 
9253c67
fa58ff0
 
 
 
 
 
 
 
 
9253c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa58ff0
 
 
 
 
 
 
 
 
9253c67
 
 
 
 
fa58ff0
 
 
 
 
9253c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637bd04
fa58ff0
637bd04
 
 
 
 
fa58ff0
 
 
 
 
 
 
637bd04
9253c67
637bd04
 
 
 
 
9253c67
 
 
fa58ff0
 
 
 
 
 
 
 
 
 
 
9253c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import pandas as pd
import streamlit as st
from config.config import CRRA_COLORS, CRRA_FONT, PLOTLY_TEMPLATE


def render_resource_input(name_clean, r, tooltip=None):
    """Render a number_input widget for a single resource availability cap.

    Writes the entered value into st.session_state.resource_caps[r].

    Args:
        name_clean (str): display label (resource name without unit suffix).
        r (str): canonical resource name used as the session state key.
        tooltip (str | None): optional help text shown on hover.

    Returns:
        None
    """
    widget_key = f"cap_{r}"

    if widget_key not in st.session_state:
        existing = st.session_state.resource_caps.get(r, 0.0)
        st.session_state[widget_key] = existing if existing else None

    cap = st.number_input(
        label=name_clean,
        min_value=0.0,
        format="%.10g",
        value=None,
        placeholder="0.0",
        key=widget_key,
        help=tooltip,
    )

    st.session_state.resource_caps[r] = cap if cap is not None else 0.0


def apply_chart_style(fig, height=None):
    """Apply the standard CRRA theme (font, colours, margins) to a Plotly figure.

    Args:
        fig: Plotly figure object.
        height (int | None): optional fixed height in pixels.

    Returns:
        fig: the same figure with updated layout.
    """
    fig.update_layout(
        template=PLOTLY_TEMPLATE,
        font=dict(family=CRRA_FONT, size=12, color=CRRA_COLORS["black"]),
        margin=dict(t=40, b=40, l=40, r=20),
        legend=dict(
            orientation="v",
            yanchor="middle", y=0.5,
            xanchor="left", x=1.02,
        ),
    )
    if height:
        fig.update_layout(height=height)
    return fig


def register_chart_data(name: str, df: pd.DataFrame):
    """Store a chart's underlying DataFrame in the session-level registry for bulk export.

    Args:
        name (str): unique chart label used as the dict key and export sheet name.
        df (pd.DataFrame): data behind the chart.

    Returns:
        None
    """
    st.session_state.setdefault("chart_data_registry", {})
    st.session_state["chart_data_registry"][name] = df.copy()


def show_bar():
    """Render the CRRA four-colour decorative bar below page headings.

    Returns:
        None
    """
    st.markdown(
        """
        <div class="et_pb_module">
            <div style="display:flex;width:100%;height:12px;margin:0 auto 0 0;">
                <span style="flex:1;background:#FF5F4D;"></span>
                <span style="flex:1;background:#A0A0A0;"></span>
                <span style="flex:1;background:#D9E210;"></span>
                <span style="flex:1;background:#55C95B;"></span>
            </div>
        </div>
        """,
        unsafe_allow_html=True,
    )


def smart_display_format(val):
    """Format a numeric value showing all significant digits, no rounding.

    Uses repr() which gives the shortest decimal string that round-trips
    exactly to the same float — avoids floating-point noise like
    462961.099999999976717 when the value is actually 462961.1.
    Falls back to fixed-decimal notation when repr() produces scientific
    notation (very small numbers such as 1e-11).

    Args:
        val (float): numeric value to format.

    Returns:
        str: formatted string, or "" for near-zero values.
    """
    if abs(val) < 1e-14:
        return ""
    s = repr(val)
    if "e" not in s.lower():
        return s
    # Scientific notation → convert to fixed decimal (e.g. 1e-11 → 0.00000000001)
    return f"{val:.15f}".rstrip("0").rstrip(".")


def format_efficiency_summary(df: pd.DataFrame, df_default: pd.DataFrame):
    """Build a styled Pandas Styler for the resource-efficiency coefficient summary table.

    Cells whose value differs from the default median are highlighted in red.

    Args:
        df (pd.DataFrame): current coefficient values (methods × resources).
        df_default (pd.DataFrame): default median values for the same shape.

    Returns:
        pandas.io.formats.style.Styler: styled dataframe ready for st.dataframe().
    """
    df_numeric = df.copy()
    df_display = df_numeric.map(smart_display_format)

    def highlight_changes(data):
        styled = pd.DataFrame('', index=data.index, columns=data.columns)
        for m in data.index:
            for r in data.columns:
                try:
                    current = df_numeric.at[m, r]
                    default = df_default.at[m, r]
                    if abs(current - default) > 1e-6:
                        styled.at[m, r] = 'background-color: #ff5f4d'
                except:
                    continue
        return styled

    return (
        df_display
        .style
        .apply(highlight_changes, axis=None)
    )