google-meridian 1.3.2__py3-none-any.whl → 1.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 (78) hide show
  1. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/METADATA +18 -11
  2. google_meridian-1.5.0.dist-info/RECORD +112 -0
  3. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/WHEEL +1 -1
  4. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/top_level.txt +1 -0
  5. meridian/analysis/analyzer.py +558 -398
  6. meridian/analysis/optimizer.py +90 -68
  7. meridian/analysis/review/reviewer.py +4 -1
  8. meridian/analysis/summarizer.py +13 -3
  9. meridian/analysis/test_utils.py +2911 -2102
  10. meridian/analysis/visualizer.py +37 -14
  11. meridian/backend/__init__.py +106 -0
  12. meridian/constants.py +2 -0
  13. meridian/data/input_data.py +30 -52
  14. meridian/data/input_data_builder.py +2 -9
  15. meridian/data/test_utils.py +107 -51
  16. meridian/data/validator.py +48 -0
  17. meridian/mlflow/autolog.py +19 -9
  18. meridian/model/__init__.py +2 -0
  19. meridian/model/adstock_hill.py +3 -5
  20. meridian/model/context.py +1059 -0
  21. meridian/model/eda/constants.py +335 -4
  22. meridian/model/eda/eda_engine.py +723 -312
  23. meridian/model/eda/eda_outcome.py +177 -33
  24. meridian/model/equations.py +418 -0
  25. meridian/model/knots.py +58 -47
  26. meridian/model/model.py +228 -878
  27. meridian/model/model_test_data.py +38 -0
  28. meridian/model/posterior_sampler.py +103 -62
  29. meridian/model/prior_sampler.py +114 -94
  30. meridian/model/spec.py +23 -14
  31. meridian/templates/card.html.jinja +9 -7
  32. meridian/templates/chart.html.jinja +1 -6
  33. meridian/templates/finding.html.jinja +19 -0
  34. meridian/templates/findings.html.jinja +33 -0
  35. meridian/templates/formatter.py +41 -5
  36. meridian/templates/formatter_test.py +127 -0
  37. meridian/templates/style.css +66 -9
  38. meridian/templates/style.scss +85 -4
  39. meridian/templates/table.html.jinja +1 -0
  40. meridian/version.py +1 -1
  41. scenarioplanner/__init__.py +42 -0
  42. scenarioplanner/converters/__init__.py +25 -0
  43. scenarioplanner/converters/dataframe/__init__.py +28 -0
  44. scenarioplanner/converters/dataframe/budget_opt_converters.py +383 -0
  45. scenarioplanner/converters/dataframe/common.py +71 -0
  46. scenarioplanner/converters/dataframe/constants.py +137 -0
  47. scenarioplanner/converters/dataframe/converter.py +42 -0
  48. scenarioplanner/converters/dataframe/dataframe_model_converter.py +70 -0
  49. scenarioplanner/converters/dataframe/marketing_analyses_converters.py +543 -0
  50. scenarioplanner/converters/dataframe/rf_opt_converters.py +314 -0
  51. scenarioplanner/converters/mmm.py +743 -0
  52. scenarioplanner/converters/mmm_converter.py +58 -0
  53. scenarioplanner/converters/sheets.py +156 -0
  54. scenarioplanner/converters/test_data.py +714 -0
  55. scenarioplanner/linkingapi/__init__.py +47 -0
  56. scenarioplanner/linkingapi/constants.py +27 -0
  57. scenarioplanner/linkingapi/url_generator.py +131 -0
  58. scenarioplanner/mmm_ui_proto_generator.py +355 -0
  59. schema/__init__.py +5 -2
  60. schema/mmm_proto_generator.py +71 -0
  61. schema/model_consumer.py +133 -0
  62. schema/processors/__init__.py +77 -0
  63. schema/processors/budget_optimization_processor.py +832 -0
  64. schema/processors/common.py +64 -0
  65. schema/processors/marketing_processor.py +1137 -0
  66. schema/processors/model_fit_processor.py +367 -0
  67. schema/processors/model_kernel_processor.py +117 -0
  68. schema/processors/model_processor.py +415 -0
  69. schema/processors/reach_frequency_optimization_processor.py +584 -0
  70. schema/serde/distribution.py +12 -7
  71. schema/serde/hyperparameters.py +54 -107
  72. schema/serde/meridian_serde.py +6 -1
  73. schema/test_data.py +380 -0
  74. schema/utils/__init__.py +2 -0
  75. schema/utils/date_range_bucketing.py +117 -0
  76. schema/utils/proto_enum_converter.py +127 -0
  77. google_meridian-1.3.2.dist-info/RECORD +0 -76
  78. {google_meridian-1.3.2.dist-info → google_meridian-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -18,6 +18,7 @@ from collections.abc import Sequence
18
18
  import dataclasses
19
19
  import math
20
20
  import os
21
+ import re
21
22
 
22
23
  import altair as alt
23
24
  import immutabledict
@@ -46,6 +47,9 @@ class ChartSpec:
46
47
  id: str
47
48
  chart_json: str
48
49
  description: str | None = None
50
+ errors: Sequence[str] | None = None
51
+ warnings: Sequence[str] | None = None
52
+ infos: Sequence[str] | None = None
49
53
 
50
54
 
51
55
  @dataclasses.dataclass(frozen=True)
@@ -55,6 +59,9 @@ class TableSpec:
55
59
  column_headers: Sequence[str]
56
60
  row_values: Sequence[Sequence[str]]
57
61
  description: str | None = None
62
+ errors: Sequence[str] | None = None
63
+ warnings: Sequence[str] | None = None
64
+ infos: Sequence[str] | None = None
58
65
 
59
66
 
60
67
  @dataclasses.dataclass(frozen=True)
@@ -198,6 +205,25 @@ def format_monetary_num(num: float, currency: str) -> str:
198
205
  return compact_number(num, precision=precision, currency=currency)
199
206
 
200
207
 
208
+ def format_col_names(headers: Sequence[str]) -> Sequence[str]:
209
+ """Turns underscores to spaces and capitalizes words.
210
+
211
+ Ex. ['col_name', ...] to ['Col Name', ...])
212
+
213
+ Args:
214
+ headers: The list of column names to format.
215
+
216
+ Returns:
217
+ Human readable list of column names.
218
+ """
219
+ # \b matches the start of a word
220
+ # [a-z] matches only if the first letter is lowercase
221
+ return [
222
+ re.sub(r'\b[a-z]', lambda m: m.group().upper(), header.replace('_', ' '))
223
+ for header in headers
224
+ ]
225
+
226
+
201
227
  def create_template_env() -> jinja2.Environment:
202
228
  """Creates a Jinja2 template environment."""
203
229
  return jinja2.Environment(
@@ -220,19 +246,20 @@ def create_summary_html(
220
246
  def create_card_html(
221
247
  template_env: jinja2.Environment,
222
248
  card_spec: CardSpec,
223
- insights: str,
249
+ insights: str | None = None,
224
250
  chart_specs: Sequence[ChartSpec | TableSpec] | None = None,
225
251
  stats_specs: Sequence[StatsSpec] | None = None,
226
252
  ) -> str:
227
253
  """Creates a card's HTML snippet that includes given card and chart specs."""
228
- insights_html = template_env.get_template('insights.html.jinja').render(
229
- text_html=insights
230
- )
231
254
  card_params = dataclasses.asdict(card_spec)
232
255
  card_params[c.CARD_CHARTS] = (
233
256
  _create_charts_htmls(template_env, chart_specs) if chart_specs else None
234
257
  )
235
- card_params[c.CARD_INSIGHTS] = insights_html
258
+ if insights:
259
+ insights_html = template_env.get_template('insights.html.jinja').render(
260
+ text_html=insights
261
+ )
262
+ card_params[c.CARD_INSIGHTS] = insights_html
236
263
  card_params[c.CARD_STATS] = (
237
264
  _create_stats_htmls(template_env, stats_specs) if stats_specs else None
238
265
  )
@@ -267,3 +294,12 @@ def _create_charts_htmls(
267
294
  else:
268
295
  htmls.append(table_template.render(dataclasses.asdict(spec)))
269
296
  return htmls
297
+
298
+
299
+ def create_finding_html(
300
+ template_env: jinja2.Environment, text: str, finding_type: str
301
+ ) -> str:
302
+ """Generates an HTML tag for the table finding."""
303
+ return template_env.get_template('finding.html.jinja').render(
304
+ finding_class=finding_type, text=text
305
+ )
@@ -92,6 +92,36 @@ class FormatterTest(parameterized.TestCase):
92
92
  formatted_number = formatter.compact_number(num, precision, currency)
93
93
  self.assertEqual(formatted_number, expected)
94
94
 
95
+ @parameterized.named_parameters(
96
+ dict(
97
+ testcase_name='basic_snake_case',
98
+ input_headers=['finding_cause'],
99
+ expected=['Finding Cause'],
100
+ ),
101
+ dict(
102
+ testcase_name='multiple_columns',
103
+ input_headers=['geo', 'time_index', 'channel_name'],
104
+ expected=['Geo', 'Time Index', 'Channel Name'],
105
+ ),
106
+ dict(
107
+ testcase_name='preserves_acronyms',
108
+ input_headers=['VIF_score', 'national_KPI'],
109
+ expected=['VIF Score', 'National KPI'],
110
+ ),
111
+ dict(
112
+ testcase_name='handles_tuples_input',
113
+ input_headers=('row_id', 'value'),
114
+ expected=['Row Id', 'Value'],
115
+ ),
116
+ dict(
117
+ testcase_name='empty_input',
118
+ input_headers=[],
119
+ expected=[],
120
+ ),
121
+ )
122
+ def test_format_col_names(self, input_headers, expected):
123
+ self.assertEqual(formatter.format_col_names(input_headers), expected)
124
+
95
125
  def test_create_summary_html(self):
96
126
  template_env = formatter.create_template_env()
97
127
  title = 'Integration Test Report'
@@ -211,6 +241,103 @@ class FormatterTest(parameterized.TestCase):
211
241
  self.assertEqual(stats_html[0][2].tag, 'delta')
212
242
  self.assertContainsSubset('+0.3', stats_html[0][2].text)
213
243
 
244
+ def test_create_card_html_no_insights(self):
245
+ template_env = formatter.create_template_env()
246
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
247
+ stats_spec = formatter.StatsSpec(title='stats_title', stat='test_stat')
248
+
249
+ card_html = ET.fromstring(
250
+ formatter.create_card_html(
251
+ template_env, card_spec, insights=None, stats_specs=[stats_spec]
252
+ )
253
+ )
254
+
255
+ self.assertEqual(card_html.tag, 'card')
256
+ self.assertIsNone(card_html.find('card-insights'))
257
+ self.assertIsNotNone(card_html.find('stats-section'))
258
+
259
+ def test_create_card_html_chart_findings(self):
260
+ """Tests that errors, warnings, and infos render inside a chart."""
261
+ template_env = formatter.create_template_env()
262
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
263
+ chart_spec = formatter.ChartSpec(
264
+ id='id',
265
+ chart_json='{}',
266
+ errors=['Chart Error'],
267
+ warnings=['Chart Warning'],
268
+ infos=['Chart Info'],
269
+ )
270
+ card_html = ET.fromstring(
271
+ formatter.create_card_html(
272
+ template_env, card_spec, insights=None, chart_specs=[chart_spec]
273
+ )
274
+ )
275
+
276
+ charts_elem = card_html.find('charts')
277
+ self.assertIsNotNone(charts_elem)
278
+ chart_elem = charts_elem.find('chart')
279
+ self.assertIsNotNone(chart_elem)
280
+
281
+ error_elem = chart_elem.find('errors')
282
+ self.assertIsNotNone(error_elem)
283
+ error_p = error_elem.find('p')
284
+ self.assertIsNotNone(error_p)
285
+ self.assertIn('Chart Error', error_p.text)
286
+
287
+ warning_elem = chart_elem.find('warnings')
288
+ self.assertIsNotNone(warning_elem)
289
+ warning_p = warning_elem.find('p')
290
+ self.assertIsNotNone(warning_p)
291
+ self.assertIn('Chart Warning', warning_p.text)
292
+
293
+ info_elem = chart_elem.find('infos')
294
+ self.assertIsNotNone(info_elem)
295
+ info_p = info_elem.find('p')
296
+ self.assertIsNotNone(info_p)
297
+ self.assertIn('Chart Info', info_p.text)
298
+
299
+ def test_create_card_html_table_findings(self):
300
+ """Tests that errors, warnings, and infos render inside a table."""
301
+ template_env = formatter.create_template_env()
302
+ card_spec = formatter.CardSpec(id='test_id', title='test_title')
303
+ table_spec = formatter.TableSpec(
304
+ id='table_id',
305
+ title='Table Title',
306
+ column_headers=['Col1'],
307
+ row_values=[['Val1']],
308
+ errors=['Table Error'],
309
+ warnings=['Table Warning'],
310
+ infos=['Table Info'],
311
+ )
312
+ card_html = ET.fromstring(
313
+ formatter.create_card_html(
314
+ template_env, card_spec, insights=None, chart_specs=[table_spec]
315
+ )
316
+ )
317
+
318
+ charts_elem = card_html.find('charts')
319
+ self.assertIsNotNone(charts_elem)
320
+ table_elem = charts_elem.find('chart-table')
321
+ self.assertIsNotNone(table_elem)
322
+
323
+ error_elem = table_elem.find('errors')
324
+ self.assertIsNotNone(error_elem)
325
+ error_p = error_elem.find('p')
326
+ self.assertIsNotNone(error_p)
327
+ self.assertIn('Table Error', error_p.text)
328
+
329
+ warning_elem = table_elem.find('warnings')
330
+ self.assertIsNotNone(warning_elem)
331
+ warning_p = warning_elem.find('p')
332
+ self.assertIsNotNone(warning_p)
333
+ self.assertIn('Table Warning', warning_p.text)
334
+
335
+ info_elem = table_elem.find('infos')
336
+ self.assertIsNotNone(info_elem)
337
+ info_p = info_elem.find('p')
338
+ self.assertIsNotNone(info_p)
339
+ self.assertIn('Table Info', info_p.text)
340
+
214
341
 
215
342
  if __name__ == '__main__':
216
343
  absltest.main()
@@ -86,11 +86,30 @@ card {
86
86
  font-weight: 400;
87
87
  line-height: 50px; }
88
88
  card > card-insights {
89
+ background: #eee; }
90
+ card errors,
91
+ card warnings,
92
+ card infos {
93
+ margin-top: -39px;
94
+ margin-left: -69px;
95
+ margin-right: -69px; }
96
+ card errors {
97
+ background: #f8d7da; }
98
+ card warnings {
99
+ background: #fff3cd; }
100
+ card infos {
101
+ background: #dae8fc; }
102
+ card > card-insights,
103
+ card errors,
104
+ card warnings,
105
+ card infos {
89
106
  display: flex;
90
107
  flex-direction: row;
91
- padding: 22px 20px;
92
- background: #eee; }
93
- card > card-insights p.insights-text {
108
+ padding: 22px 20px; }
109
+ card > card-insights p,
110
+ card errors p,
111
+ card warnings p,
112
+ card infos p {
94
113
  margin: 0px 25px;
95
114
  color: var(--Grey-800, #3c4043);
96
115
  font-family: Roboto;
@@ -110,7 +129,10 @@ charts {
110
129
  charts chart {
111
130
  display: flex;
112
131
  flex-flow: column nowrap;
113
- gap: 12px; }
132
+ gap: 12px;
133
+ flex: 1 1 auto;
134
+ min-width: 0;
135
+ max-width: 100%; }
114
136
  charts chart > chart-description {
115
137
  color: var(--Grey-800, #3c4043);
116
138
  font-family: "Google Sans Display", "Google Sans", sans-serif;
@@ -119,10 +141,18 @@ charts {
119
141
  font-weight: 400;
120
142
  line-height: 16px;
121
143
  max-width: 450px; }
144
+ charts chart-embed {
145
+ display: block;
146
+ width: 100%;
147
+ overflow-x: auto;
148
+ padding: 10px 5px 20px 2px; }
122
149
  charts chart-table {
123
150
  display: flex;
124
151
  flex-flow: column nowrap;
125
- gap: 12px; }
152
+ gap: 12px;
153
+ flex: 1 1 auto;
154
+ min-width: 0;
155
+ max-width: 100%; }
126
156
  charts chart-table .chart-table-title {
127
157
  color: var(--Grey-800, #3c4043);
128
158
  font-family: "Google Sans Display", "Google Sans", sans-serif;
@@ -138,7 +168,10 @@ charts {
138
168
  font-style: normal;
139
169
  font-weight: 400;
140
170
  line-height: 20px;
141
- letter-spacing: 0.2px; }
171
+ letter-spacing: 0.2px;
172
+ width: 100%;
173
+ overflow-x: auto;
174
+ padding-bottom: 20px; }
142
175
  charts chart-table .chart-table-content table {
143
176
  border-radius: 4px;
144
177
  border: 1px solid var(--Grey-300, #dadce0);
@@ -160,15 +193,39 @@ charts {
160
193
  font-weight: 400;
161
194
  line-height: 16px;
162
195
  max-width: 450px; }
196
+ charts chart-table finding {
197
+ display: flex;
198
+ width: fit-content;
199
+ align-items: center;
200
+ justify-content: center;
201
+ white-space: nowrap;
202
+ padding: 4px 12px;
203
+ border-radius: 16px;
204
+ margin: 2px 4px 2px 0;
205
+ font-family: "Google Sans Display", "Google Sans", sans-serif;
206
+ font-weight: 500;
207
+ font-size: 13px;
208
+ line-height: 20px; }
209
+ charts chart-table finding.error {
210
+ background-color: #f8d7da;
211
+ color: var(--Red-600, #d93025); }
212
+ charts chart-table finding.attention {
213
+ background-color: #fff3cd;
214
+ color: #664d03; }
215
+ charts chart-table finding.info {
216
+ background-color: #dae8fc;
217
+ color: var(--blue-700, #1967d2); }
163
218
 
164
219
  stats-section {
165
220
  display: flex;
166
- flex-direction: row;
167
- justify-content: space-around;
221
+ flex-flow: row wrap;
222
+ justify-content: flex-start;
223
+ gap: 40px;
168
224
  padding: 32px; }
169
225
  stats-section stats {
170
226
  display: flex;
171
- flex-direction: column; }
227
+ flex-direction: column;
228
+ flex: 0 0 auto; }
172
229
  stats-section stats > stats-title {
173
230
  color: var(--grey-900, #202124);
174
231
  font-family: Roboto;
@@ -20,6 +20,10 @@ $insights_bg_grey: #eee;
20
20
  $text_grey: var(--Grey-800, #3c4043);
21
21
  $text_green: var(--Green-500, #34a853);
22
22
  $text_red: var(--Red-600, #d93025);
23
+ $error_red: #f8d7da;
24
+ $text_yellow: #664d03;
25
+ $warning_yellow: #fff3cd;
26
+ $info_blue: #dae8fc;
23
27
 
24
28
  $google_sans: 'Google Sans Display', 'Google Sans', sans-serif;
25
29
  $roboto: Roboto;
@@ -112,12 +116,38 @@ card {
112
116
  }
113
117
 
114
118
  > card-insights {
119
+ background: $insights_bg_grey;
120
+ }
121
+
122
+ errors,
123
+ warnings,
124
+ infos {
125
+ margin-top: -39px;
126
+ margin-left: -69px;
127
+ margin-right: -69px;
128
+ }
129
+
130
+ errors {
131
+ background: $error_red;
132
+ }
133
+
134
+ warnings {
135
+ background: $warning_yellow;
136
+ }
137
+
138
+ infos {
139
+ background: $info_blue;
140
+ }
141
+
142
+ > card-insights,
143
+ errors,
144
+ warnings,
145
+ infos {
115
146
  display: flex;
116
147
  flex-direction: row;
117
148
  padding: 22px 20px;
118
- background: $insights_bg_grey;
119
149
 
120
- p.insights-text {
150
+ p {
121
151
  margin: 0px 25px;
122
152
  color: $text_grey;
123
153
 
@@ -149,6 +179,9 @@ charts {
149
179
 
150
180
  chart {
151
181
  @include chart-style;
182
+ flex: 1 1 auto;
183
+ min-width: 0;
184
+ max-width: 100%;
152
185
 
153
186
  > chart-description {
154
187
  color: $text_grey;
@@ -162,8 +195,19 @@ charts {
162
195
  }
163
196
  }
164
197
 
198
+ chart-embed {
199
+ display: block;
200
+ width: 100%;
201
+
202
+ overflow-x: auto;
203
+ padding: 10px 5px 20px 2px;
204
+ }
205
+
165
206
  chart-table {
166
207
  @include chart-style;
208
+ flex: 1 1 auto;
209
+ min-width: 0;
210
+ max-width: 100%;
167
211
 
168
212
  .chart-table-title {
169
213
  color: $text_grey;
@@ -185,6 +229,9 @@ charts {
185
229
  line-height: 20px;
186
230
  letter-spacing: 0.2px;
187
231
 
232
+ width: 100%;
233
+ overflow-x: auto;
234
+ padding-bottom: 20px;
188
235
  @mixin border-style {
189
236
  border-radius: 4px;
190
237
  border: 1px solid var(--Grey-300, #dadce0);
@@ -216,18 +263,52 @@ charts {
216
263
  line-height: 16px;
217
264
  max-width: 450px;
218
265
  }
266
+
267
+ finding {
268
+ display: flex;
269
+ width: fit-content;
270
+ align-items: center;
271
+ justify-content: center;
272
+ white-space: nowrap;
273
+
274
+ padding: 4px 12px;
275
+ border-radius: 16px;
276
+ margin: 2px 4px 2px 0;
277
+
278
+ font-family: $google_sans;
279
+ font-weight: 500;
280
+ font-size: 13px;
281
+ line-height: 20px;
282
+
283
+ &.error {
284
+ background-color: $error_red;
285
+ color: $text_red;
286
+ }
287
+
288
+ &.attention {
289
+ background-color: $warning_yellow;
290
+ color: $text_yellow;
291
+ }
292
+
293
+ &.info {
294
+ background-color: $info_blue;
295
+ color: $chip_blue;
296
+ }
297
+ }
219
298
  }
220
299
  }
221
300
 
222
301
  stats-section {
223
302
  display: flex;
224
- flex-direction: row;
225
- justify-content: space-around;
303
+ flex-flow: row wrap;
304
+ justify-content: flex-start;
305
+ gap: 40px;
226
306
  padding: 32px;
227
307
 
228
308
  stats {
229
309
  display: flex;
230
310
  flex-direction: column;
311
+ flex: 0 0 auto;
231
312
 
232
313
  > stats-title {
233
314
  color: $title_dark_grey;
@@ -15,6 +15,7 @@ limitations under the License.
15
15
  #}
16
16
 
17
17
  <chart-table id="{{ id }}">
18
+ {% include "findings.html.jinja" %}
18
19
  <div class="chart-table-title">{{ title }}</div>
19
20
  <div class="chart-table-content">
20
21
  <table>
meridian/version.py CHANGED
@@ -14,4 +14,4 @@
14
14
 
15
15
  """Module for Meridian version."""
16
16
 
17
- __version__ = "1.3.2"
17
+ __version__ = "1.5.0"
@@ -0,0 +1,42 @@
1
+ # Copyright 2025 The Meridian Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Generates Meridian Scenario Planner Dashboards in Looker Studio.
16
+
17
+ This package provides tools to create and manage Meridian dashboards. It helps
18
+ transform data from the MMM (Marketing Mix Modeling) schema into a custom
19
+ Looker Studio dashboard, which can be shared via a URL.
20
+
21
+ The typical workflow is:
22
+
23
+ 1. Analyze MMM data into the appropriate schema.
24
+ 2. Generate UI-specific proto messages from this data using
25
+ `mmm_ui_proto_generator`.
26
+ 3. Build a Looker Studio URL that embeds this UI proto data using
27
+ `linkingapi`.
28
+
29
+ Key functionalities include:
30
+
31
+ - `linkingapi`: Builds Looker Studio report URLs with embedded data sources.
32
+ This allows for the creation of pre-configured reports.
33
+ - `mmm_ui_proto_generator`: Generates a `Mmm` proto message for the Meridian
34
+ Scenario Planner UI. It takes structured MMM data and transforms it into the
35
+ specific proto format that the dashboard frontend expects.
36
+ - `converters`: Provides utilities to convert and transform analyzed model
37
+ data into a data format that Looker Studio expects.
38
+ """
39
+
40
+ from scenarioplanner import converters
41
+ from scenarioplanner import linkingapi
42
+ from scenarioplanner import mmm_ui_proto_generator
@@ -0,0 +1,25 @@
1
+ # Copyright 2025 The Meridian Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Provides tools for converting and wrapping MMM schema data.
16
+
17
+ This package contains modules to transform Marketing Mix Modeling (MMM) protocol
18
+ buffer data into other formats and provides high-level wrappers for easier data
19
+ manipulation, analysis, and reporting.
20
+ """
21
+
22
+ from scenarioplanner.converters import dataframe
23
+ from scenarioplanner.converters import mmm
24
+ from scenarioplanner.converters import mmm_converter
25
+ from scenarioplanner.converters import sheets
@@ -0,0 +1,28 @@
1
+ # Copyright 2025 The Meridian Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Converters for `Mmm` protos to flat dataframes.
16
+
17
+ This package provides a set of tools for transforming data from `Mmm`
18
+ protos into flat dataframes. This conversion makes the data easier to analyze,
19
+ visualize, and use in other data processing pipelines.
20
+ """
21
+
22
+ from scenarioplanner.converters.dataframe import budget_opt_converters
23
+ from scenarioplanner.converters.dataframe import common
24
+ from scenarioplanner.converters.dataframe import constants
25
+ from scenarioplanner.converters.dataframe import converter
26
+ from scenarioplanner.converters.dataframe import dataframe_model_converter
27
+ from scenarioplanner.converters.dataframe import marketing_analyses_converters
28
+ from scenarioplanner.converters.dataframe import rf_opt_converters