misata 0.3.0b0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. misata/__init__.py +1 -1
  2. misata/agents/__init__.py +23 -0
  3. misata/agents/pipeline.py +286 -0
  4. misata/causal/__init__.py +5 -0
  5. misata/causal/graph.py +109 -0
  6. misata/causal/solver.py +115 -0
  7. misata/cli.py +31 -0
  8. misata/generators/__init__.py +19 -0
  9. misata/generators/copula.py +198 -0
  10. misata/llm_parser.py +180 -137
  11. misata/quality.py +78 -33
  12. misata/reference_data.py +221 -0
  13. misata/research/__init__.py +3 -0
  14. misata/research/agent.py +70 -0
  15. misata/schema.py +25 -0
  16. misata/simulator.py +264 -12
  17. misata/smart_values.py +144 -6
  18. misata/studio/__init__.py +55 -0
  19. misata/studio/app.py +49 -0
  20. misata/studio/components/inspector.py +81 -0
  21. misata/studio/components/sidebar.py +35 -0
  22. misata/studio/constraint_generator.py +781 -0
  23. misata/studio/inference.py +319 -0
  24. misata/studio/outcome_curve.py +284 -0
  25. misata/studio/state/store.py +55 -0
  26. misata/studio/tabs/configure.py +50 -0
  27. misata/studio/tabs/generate.py +117 -0
  28. misata/studio/tabs/outcome_curve.py +149 -0
  29. misata/studio/tabs/schema_designer.py +217 -0
  30. misata/studio/utils/styles.py +143 -0
  31. misata/studio_constraints/__init__.py +29 -0
  32. misata/studio_constraints/z3_solver.py +259 -0
  33. {misata-0.3.0b0.dist-info → misata-0.5.0.dist-info}/METADATA +13 -2
  34. misata-0.5.0.dist-info/RECORD +61 -0
  35. {misata-0.3.0b0.dist-info → misata-0.5.0.dist-info}/WHEEL +1 -1
  36. {misata-0.3.0b0.dist-info → misata-0.5.0.dist-info}/entry_points.txt +1 -0
  37. misata-0.3.0b0.dist-info/RECORD +0 -37
  38. /misata/{generators.py → generators_legacy.py} +0 -0
  39. {misata-0.3.0b0.dist-info → misata-0.5.0.dist-info}/licenses/LICENSE +0 -0
  40. {misata-0.3.0b0.dist-info → misata-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,50 @@
1
+ import streamlit as st
2
+ from misata.studio.state.store import StudioStore
3
+
4
+ def render_configure_tab():
5
+ """Render the configuration tab."""
6
+ st.markdown("#### Generation Configuration")
7
+
8
+ config = StudioStore.get('warehouse_config', {})
9
+
10
+ col1, col2 = st.columns(2)
11
+
12
+ with col1:
13
+ avg_txn = st.number_input(
14
+ "Avg Transaction Value ($)",
15
+ min_value=1.0,
16
+ value=config.get('avg_transaction', 50.0),
17
+ help="Average value of a single invoice/transaction"
18
+ )
19
+ config['avg_transaction'] = avg_txn
20
+
21
+ with col2:
22
+ seed = st.number_input(
23
+ "Random Seed",
24
+ min_value=1,
25
+ value=config.get('seed', 42),
26
+ help="Fixed seed for reproducibility"
27
+ )
28
+ config['seed'] = seed
29
+
30
+ st.markdown("#### Tier Distribution")
31
+ st.caption("Distribution of Customer Segments")
32
+
33
+ c1, c2, c3 = st.columns(3)
34
+ defaults = config.get('tier_distribution', [0.5, 0.3, 0.2])
35
+
36
+ with c1:
37
+ tier_basic = st.slider("Basic %", 0, 100, int(defaults[0]*100))
38
+ with c2:
39
+ tier_pro = st.slider("Pro %", 0, 100, int(defaults[1]*100))
40
+ with c3:
41
+ tier_enterprise = st.slider("Enterprise %", 0, 100, int(defaults[2]*100))
42
+
43
+ config['tier_distribution'] = [tier_basic/100, tier_pro/100, tier_enterprise/100]
44
+ StudioStore.set('warehouse_config', config)
45
+
46
+ # NEXT BUTTON (Bottom of Tab)
47
+ st.markdown("<br><br>", unsafe_allow_html=True)
48
+ if st.button("Next: Generate Data →", type="primary", use_container_width=True):
49
+ StudioStore.set('active_tab', "Generate")
50
+ st.rerun()
@@ -0,0 +1,117 @@
1
+ import streamlit as st
2
+ from datetime import datetime, timedelta
3
+ from misata.studio.state.store import StudioStore
4
+ from misata.studio.outcome_curve import OutcomeCurve, CurvePoint
5
+ from misata.studio.constraint_generator import (
6
+ create_service_company_schema, generate_constrained_warehouse, convert_schema_config_to_spec
7
+ )
8
+
9
+ def render_generate_tab():
10
+ """Render the generation and export tab."""
11
+ st.markdown("#### Generate Complete Data Warehouse")
12
+
13
+ # Summary
14
+ schema = StudioStore.get('warehouse_schema', {})
15
+ curve = StudioStore.get('warehouse_curve', [])
16
+ config = StudioStore.get('warehouse_config', {})
17
+ schema_source = StudioStore.get('schema_source', 'Template')
18
+ schema_config = StudioStore.get('schema_config')
19
+
20
+ schema_name = "Service Company"
21
+ if schema_source == "AI" and schema_config:
22
+ schema_name = schema_config.name
23
+
24
+ st.markdown(f"""
25
+ **Schema:** {schema_name}
26
+ **Revenue Periods:** {len(curve)}
27
+ **Total Target Revenue:** ${sum(curve):,.0f}
28
+ """)
29
+
30
+ if st.button("Generate Warehouse ▸", type="primary", use_container_width=True):
31
+ with st.spinner("Generating complete data warehouse..."):
32
+ # Build revenue curve
33
+ start_date = datetime.combine(
34
+ StudioStore.get('start_date_input', datetime.now().date()),
35
+ datetime.min.time()
36
+ )
37
+ time_unit_val = "month" # Simplify for now
38
+
39
+ revenue_curve = OutcomeCurve(
40
+ metric_name='revenue',
41
+ time_unit=time_unit_val,
42
+ points=[
43
+ CurvePoint(timestamp=start_date + timedelta(days=30*i), value=v)
44
+ for i, v in enumerate(curve)
45
+ ],
46
+ avg_transaction_value=config.get('avg_transaction', 50.0)
47
+ )
48
+
49
+ # Create warehouse spec
50
+ spec = None
51
+ if schema_source == "AI" and schema_config:
52
+ spec = convert_schema_config_to_spec(
53
+ schema_config,
54
+ revenue_curve=revenue_curve
55
+ )
56
+ else:
57
+ spec = create_service_company_schema(
58
+ customer_count=schema.get('customer_count', 500),
59
+ project_count=schema.get('project_count', 2000),
60
+ revenue_curve=revenue_curve
61
+ )
62
+
63
+ # Generate
64
+ result = generate_constrained_warehouse(spec, seed=config.get('seed', 42))
65
+
66
+ # Store result
67
+ StudioStore.set('generated_warehouse', result)
68
+ StudioStore.set('warehouse_generated', True)
69
+
70
+ st.success("◆ Data warehouse generated successfully!")
71
+
72
+ # Show results
73
+ result = StudioStore.get('generated_warehouse')
74
+ if result:
75
+ # Show results
76
+ for table_name, df in result.items():
77
+ st.write(f"**{table_name}** ({len(df):,} rows)")
78
+ st.dataframe(df.head(100), use_container_width=True)
79
+
80
+ # Dynamic Verification
81
+ # (Simplified for the modular view, but should use spec.constraints)
82
+ # Using heuristic check for now if available
83
+ if "invoices" in result:
84
+ invoices = result['invoices']
85
+ if 'amount' in invoices.columns:
86
+ total_actual = invoices['amount'].sum()
87
+ total_expected = sum(curve)
88
+ match_pct = 100 * total_actual / total_expected if total_expected > 0 else 0
89
+
90
+ st.markdown("#### Verification")
91
+ col1, col2, col3 = st.columns(3)
92
+ with col1:
93
+ st.metric("Target Revenue", f"${total_expected:,.0f}")
94
+ with col2:
95
+ st.metric("Actual Revenue", f"${total_actual:,.0f}")
96
+ with col3:
97
+ st.metric("Match", f"{match_pct:.2f}%")
98
+
99
+ # Export
100
+ st.markdown("#### Export")
101
+
102
+ if st.button("Download All Tables ▼"):
103
+ import io
104
+ import zipfile
105
+
106
+ zip_buffer = io.BytesIO()
107
+ with zipfile.ZipFile(zip_buffer, "w") as zf:
108
+ for name, df in result.items():
109
+ csv_data = df.to_csv(index=False)
110
+ zf.writestr(f"{name}.csv", csv_data)
111
+
112
+ st.download_button(
113
+ "Download ZIP",
114
+ zip_buffer.getvalue(),
115
+ file_name="misata_warehouse.zip",
116
+ mime="application/zip"
117
+ )
@@ -0,0 +1,149 @@
1
+ import streamlit as st
2
+ import plotly.graph_objects as go
3
+ from misata.studio.state.store import StudioStore
4
+ from misata.studio.outcome_curve import get_curve_presets, CurvePoint, OutcomeCurve
5
+
6
+ def render_outcome_tab():
7
+ """Render the Outcome Constraint configuration tab."""
8
+ st.markdown("#### Define Outcome Constraints")
9
+
10
+ # Check if a constraint is selected
11
+ selected = StudioStore.get('selected_constraint')
12
+
13
+ if not selected:
14
+ # In AI mode, let them pick any float column
15
+ schema_source = StudioStore.get('schema_source')
16
+ schema_config = StudioStore.get('schema_config')
17
+
18
+ if schema_source == "AI" and schema_config:
19
+ st.info("Pick a column to constrain:")
20
+ # List candidate columns
21
+ candidates = []
22
+ for t_name, cols in schema_config.columns.items():
23
+ for c in cols:
24
+ if c.type in ['float', 'int'] and c.name in ['amount', 'price', 'total', 'revenue', 'value', 'cost']:
25
+ candidates.append(f"{t_name}.{c.name}")
26
+
27
+ if candidates:
28
+ selected_col = st.selectbox("Column", candidates)
29
+ if st.button("Add Constraint"):
30
+ StudioStore.set('selected_constraint', selected_col)
31
+ st.rerun()
32
+ else:
33
+ st.warning("No suitable columns found for outcome constraints (need float/int like 'amount').")
34
+
35
+ else:
36
+ st.info("👈 Select a numerical column (like **invoices.amount**) from the Schema tab to add a constraint.")
37
+ st.markdown("""
38
+ **What is an Outcome Constraint?**
39
+ It forces the generated data to match a specific aggregated shape over time.
40
+
41
+ *Example: Draw a revenue curve, and Misata generates millions of transactions that sum up exactly to that curve.*
42
+ """)
43
+ return
44
+
45
+ st.markdown(f"""
46
+ <div style="background:rgba(52, 78, 65, 0.05); border-left: 3px solid var(--sage-hunter); padding:1rem; border-radius:4px; margin-bottom:2rem;">
47
+ <span style="font-size:0.8rem; text-transform:uppercase; color:var(--text-tertiary);">Active Constraint</span>
48
+ <div style="font-size:1.2rem; font-weight:600; font-family:var(--font-mono); color:var(--sage-hunter);">
49
+ {selected}
50
+ </div>
51
+ </div>
52
+ """, unsafe_allow_html=True)
53
+
54
+ col1, col2 = st.columns([1, 2])
55
+
56
+ with col1:
57
+ st.markdown("#### Presets")
58
+ presets = get_curve_presets()
59
+
60
+ # Current curve state
61
+ current_curve = StudioStore.get('warehouse_curve', [100000] * 12)
62
+
63
+ selected_preset = st.radio(
64
+ "Shape",
65
+ list(presets.keys()),
66
+ help="Choose a starting shape for your curve"
67
+ )
68
+
69
+ # Scale
70
+ total_target = st.number_input(
71
+ "Total Target",
72
+ min_value=1000,
73
+ value=int(sum(current_curve)),
74
+ step=10000,
75
+ format="%d"
76
+ )
77
+
78
+ if st.button("Apply Preset", type="primary", use_container_width=True):
79
+ base_values = presets[selected_preset]
80
+ # Rescale to match target
81
+ current_sum = sum(base_values)
82
+ scale = total_target / current_sum if current_sum > 0 else 1
83
+
84
+ if selected_preset == "Random Volatility":
85
+ import random
86
+ values = [v * scale * random.uniform(0.8, 1.2) for v in base_values]
87
+ else:
88
+ values = [v * scale for v in base_values]
89
+ StudioStore.set('warehouse_curve', values)
90
+
91
+ # NEXT BUTTON (Bottom of Tab)
92
+ st.markdown("<br><br>", unsafe_allow_html=True)
93
+ if st.button("Next: Configure Generation →", type="primary", use_container_width=True):
94
+ StudioStore.set('active_tab', "Configure")
95
+ st.rerun()
96
+
97
+ with col2:
98
+ st.markdown("#### Shape the Curve")
99
+
100
+ # Sliders for each month
101
+ curve_points = StudioStore.get('warehouse_curve', [100000] * 12)
102
+ months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
103
+
104
+ # Interactive chart
105
+ fig = go.Figure()
106
+ fig.add_trace(go.Bar(
107
+ x=months,
108
+ y=curve_points,
109
+ marker_color='#588157',
110
+ opacity=0.8
111
+ ))
112
+
113
+ fig.add_trace(go.Scatter(
114
+ x=months,
115
+ y=curve_points,
116
+ mode='lines+markers',
117
+ line=dict(color='#344E41', width=3, shape='spline'),
118
+ marker=dict(size=8, color='#344E41')
119
+ ))
120
+
121
+ fig.update_layout(
122
+ paper_bgcolor='rgba(0,0,0,0)',
123
+ plot_bgcolor='rgba(0,0,0,0)',
124
+ margin=dict(l=20, r=20, t=20, b=20),
125
+ height=300,
126
+ showlegend=False
127
+ )
128
+
129
+ st.plotly_chart(fig, use_container_width=True)
130
+
131
+ st.markdown("---")
132
+ st.caption("Adjust Monthly Targets")
133
+
134
+ # 3 columns of 4 sliders
135
+ sc1, sc2, sc3 = st.columns(3)
136
+ cols = [sc1, sc2, sc3]
137
+
138
+ new_curve = list(curve_points)
139
+ for i, month in enumerate(months):
140
+ with cols[i // 4]:
141
+ new_curve[i] = st.slider(
142
+ f"{month}",
143
+ 0,
144
+ int(max(curve_points) * 2),
145
+ int(new_curve[i]),
146
+ key=f"slider_{i}"
147
+ )
148
+
149
+ StudioStore.set('warehouse_curve', new_curve)
@@ -0,0 +1,217 @@
1
+ import streamlit as st
2
+ from misata.studio.state.store import StudioStore
3
+ from misata.llm_parser import generate_schema
4
+ from misata.studio.components.inspector import render_table_inspector
5
+
6
+ def render_schema_tab():
7
+ """Render the Schema Designer tab."""
8
+ # Check if we are in Inspector Mode
9
+ if StudioStore.get("selected_table"):
10
+ render_table_inspector()
11
+ # Initial view is the inspector, but we also want to see the schema context?
12
+ # For now, let's keep it simple: Modal-like experience.
13
+ # Or render inspector at top, schema below.
14
+ st.markdown("---")
15
+
16
+ st.markdown("#### Schema Designer")
17
+
18
+ col_controls, col_viz = st.columns([1, 1])
19
+
20
+ with col_controls:
21
+ # Schema Source Selection
22
+ source_mode = st.radio(
23
+ "Design Mode",
24
+ ["Template", "AI Story 🧠"],
25
+ horizontal=True,
26
+ label_visibility="collapsed"
27
+ )
28
+
29
+ if source_mode == "Template":
30
+ schema_type = st.selectbox(
31
+ "Select Template",
32
+ ["Service Company", "E-commerce Store", "SaaS Platform", "Custom"],
33
+ label_visibility="collapsed"
34
+ )
35
+
36
+ if schema_type == "Service Company":
37
+ # Initialize default if not present
38
+ warehouse_schema = StudioStore.get('warehouse_schema') or {}
39
+ if not warehouse_schema or warehouse_schema.get('type') != 'service_company':
40
+ StudioStore.set('warehouse_schema', {
41
+ "type": "service_company",
42
+ "customer_count": 500,
43
+ "project_count": 2000
44
+ })
45
+ else:
46
+ # AI Story Mode
47
+ schema_type = "AI"
48
+ story = st.text_area(
49
+ "Describe your data needs:",
50
+ placeholder="e.g., A healthcare system with patients, doctors, and appointments...",
51
+ height=100
52
+ )
53
+ if st.button("Generate Schema ✨", type="primary"):
54
+ with st.spinner("consulting the architect..."):
55
+ try:
56
+ # Call LLM
57
+ config = generate_schema(story)
58
+ StudioStore.set('schema_config', config)
59
+ StudioStore.set('schema_source', "AI")
60
+ StudioStore.set('selected_table', None) # Reset selection
61
+ st.success("Schema generated!")
62
+ st.rerun()
63
+ except Exception as e:
64
+ st.error(f"Generation failed: {str(e)}")
65
+
66
+ with col_viz:
67
+ st.markdown("""
68
+ <div style="text-align: center; color: var(--text-secondary); padding: 2rem;">
69
+ <div style="font-size: 0.9rem;">Visual Relationship Mapper</div>
70
+ </div>
71
+ """, unsafe_allow_html=True)
72
+
73
+ st.markdown("---")
74
+
75
+ # RENDER AREA
76
+ schema_source = StudioStore.get("schema_source")
77
+ schema_config = StudioStore.get("schema_config")
78
+
79
+ # 1. AI GENERATED SCHEMA
80
+ if schema_source == "AI" and schema_config:
81
+ st.markdown(f"#### {schema_config.name}")
82
+ st.markdown(f"_{schema_config.description or 'Custom generated schema'}_")
83
+
84
+ # Grid layout for tables
85
+ cols = st.columns(3)
86
+ for i, table in enumerate(schema_config.tables):
87
+ col = cols[i % 3]
88
+ with col:
89
+ # Badge logic
90
+ badge_color = "var(--sage-hunter)"
91
+ badge_text = "Table"
92
+ bg_style = ""
93
+
94
+ if table.is_reference:
95
+ badge_color = "#D4A574" # Gold
96
+ badge_text = "Reference"
97
+ elif hasattr(table, 'row_count') and table.row_count > 10000:
98
+ badge_text = "Transactional"
99
+ bg_style = "border-left-color: var(--sage-hunter); background: rgba(88, 129, 87, 0.05);"
100
+
101
+ # Check if this table is being edited
102
+ is_selected = (StudioStore.get("selected_table") == table.name)
103
+ if is_selected:
104
+ bg_style += "border: 2px solid var(--sage-mint); box-shadow: 0 0 10px rgba(88,129,87,0.2);"
105
+
106
+ st.markdown(f"""
107
+ <div class="schema-node" style="{bg_style}">
108
+ <div class="node-header">
109
+ <span class="node-title">{table.name}</span>
110
+ <span class="node-badge" style="background:{badge_color}; color:white;">{badge_text}</span>
111
+ </div>
112
+ """, unsafe_allow_html=True)
113
+
114
+ # Header Actions: Edit Button
115
+ c_row, c_edit = st.columns([2, 1])
116
+ with c_row:
117
+ if not table.is_reference:
118
+ st.caption(f"{table.row_count:,} rows")
119
+ else:
120
+ st.caption("Ref Table")
121
+ with c_edit:
122
+ if st.button("Edit ✎", key=f"edit_{table.name}"):
123
+ StudioStore.set("selected_table", table.name)
124
+ st.rerun()
125
+
126
+ # Columns preview
127
+ if table.name in schema_config.columns:
128
+ col_html = ""
129
+ for col_def in schema_config.columns[table.name][:5]:
130
+ col_html += f"""
131
+ <div style="display:flex; justify-content:space-between; margin-bottom:4px; font-size:0.8rem;">
132
+ <span>{col_def.name}</span><span style="color:var(--text-tertiary);">{col_def.type}</span>
133
+ </div>
134
+ """
135
+ if len(schema_config.columns[table.name]) > 5:
136
+ col_html += f"<div style='font-size:0.7rem; color:var(--text-tertiary);'>+ {len(schema_config.columns[table.name])-5} more...</div>"
137
+
138
+ st.markdown(f"""
139
+ <div style="margin-top:0.5rem; padding:0.5rem; background:rgba(255,255,255,0.5); border-radius:4px;">
140
+ {col_html}
141
+ </div>
142
+ """, unsafe_allow_html=True)
143
+
144
+ st.markdown("</div>", unsafe_allow_html=True)
145
+
146
+ # 2. LEGACY TEMPLATE (Service Company)
147
+ elif schema_source == "Template":
148
+ warehouse_schema = StudioStore.get('warehouse_schema')
149
+ col_t1, col_t2, col_t3 = st.columns(3)
150
+
151
+ with col_t1:
152
+ st.markdown("""
153
+ <div class="schema-node">
154
+ <div class="node-header">
155
+ <span class="node-title">customers</span>
156
+ <span class="node-badge">Dimension</span>
157
+ </div>
158
+ """, unsafe_allow_html=True)
159
+
160
+ customer_count = st.number_input("Rows", 100, 100000,
161
+ warehouse_schema.get('customer_count', 500),
162
+ step=100, key="n_cust")
163
+ st.markdown("<div style='font-size:0.8rem; color:var(--text-secondary); margin-top:0.5rem;'>Attributes: id, name, email, tier, created_at</div>", unsafe_allow_html=True)
164
+ st.markdown("</div>", unsafe_allow_html=True)
165
+
166
+ with col_t2:
167
+ st.markdown("""
168
+ <div class="schema-node">
169
+ <div class="node-header">
170
+ <span class="node-title">projects</span>
171
+ <span class="node-badge">Dimension</span>
172
+ </div>
173
+ """, unsafe_allow_html=True)
174
+
175
+ project_count = st.number_input("Rows", 500, 500000,
176
+ warehouse_schema.get('project_count', 2000),
177
+ step=500, key="n_proj")
178
+ st.markdown("<div style='font-size:0.8rem; color:var(--text-secondary); margin-top:0.5rem;'>Attributes: id, customer_id, name, status</div>", unsafe_allow_html=True)
179
+ st.markdown("</div>", unsafe_allow_html=True)
180
+
181
+ with col_t3:
182
+ st.markdown("""
183
+ <div class="schema-node" style="border-left-color: var(--sage-hunter); background: rgba(88, 129, 87, 0.05);">
184
+ <div class="node-header">
185
+ <span class="node-title">invoices</span>
186
+ <span class="node-badge" style="background:var(--sage-hunter); color:white;">Fact Table</span>
187
+ </div>
188
+ """, unsafe_allow_html=True)
189
+
190
+ st.markdown("""
191
+ <div style="font-size:0.85rem; font-family:'JetBrains Mono'; margin-bottom:0.5rem;">
192
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px; background:rgba(255,255,255,0.5); padding:2px 4px; border-radius:4px;">
193
+ <span>amount</span>
194
+ <div style="display:flex; align-items:center; gap:4px;">
195
+ <span style="color:var(--text-tertiary); font-size:0.8rem;">float</span>
196
+ <span style="font-size:0.8rem; color:var(--sage-hunter);" title="Has Outcome Constraint">◭</span>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ """, unsafe_allow_html=True)
201
+
202
+ if st.button("Configure Constraint ◭", key="btn_const_inv", use_container_width=True):
203
+ StudioStore.set('active_tab', "Outcome Curve")
204
+ StudioStore.set('selected_constraint', "invoices.amount")
205
+ st.rerun()
206
+
207
+ st.markdown("</div>", unsafe_allow_html=True)
208
+
209
+ # Update state
210
+ warehouse_schema['customer_count'] = customer_count
211
+ warehouse_schema['project_count'] = project_count
212
+
213
+ # NEXT BUTTON
214
+ st.markdown("<br>", unsafe_allow_html=True)
215
+ if st.button("Next: Define Outcome Curve →", type="primary", use_container_width=True):
216
+ StudioStore.set('active_tab', "Outcome Curve")
217
+ st.rerun()
@@ -0,0 +1,143 @@
1
+ import streamlit as st
2
+
3
+ def apply_custom_styles():
4
+ """Apply the premium 'Botanical Sage' theme and architectural overrides."""
5
+ st.markdown("""
6
+ <style>
7
+ /* ============ VARIABLES ============ */
8
+ :root {
9
+ --sage-bg: #F4F7F5;
10
+ --sage-dark: #2A3B30;
11
+ --sage-hunter: #344E41;
12
+ --sage-fern: #588157;
13
+ --sage-mint: #A3B18A;
14
+ --sage-pale: #DAD7CD;
15
+
16
+ --text-primary: #1A1C1A;
17
+ --text-secondary: #4A524A;
18
+ --text-tertiary: #849685;
19
+
20
+ --card-bg: #FFFFFF;
21
+ --border-default: #E2E8E2;
22
+ --shadow-sm: 0 1px 2px rgba(42, 59, 48, 0.05);
23
+ --shadow-md: 0 4px 6px -1px rgba(42, 59, 48, 0.08);
24
+ --font-display: 'Instrument Serif', serif;
25
+ --font-body: 'Inter', sans-serif;
26
+ --font-mono: 'JetBrains Mono', monospace;
27
+ }
28
+
29
+ /* ============ GLOBAL RESET ============ */
30
+ .stApp {
31
+ background-color: var(--sage-bg);
32
+ font-family: var(--font-body);
33
+ color: var(--text-primary);
34
+ }
35
+
36
+ /* Override Sidebar */
37
+ [data-testid="stSidebar"] {
38
+ background-color: var(--sage-hunter);
39
+ border-right: 1px solid var(--sage-dark);
40
+ }
41
+
42
+ /* Force White Text in Sidebar */
43
+ section[data-testid="stSidebar"] p,
44
+ section[data-testid="stSidebar"] span,
45
+ section[data-testid="stSidebar"] div,
46
+ section[data-testid="stSidebar"] label {
47
+ color: #FFFFFF !important;
48
+ }
49
+
50
+ /* Restore header but transparent */
51
+ header {
52
+ visibility: visible !important;
53
+ background: transparent !important;
54
+ }
55
+ .header-decoration { visibility: hidden; }
56
+
57
+ /* ===== SIDEBAR TOGGLE (Locked Open) ===== */
58
+ [data-testid="collapsedControl"], [data-testid="stSidebarCollapsedControl"] {
59
+ display: none !important;
60
+ }
61
+
62
+ /* ============ TYPOGRAPHY ============ */
63
+ @import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&family=Pinyon+Script&display=swap');
64
+
65
+ h1, h2, h3 { font-family: var(--font-body); letter-spacing: -0.02em; }
66
+ h3 {
67
+ font-family: 'Cormorant Garamond', serif !important;
68
+ color: var(--text-primary) !important;
69
+ font-weight: 500 !important;
70
+ }
71
+
72
+ /* ============ COMPONENT: LOGO ============ */
73
+ .brand-logo {
74
+ font-family: 'Pinyon Script', cursive;
75
+ font-size: 2.8rem;
76
+ color: #FFFFFF !important;
77
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
78
+ margin-bottom: 0.5rem;
79
+ }
80
+
81
+ /* ============ COMPONENT: SCHEMA NODE ============ */
82
+ .schema-node {
83
+ background: white;
84
+ border: 1px solid var(--border-default);
85
+ border-radius: 8px;
86
+ padding: 1rem;
87
+ box-shadow: var(--shadow-sm);
88
+ margin-bottom: 1rem;
89
+ transition: all 0.2s ease;
90
+ border-left: 3px solid var(--sage-mint);
91
+ }
92
+ .schema-node:hover {
93
+ transform: translateY(-2px);
94
+ box-shadow: var(--shadow-md);
95
+ border-color: var(--sage-mint);
96
+ }
97
+ .node-header {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: center;
101
+ margin-bottom: 0.75rem;
102
+ padding-bottom: 0.5rem;
103
+ border-bottom: 1px solid var(--border-default);
104
+ }
105
+ .node-title {
106
+ font-family: var(--font-mono);
107
+ color: var(--sage-hunter);
108
+ font-weight: 600;
109
+ font-size: 0.95rem;
110
+ background: rgba(52, 78, 65, 0.05);
111
+ padding: 2px 6px;
112
+ border-radius: 4px;
113
+ }
114
+ .node-badge {
115
+ font-size: 0.7rem;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.05em;
118
+ color: var(--sage-fern);
119
+ background: rgba(88, 129, 87, 0.1);
120
+ padding: 2px 6px;
121
+ border-radius: 99px;
122
+ font-weight: 600;
123
+ }
124
+
125
+ /* ============ COMPONENT: BUTTONS ============ */
126
+ .stButton button {
127
+ border-radius: 6px;
128
+ font-weight: 500;
129
+ transition: all 0.2s;
130
+ }
131
+ /* Primary Button override */
132
+ .stButton button[kind="primary"] {
133
+ background: linear-gradient(135deg, var(--sage-hunter) 0%, #2A3B30 100%);
134
+ border: none;
135
+ box-shadow: 0 2px 8px rgba(52, 78, 65, 0.2);
136
+ }
137
+ .stButton button[kind="primary"]:hover {
138
+ transform: translateY(-1px);
139
+ box-shadow: 0 4px 12px rgba(52, 78, 65, 0.3);
140
+ }
141
+
142
+ </style>
143
+ """, unsafe_allow_html=True)