Final Report and Figures for Discovery Quality Mediation

This notebook closes the discovery-quality mediation analysis. Earlier notebooks built the dataset, constructed discovery and satisfaction metrics, defined mediation estimands, estimated direct and indirect effects, tested robustness, and compared advanced model families.

This notebook does three things:

The final story is intentionally balanced: high discovery exposure is associated with higher future user value in the linear and SEM-style analyses; flexible ML models keep the sign positive but shrink the effect size. Across specifications, the satisfaction-depth mediated pathway is small relative to the direct pathway.

1. Load Libraries and Paths

This cell imports the report-building libraries and defines the project paths. The final notebook mostly reads saved outputs and writes final artifacts, so the path setup is the most important piece of infrastructure here.

from pathlib import Path
import textwrap

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from IPython.display import Markdown, display

sns.set_theme(style="whitegrid", context="notebook")
plt.rcParams["figure.figsize"] = (11, 6)
plt.rcParams["axes.titlesize"] = 13
plt.rcParams["axes.labelsize"] = 11
pd.set_option("display.max_columns", 180)
pd.set_option("display.max_colwidth", 160)

PROJECT_ROOT = Path.cwd().resolve()
while not (PROJECT_ROOT / "data").exists() and PROJECT_ROOT.parent != PROJECT_ROOT:
    PROJECT_ROOT = PROJECT_ROOT.parent

PROCESSED_DIR = PROJECT_ROOT / "data" / "processed"
NOTEBOOK_DIR = PROJECT_ROOT / "notebooks" / "discovery_quality_mediation"
WRITEUP_DIR = NOTEBOOK_DIR / "writeup"
FIGURE_DIR = WRITEUP_DIR / "figures"
TABLE_DIR = WRITEUP_DIR / "tables"

FIGURE_DIR.mkdir(parents=True, exist_ok=True)
TABLE_DIR.mkdir(parents=True, exist_ok=True)

FINAL_SUMMARY_OUTPUT = WRITEUP_DIR / "final_project_summary.md"
RESUME_BULLETS_OUTPUT = WRITEUP_DIR / "resume_bullets.md"
ARTIFACT_INDEX_OUTPUT = WRITEUP_DIR / "artifact_index.csv"
FINAL_EXECUTIVE_TABLE_OUTPUT = TABLE_DIR / "final_executive_summary.csv"
FINAL_MAIN_EFFECTS_OUTPUT = TABLE_DIR / "final_main_effects.csv"
FINAL_ROBUSTNESS_OUTPUT = TABLE_DIR / "final_robustness_snapshot.csv"
FINAL_ADVANCED_OUTPUT = TABLE_DIR / "final_advanced_model_snapshot.csv"
FINAL_LIMITATIONS_OUTPUT = TABLE_DIR / "final_limitations.csv"

The output paths are all inside this project’s writeup folder, except for the processed inputs that were created by earlier notebooks. This keeps the final report self-contained.

2. Load Final Analysis Artifacts

This cell loads the outputs produced across notebooks 01-06. These files are the source of truth for the final report: data readiness, metric definitions, assumptions, effect estimates, robustness checks, advanced model comparisons, and limitations.

inputs = {
    "readiness": PROCESSED_DIR / "kuairec_discovery_quality_readiness.csv",
    "selected_metrics": PROCESSED_DIR / "kuairec_discovery_quality_selected_metrics.csv",
    "metric_registry": PROCESSED_DIR / "kuairec_discovery_quality_metric_registry.csv",
    "assumption_checks": PROCESSED_DIR / "kuairec_discovery_quality_assumption_checks.csv",
    "effect_summary": PROCESSED_DIR / "kuairec_discovery_quality_effect_summary.csv",
    "controlled_direct_effects": PROCESSED_DIR / "kuairec_discovery_quality_controlled_direct_effects.csv",
    "robustness_summary": PROCESSED_DIR / "kuairec_discovery_quality_robustness_summary.csv",
    "threshold_robustness": PROCESSED_DIR / "kuairec_discovery_quality_robustness_thresholds.csv",
    "mediator_robustness": PROCESSED_DIR / "kuairec_discovery_quality_robustness_mediators.csv",
    "model_sensitivity": PROCESSED_DIR / "kuairec_discovery_quality_model_sensitivity.csv",
    "continuous_sensitivity": PROCESSED_DIR / "kuairec_discovery_quality_continuous_score_sensitivity.csv",
    "placebo_checks": PROCESSED_DIR / "kuairec_discovery_quality_placebo_checks.csv",
    "advanced_summary": PROCESSED_DIR / "kuairec_discovery_quality_advanced_model_summary.csv",
    "advanced_ml_effects": PROCESSED_DIR / "kuairec_discovery_quality_advanced_ml_effects.csv",
    "advanced_performance": PROCESSED_DIR / "kuairec_discovery_quality_advanced_ml_performance.csv",
    "advanced_heterogeneity": PROCESSED_DIR / "kuairec_discovery_quality_advanced_heterogeneity.csv",
    "limitations": PROCESSED_DIR / "kuairec_discovery_quality_limitations.csv",
}

loaded = {name: pd.read_csv(path) for name, path in inputs.items()}
load_summary = pd.DataFrame(
    {
        "artifact": list(inputs.keys()),
        "path": [str(path) for path in inputs.values()],
        "rows": [len(loaded[name]) for name in inputs.keys()],
        "columns": [loaded[name].shape[1] for name in inputs.keys()],
    }
)

display(load_summary)
artifact path rows columns
0 readiness /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_readiness.csv 6 3
1 selected_metrics /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_selected_metrics.csv 4 7
2 metric_registry /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_metric_registry.csv 6 5
3 assumption_checks /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_assumption_checks.csv 6 5
4 effect_summary /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_effect_summary.csv 21 12
5 controlled_direct_effects /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_controlled_direct_effects.csv 9 7
6 robustness_summary /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_robustness_summary.csv 4 9
7 threshold_robustness /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_robustness_thresholds.csv 4 26
8 mediator_robustness /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_robustness_mediators.csv 5 25
9 model_sensitivity /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_model_sensitivity.csv 5 25
10 continuous_sensitivity /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_continuous_score_sensitivity.csv 4 6
11 placebo_checks /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_placebo_checks.csv 5 5
12 advanced_summary /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_advanced_model_summary.csv 5 9
13 advanced_ml_effects /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_advanced_ml_effects.csv 6 10
14 advanced_performance /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_advanced_ml_performance.csv 12 6
15 advanced_heterogeneity /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_advanced_heterogeneity.csv 12 8
16 limitations /home/apex/Documents/ranking_sys/data/processed/kuairec_discovery_quality_limitations.csv 5 4

The load summary confirms that the final notebook is not doing fresh causal estimation. It is assembling the outputs that were already created and validated earlier.

3. Build the Executive Summary Table

This cell creates a compact executive summary. It captures the dataset scale, primary estimand, main result, robustness finding, advanced-model finding, and central caveat.

readiness = loaded["readiness"]
effect_summary = loaded["effect_summary"]
robustness_summary = loaded["robustness_summary"]
advanced_summary = loaded["advanced_summary"]
limitations = loaded["limitations"]

primary_total = effect_summary.query(
    "outcome == 'Y_future_interactions' and estimand == 'gcomp_total_effect'"
).iloc[0]
primary_direct = effect_summary.query(
    "outcome == 'Y_future_interactions' and estimand == 'natural_direct_effect'"
).iloc[0]
primary_indirect = effect_summary.query(
    "outcome == 'Y_future_interactions' and estimand == 'natural_indirect_effect'"
).iloc[0]
play_hours_total = effect_summary.query(
    "outcome == 'Y_future_play_hours' and estimand == 'gcomp_total_effect'"
).iloc[0]

active_user_days = int(readiness.loc[readiness["check"].eq("active_user_days"), "value"].iloc[0])
sampled_users = int(readiness.loc[readiness["check"].eq("sampled_users"), "value"].iloc[0])

threshold_row = robustness_summary.query("robustness_family == 'threshold_sensitivity'").iloc[0]
model_row = robustness_summary.query("robustness_family == 'model_sensitivity'").iloc[0]
lightgbm_row = advanced_summary.query("model_family == 'lightgbm'").iloc[0]
xgboost_row = advanced_summary.query("model_family == 'xgboost'").iloc[0]

executive_summary = pd.DataFrame(
    [
        {"item": "Analysis unit", "summary": f"{active_user_days:,} active user-days from {sampled_users:,} KuaiRec users."},
        {"item": "Treatment", "summary": "High discovery-breadth day, based on long-tail exposure, new category exposure, and category breadth."},
        {"item": "Mediator", "summary": "Same-day satisfaction depth, combining watch-ratio, valid-play, completion, and abandonment proxies."},
        {"item": "Primary outcome", "summary": "Future seven-day interactions after the current active day."},
        {"item": "Main total effect", "summary": f"+{primary_total['estimate']:.1f} future interactions, 95% bootstrap interval [{primary_total['ci_95_lower']:.1f}, {primary_total['ci_95_upper']:.1f}]."},
        {"item": "Direct pathway", "summary": f"+{primary_direct['estimate']:.1f} future interactions, larger than the total because the mediated pathway is slightly negative."},
        {"item": "Satisfaction-mediated pathway", "summary": f"{primary_indirect['estimate']:.1f} future interactions, about {primary_total['proportion_mediated']:.1%} of the total effect."},
        {"item": "Play-time outcome", "summary": f"+{play_hours_total['estimate']:.3f} future play hours, with a small positive mediated pathway."},
        {"item": "Robustness", "summary": f"Threshold tests kept the total effect positive across {int(threshold_row['specifications'])} specifications; model sensitivity range was {model_row['total_effect_min']:.1f} to {model_row['total_effect_max']:.1f}."},
        {"item": "Advanced models", "summary": f"LightGBM and XGBoost kept the sign positive but shrank total effects to +{lightgbm_row['total_effect']:.1f} and +{xgboost_row['total_effect']:.1f}."},
        {"item": "Main caveat", "summary": "The logs are observational, so unobserved user intent and ranking-system state can still confound the estimates."},
    ]
)

display(executive_summary)
item summary
0 Analysis unit 8,199 active user-days from 133 KuaiRec users.
1 Treatment High discovery-breadth day, based on long-tail exposure, new category exposure, and category breadth.
2 Mediator Same-day satisfaction depth, combining watch-ratio, valid-play, completion, and abandonment proxies.
3 Primary outcome Future seven-day interactions after the current active day.
4 Main total effect +37.3 future interactions, 95% bootstrap interval [32.2, 42.8].
5 Direct pathway +38.6 future interactions, larger than the total because the mediated pathway is slightly negative.
6 Satisfaction-mediated pathway -1.3 future interactions, about -3.5% of the total effect.
7 Play-time outcome +0.078 future play hours, with a small positive mediated pathway.
8 Robustness Threshold tests kept the total effect positive across 4 specifications; model sensitivity range was 34.8 to 37.6.
9 Advanced models LightGBM and XGBoost kept the sign positive but shrank total effects to +3.2 and +2.7.
10 Main caveat The logs are observational, so unobserved user intent and ranking-system state can still confound the estimates.

The executive summary is deliberately concise. It is meant to be readable without requiring someone to open every intermediate notebook.

4. Present the Metric Roles

This cell turns the selected metric table into a final report table. It clarifies which metric served as exposure, mediator, product monitoring score, and guardrail.

selected_metrics = loaded["selected_metrics"].copy()
metric_roles = selected_metrics[
    ["selected_for", "metric", "reason", "future_alignment_score", "history_dependence_score", "screening_score"]
].copy()
metric_roles["selected_for"] = metric_roles["selected_for"].str.replace("_", " ").str.title()
metric_roles = metric_roles.rename(
    columns={
        "selected_for": "Role",
        "metric": "Metric",
        "reason": "Why it was selected",
        "future_alignment_score": "Future alignment",
        "history_dependence_score": "History dependence",
        "screening_score": "Screening score",
    }
)

display(metric_roles.round(4))
Role Metric Why it was selected Future alignment History dependence Screening score
0 Exposure Analysis discovery_breadth_score Purest discovery exposure score; does not use future outcomes or satisfaction depth as a defining component. 0.5015 0.4008 0.3612
1 Mediator Analysis satisfaction_depth_score Aggregates several same-day quality signals and is appropriate as a mediator candidate. 0.0510 0.0465 0.0347
2 Product Metric Monitoring quality_adjusted_discovery_score Requires both discovery breadth and satisfaction depth to be high, making it a useful composite quality metric. 0.4924 0.3574 0.3673
3 Guardrail Monitoring shallow_click_pressure_score Flags high-volume days with low satisfaction, useful as a warning against click-only optimization. 0.3316 0.4440 0.1762

The key design choice is that the exposure metric and mediator metric stay separate. That separation is what makes the mediation decomposition understandable.

5. Create the Final Main Effects Table

This cell builds the final direct, indirect, and total effect table for the primary outcome and the play-hours outcome. These are the headline estimates from notebook 04.

final_effects = effect_summary.query(
    "estimand in ['gcomp_total_effect', 'natural_direct_effect', 'natural_indirect_effect', 'mediator_shift_M1_minus_M0']"
).copy()
final_effects["Estimand"] = final_effects["estimand"].map(
    {
        "gcomp_total_effect": "Total effect",
        "natural_direct_effect": "Natural direct effect",
        "natural_indirect_effect": "Natural indirect effect",
        "mediator_shift_M1_minus_M0": "Satisfaction-depth shift",
    }
)
final_effects = final_effects[
    [
        "outcome_label",
        "Estimand",
        "estimate",
        "ci_95_lower",
        "ci_95_upper",
        "relative_effect",
        "proportion_mediated",
    ]
].rename(
    columns={
        "outcome_label": "Outcome",
        "estimate": "Estimate",
        "ci_95_lower": "CI lower",
        "ci_95_upper": "CI upper",
        "relative_effect": "Relative effect",
        "proportion_mediated": "Proportion mediated",
    }
)

# The mediator shift is on a different scale, so relative effect and proportion mediated are not reported for that row.
final_effects.loc[final_effects["Estimand"].eq("Satisfaction-depth shift"), ["Relative effect", "Proportion mediated"]] = np.nan

display(final_effects.round(4))
Outcome Estimand Estimate CI lower CI upper Relative effect Proportion mediated
2 Future 7-day interactions Total effect 37.2781 32.2444 42.8135 0.1152 -0.0351
3 Future 7-day interactions Natural direct effect 38.5858 33.3634 43.7154 0.1192 -0.0351
4 Future 7-day interactions Natural indirect effect -1.3077 -2.3047 -0.4290 -0.0036 -0.0351
6 Future 7-day interactions Satisfaction-depth shift 0.0231 0.0185 0.0279 NaN NaN
9 Future 7-day active days Total effect 0.0074 -0.0452 0.0631 0.0011 0.7175
10 Future 7-day active days Natural direct effect 0.0021 -0.0449 0.0648 0.0003 0.7175
11 Future 7-day active days Natural indirect effect 0.0053 -0.0145 0.0201 0.0008 0.7175
13 Future 7-day active days Satisfaction-depth shift 0.0231 0.0185 0.0279 NaN NaN
16 Future 7-day play hours Total effect 0.0784 0.0689 0.0954 0.1000 0.0562
17 Future 7-day play hours Natural direct effect 0.0739 0.0630 0.0927 0.0943 0.0562
18 Future 7-day play hours Natural indirect effect 0.0044 0.0014 0.0060 0.0051 0.0562
20 Future 7-day play hours Satisfaction-depth shift 0.0231 0.0185 0.0279 NaN NaN

This table makes the main project finding explicit: the future-interaction effect is positive, and the satisfaction-depth pathway is small compared with the direct pathway.

6. Plot the Final Effect Decomposition

This cell creates a clean final figure for the primary outcome. The plot shows total, direct, and indirect effects with bootstrap intervals.

main_plot = final_effects.query(
    "Outcome == 'Future 7-day interactions' and Estimand in ['Total effect', 'Natural direct effect', 'Natural indirect effect']"
).copy()
main_plot["lower_error"] = main_plot["Estimate"] - main_plot["CI lower"]
main_plot["upper_error"] = main_plot["CI upper"] - main_plot["Estimate"]

fig, ax = plt.subplots(figsize=(10, 5.5))
sns.barplot(data=main_plot, x="Estimate", y="Estimand", color="steelblue", ax=ax)
for row_index, row in main_plot.reset_index(drop=True).iterrows():
    ax.errorbar(
        x=row["Estimate"],
        y=row_index,
        xerr=[[row["lower_error"]], [row["upper_error"]]],
        fmt="none",
        color="black",
        capsize=4,
        linewidth=1.2,
    )
ax.axvline(0, color="black", linewidth=1)
ax.set_title("Final Discovery-Quality Mediation Effects")
ax.set_xlabel("Effect on future 7-day interactions")
ax.set_ylabel("")
plt.tight_layout()
final_effect_figure = FIGURE_DIR / "26_final_effect_decomposition.png"
fig.savefig(final_effect_figure, dpi=160, bbox_inches="tight")
plt.show()

This is the primary report figure. It shows that the main result is driven by the direct path rather than a large satisfaction-mediated path.

7. Summarize Robustness Evidence

This cell creates a final robustness snapshot from threshold, mediator, model, and outcome sensitivity tables. The focus is whether the total effect remains positive and whether the indirect pathway stays small.

robustness_snapshot = robustness_summary.copy()
robustness_snapshot["Robustness family"] = robustness_snapshot["robustness_family"].str.replace("_", " ").str.title()
robustness_snapshot = robustness_snapshot[
    [
        "Robustness family",
        "specifications",
        "total_effect_min",
        "total_effect_max",
        "total_effect_share_positive",
        "indirect_effect_min",
        "indirect_effect_max",
        "median_abs_indirect_to_total_ratio",
    ]
].rename(
    columns={
        "specifications": "Specifications",
        "total_effect_min": "Total min",
        "total_effect_max": "Total max",
        "total_effect_share_positive": "Share total positive",
        "indirect_effect_min": "Indirect min",
        "indirect_effect_max": "Indirect max",
        "median_abs_indirect_to_total_ratio": "Median abs indirect/total",
    }
)

display(robustness_snapshot.round(4))
Robustness family Specifications Total min Total max Share total positive Indirect min Indirect max Median abs indirect/total
0 Threshold Sensitivity 4 37.2781 40.0555 1.0 -1.6532 -1.3077 0.0402
1 Mediator Sensitivity 5 37.2560 37.3502 1.0 -1.7145 0.1281 0.0359
2 Model Sensitivity 5 34.8085 37.6289 1.0 -1.5581 -0.9360 0.0351
3 Outcome Sensitivity 1 37.2781 37.2781 1.0 -1.3077 -1.3077 0.0351

The robustness snapshot supports a careful conclusion: the positive future-interaction association is stable across standard sensitivity checks, while the mediated pathway remains small.

8. Plot Robustness Ranges

This cell visualizes the range of total and indirect effects across robustness families. The goal is to show the reader where the result is stable and where it is sensitive.

robustness_plot = robustness_snapshot.copy()
robustness_plot["Family"] = robustness_plot["Robustness family"]

fig, axes = plt.subplots(1, 2, figsize=(15, 5.5))
for ax, lower_col, upper_col, title, xlabel in [
    (axes[0], "Total min", "Total max", "Total Effect Range", "Effect on future 7-day interactions"),
    (axes[1], "Indirect min", "Indirect max", "Indirect Effect Range", "Effect through satisfaction depth"),
]:
    y_positions = np.arange(len(robustness_plot))
    ax.hlines(
        y=y_positions,
        xmin=robustness_plot[lower_col],
        xmax=robustness_plot[upper_col],
        color="steelblue",
        linewidth=4,
    )
    ax.scatter(robustness_plot[lower_col], y_positions, color="black", s=25)
    ax.scatter(robustness_plot[upper_col], y_positions, color="black", s=25)
    ax.axvline(0, color="gray", linewidth=1)
    ax.set_yticks(y_positions)
    ax.set_yticklabels(robustness_plot["Family"])
    ax.set_title(title)
    ax.set_xlabel(xlabel)
    ax.set_ylabel("")

plt.tight_layout()
final_robustness_figure = FIGURE_DIR / "27_final_robustness_ranges.png"
fig.savefig(final_robustness_figure, dpi=160, bbox_inches="tight")
plt.show()

The range plot makes the robustness story easy to scan: total effects stay positive, while indirect effects remain near zero or modestly negative in the primary future-interaction setup.

9. Present Advanced Model Evidence

This cell prepares the final advanced model comparison. It includes the linear reference, SEM-style path model, cross-fitted linear model, and cross-fitted LightGBM/XGBoost models.

advanced_snapshot = advanced_summary.copy()
advanced_snapshot["Model approach"] = advanced_snapshot["model_family"].str.replace("_", " ").str.title()
advanced_snapshot = advanced_snapshot[
    [
        "Model approach",
        "total_effect",
        "direct_effect",
        "indirect_effect",
        "indirect_to_total_ratio",
        "total_effect_positive",
        "indirect_effect_small_abs_lt_5",
    ]
].rename(
    columns={
        "total_effect": "Total effect",
        "direct_effect": "Direct effect",
        "indirect_effect": "Indirect effect",
        "indirect_to_total_ratio": "Indirect/total",
        "total_effect_positive": "Total positive",
        "indirect_effect_small_abs_lt_5": "Small indirect effect",
    }
)

display(advanced_snapshot.round(4))
Model approach Total effect Direct effect Indirect effect Indirect/total Total positive Small indirect effect
0 Linear Reference From Notebook 04 37.2781 38.5858 -1.3077 -0.0351 True True
1 Sem Style Path Model 37.3139 38.4311 -1.1172 -0.0299 True True
2 Linear 37.3421 38.6464 -1.3043 -0.0349 True True
3 Lightgbm 3.2069 3.2652 -0.0583 -0.0182 True True
4 Xgboost 2.6829 2.7279 -0.0450 -0.0168 True True

The advanced models are the main nuance in the final story. Flexible nuisance models still produce positive total effects, but their effect sizes are much smaller than the linear/SEM estimates.

10. Plot Advanced Model Comparison

This cell creates the final advanced-model figure. It compares total, direct, and indirect effects across modeling approaches.

advanced_plot = advanced_snapshot.melt(
    id_vars=["Model approach"],
    value_vars=["Total effect", "Direct effect", "Indirect effect"],
    var_name="Effect type",
    value_name="Estimate",
)

fig, ax = plt.subplots(figsize=(13, 7))
sns.barplot(data=advanced_plot, x="Estimate", y="Model approach", hue="Effect type", ax=ax)
ax.axvline(0, color="black", linewidth=1)
ax.set_title("Final Advanced Model Comparison")
ax.set_xlabel("Effect on future 7-day interactions")
ax.set_ylabel("Model approach")
plt.tight_layout()
final_advanced_figure = FIGURE_DIR / "28_final_advanced_model_comparison.png"
fig.savefig(final_advanced_figure, dpi=160, bbox_inches="tight")
plt.show()

This figure prevents overclaiming from the linear model alone. It shows the positive direction is stable, but the magnitude is model-sensitive.

11. Final Limitations Table

This cell formats the limitations table created in notebook 05. It is included in the final report because the project uses observational recommendation logs and proxy satisfaction labels.

final_limitations = limitations.copy()
final_limitations["Limitation"] = final_limitations["limitation"].str.replace("_", " ").str.title()
final_limitations = final_limitations[
    ["Limitation", "why_it_matters", "mitigation_in_this_work", "remaining_risk"]
].rename(
    columns={
        "why_it_matters": "Why it matters",
        "mitigation_in_this_work": "Mitigation in this work",
        "remaining_risk": "Remaining risk",
    }
)

display(final_limitations)
Limitation Why it matters Mitigation in this work Remaining risk
0 Observational Recommendation Logs High-discovery exposure was not randomized, so user preferences and recommender selection can confound estimates. Used pre-treatment history/profile controls, propensity diagnostics, and weighted sensitivity checks. Unobserved ranking-system state and user intent can still bias estimates.
1 Mediator Outcome Confounding Satisfaction depth and future engagement may share unobserved causes. Checked residual mediator-outcome relationships and compared multiple mediator definitions. Natural direct and indirect effects rely on strong mediator assumptions.
2 Satisfaction Proxy Measurement Watch ratio and valid play are proxies, not direct surveys of satisfaction. Compared composite satisfaction, high watch ratio, valid play, average score, and completion-style mediators. The chosen proxies may miss frustration, novelty value, or long-run satisfaction.
3 Active Day Sample Selection The analysis conditions on active user-days, so it does not study days with no observed interactions. Defined the estimand clearly for active days and reported active-day alternate outcomes. Results may not generalize to dormant users or cold-start retention decisions.
4 Calendar And History Imbalance High discovery days differ from lower discovery days in time and recent behavior. Included calendar/history controls and showed placebo-style pre-period checks. Residual time-varying confounding can remain after observed adjustment.

The limitations table is part of the result, not an appendix afterthought. It defines what the analysis can and cannot support.

12. Write the Markdown Final Summary

This cell writes final_project_summary.md. The summary is intentionally concise and suitable for a repository README, portfolio page, or interview walkthrough.

summary_md = f"""
# Discovery Quality Mediation: Final Summary

## Problem

Recommendation systems can increase short-term activity without necessarily improving user satisfaction or durable value. This analysis studies whether high discovery exposure is associated with future user value, and whether that relationship is mediated by same-day satisfaction depth.

## Data and Unit of Analysis

- Dataset: KuaiRec interaction logs and metadata.
- Analysis unit: active user-day.
- Final analysis panel: {active_user_days:,} active user-days from {sampled_users:,} users.
- Treatment: high discovery-breadth day.
- Mediator: same-day satisfaction-depth score.
- Primary outcome: future seven-day interactions.

## Metrics

- `discovery_breadth_score`: exposure-like metric combining long-tail content, new category exposure, and category breadth.
- `satisfaction_depth_score`: mediator-like score combining watch quality, valid play, completion, and abandonment proxies.
- `quality_adjusted_discovery_score`: product monitoring metric that requires both discovery and satisfaction to be high.
- `shallow_click_pressure_score`: guardrail for high-volume, low-satisfaction engagement.

## Main Results

- Total effect on future seven-day interactions: **+{primary_total['estimate']:.1f}** with 95% bootstrap interval **[{primary_total['ci_95_lower']:.1f}, {primary_total['ci_95_upper']:.1f}]**.
- Natural direct effect: **+{primary_direct['estimate']:.1f}**.
- Natural indirect effect through satisfaction depth: **{primary_indirect['estimate']:.1f}**.
- Estimated shift in satisfaction depth from high discovery exposure: **+{effect_summary.query("outcome == 'Y_future_interactions' and estimand == 'mediator_shift_M1_minus_M0'")['estimate'].iloc[0]:.3f}**.
- Future play-hours total effect: **+{play_hours_total['estimate']:.3f}** hours.

The primary future-interaction gain is mostly direct in this specification. The satisfaction-depth mediated pathway is small relative to the total effect.

## Robustness

- Discovery-threshold sensitivity kept the total future-interaction effect positive across {int(threshold_row['specifications'])} tested thresholds.
- Mediator sensitivity kept the total effect positive across {int(robustness_summary.query("robustness_family == 'mediator_sensitivity'")['specifications'].iloc[0])} satisfaction proxy definitions.
- Model sensitivity kept the total effect positive across weighted/unweighted, interaction/no-interaction, and simple/rich control choices.
- Placebo-style checks show meaningful pre-period imbalance, which reinforces the need for adjustment and caution.

## Advanced Models

- SEM-style path model aligns closely with the linear g-computation result.
- Cross-fitted LightGBM and XGBoost keep the effect direction positive but shrink the estimated future-interaction effect to **+{lightgbm_row['total_effect']:.1f}** and **+{xgboost_row['total_effect']:.1f}**.
- The advanced model comparison supports a conservative conclusion: direction is stable, magnitude is model-sensitive.

## Limitations

- The data are observational recommendation logs, not randomized experiments.
- Satisfaction is measured through watch-behavior proxies rather than direct survey labels.
- Unobserved user intent and ranking-system state may confound treatment, mediator, and outcome relationships.
- The analysis conditions on active user-days and may not generalize to dormant users.

## Bottom Line

High discovery exposure appears positively associated with future user value, especially future interaction volume and play time. The evidence for a large satisfaction-mediated pathway is weak in the primary interaction-count analysis. A careful product takeaway is to track discovery quality separately from satisfaction depth and to validate discovery policies with future-value metrics, not short-term clicks alone.
""".strip()

FINAL_SUMMARY_OUTPUT.write_text(summary_md + chr(10), encoding="utf-8")
display(Markdown(summary_md))

Discovery Quality Mediation: Final Summary

Problem

Recommendation systems can increase short-term activity without necessarily improving user satisfaction or durable value. This analysis studies whether high discovery exposure is associated with future user value, and whether that relationship is mediated by same-day satisfaction depth.

Data and Unit of Analysis

  • Dataset: KuaiRec interaction logs and metadata.
  • Analysis unit: active user-day.
  • Final analysis panel: 8,199 active user-days from 133 users.
  • Treatment: high discovery-breadth day.
  • Mediator: same-day satisfaction-depth score.
  • Primary outcome: future seven-day interactions.

Metrics

  • discovery_breadth_score: exposure-like metric combining long-tail content, new category exposure, and category breadth.
  • satisfaction_depth_score: mediator-like score combining watch quality, valid play, completion, and abandonment proxies.
  • quality_adjusted_discovery_score: product monitoring metric that requires both discovery and satisfaction to be high.
  • shallow_click_pressure_score: guardrail for high-volume, low-satisfaction engagement.

Main Results

  • Total effect on future seven-day interactions: +37.3 with 95% bootstrap interval [32.2, 42.8].
  • Natural direct effect: +38.6.
  • Natural indirect effect through satisfaction depth: -1.3.
  • Estimated shift in satisfaction depth from high discovery exposure: +0.023.
  • Future play-hours total effect: +0.078 hours.

The primary future-interaction gain is mostly direct in this specification. The satisfaction-depth mediated pathway is small relative to the total effect.

Robustness

  • Discovery-threshold sensitivity kept the total future-interaction effect positive across 4 tested thresholds.
  • Mediator sensitivity kept the total effect positive across 5 satisfaction proxy definitions.
  • Model sensitivity kept the total effect positive across weighted/unweighted, interaction/no-interaction, and simple/rich control choices.
  • Placebo-style checks show meaningful pre-period imbalance, which reinforces the need for adjustment and caution.

Advanced Models

  • SEM-style path model aligns closely with the linear g-computation result.
  • Cross-fitted LightGBM and XGBoost keep the effect direction positive but shrink the estimated future-interaction effect to +3.2 and +2.7.
  • The advanced model comparison supports a conservative conclusion: direction is stable, magnitude is model-sensitive.

Limitations

  • The data are observational recommendation logs, not randomized experiments.
  • Satisfaction is measured through watch-behavior proxies rather than direct survey labels.
  • Unobserved user intent and ranking-system state may confound treatment, mediator, and outcome relationships.
  • The analysis conditions on active user-days and may not generalize to dormant users.

Bottom Line

High discovery exposure appears positively associated with future user value, especially future interaction volume and play time. The evidence for a large satisfaction-mediated pathway is weak in the primary interaction-count analysis. A careful product takeaway is to track discovery quality separately from satisfaction depth and to validate discovery policies with future-value metrics, not short-term clicks alone.

The markdown summary is now saved as a standalone report artifact. It captures the core problem, methods, results, robustness, advanced models, and caveats in one place.

13. Write Resume Bullets

This cell writes concise resume bullets based on the work. They are phrased broadly for data science, causal inference, and recommendation-system roles.

resume_md = f"""
# Resume Bullets

- Built an end-to-end causal mediation analysis on KuaiRec recommendation logs to study how discovery exposure, satisfaction depth, and future user value relate at the active user-day level.
- Engineered discovery-quality metrics separating exposure, mediator, composite product score, and guardrail roles; validated metrics against future engagement while avoiding future-label leakage.
- Estimated total, natural direct, natural indirect, and controlled direct effects with g-computation and user-level bootstrap uncertainty across {active_user_days:,} active user-days.
- Stress-tested mediation findings across treatment thresholds, mediator definitions, outcome definitions, weighting choices, interaction terms, control sets, and placebo-style pre-period checks.
- Compared linear mediation, SEM-style path modeling, and cross-fitted LightGBM/XGBoost nuisance models; found stable positive direction but model-sensitive effect magnitude.
- Produced portfolio-ready report artifacts, figures, limitations, and artifact index for a recommendation-system causal measurement workflow.
""".strip()

RESUME_BULLETS_OUTPUT.write_text(resume_md + chr(10), encoding="utf-8")
display(Markdown(resume_md))

Resume Bullets

  • Built an end-to-end causal mediation analysis on KuaiRec recommendation logs to study how discovery exposure, satisfaction depth, and future user value relate at the active user-day level.
  • Engineered discovery-quality metrics separating exposure, mediator, composite product score, and guardrail roles; validated metrics against future engagement while avoiding future-label leakage.
  • Estimated total, natural direct, natural indirect, and controlled direct effects with g-computation and user-level bootstrap uncertainty across 8,199 active user-days.
  • Stress-tested mediation findings across treatment thresholds, mediator definitions, outcome definitions, weighting choices, interaction terms, control sets, and placebo-style pre-period checks.
  • Compared linear mediation, SEM-style path modeling, and cross-fitted LightGBM/XGBoost nuisance models; found stable positive direction but model-sensitive effect magnitude.
  • Produced portfolio-ready report artifacts, figures, limitations, and artifact index for a recommendation-system causal measurement workflow.

The resume bullets emphasize the skills demonstrated: metric construction, causal mediation, robust evaluation, advanced modeling, and report packaging.

14. Create the Artifact Index

This cell creates a project artifact index. The index makes it easy to find notebooks, processed data outputs, final tables, final figures, and markdown writeups.

artifact_rows = []

for notebook_path in sorted(NOTEBOOK_DIR.glob("*.ipynb")):
    artifact_rows.append(
        {
            "artifact_type": "notebook",
            "artifact": notebook_path.name,
            "path": str(notebook_path.relative_to(PROJECT_ROOT)),
            "description": "Discovery-quality mediation notebook.",
        }
    )

for table_path in sorted(TABLE_DIR.glob("*.csv")):
    artifact_rows.append(
        {
            "artifact_type": "writeup_table",
            "artifact": table_path.name,
            "path": str(table_path.relative_to(PROJECT_ROOT)),
            "description": "CSV table used by the final report or prior writeup notebooks.",
        }
    )

for figure_path in sorted(FIGURE_DIR.glob("*.png")):
    artifact_rows.append(
        {
            "artifact_type": "figure",
            "artifact": figure_path.name,
            "path": str(figure_path.relative_to(PROJECT_ROOT)),
            "description": "PNG figure generated by the discovery-quality notebooks.",
        }
    )

for markdown_path in [FINAL_SUMMARY_OUTPUT, RESUME_BULLETS_OUTPUT]:
    artifact_rows.append(
        {
            "artifact_type": "markdown_report",
            "artifact": markdown_path.name,
            "path": str(markdown_path.relative_to(PROJECT_ROOT)),
            "description": "Standalone markdown report artifact.",
        }
    )

for processed_path in sorted(PROCESSED_DIR.glob("kuairec_discovery_quality_*")):
    artifact_rows.append(
        {
            "artifact_type": "processed_data",
            "artifact": processed_path.name,
            "path": str(processed_path.relative_to(PROJECT_ROOT)),
            "description": "Processed data or model output generated by the discovery-quality workflow.",
        }
    )

artifact_index = pd.DataFrame(artifact_rows).sort_values(["artifact_type", "artifact"]).reset_index(drop=True)
artifact_index.to_csv(ARTIFACT_INDEX_OUTPUT, index=False)

display(artifact_index.head(30))
print(f"Artifact index rows: {len(artifact_index)}")
artifact_type artifact path description
0 figure 01_watch_ratio_and_proxy_rates.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/01_watch_ratio_and_proxy_rates.png PNG figure generated by the discovery-quality notebooks.
1 figure 02_popularity_and_discovery_exposure.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/02_popularity_and_discovery_exposure.png PNG figure generated by the discovery-quality notebooks.
2 figure 03_discovery_mediator_outcome_relationships.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/03_discovery_mediator_outcome_relationships.png PNG figure generated by the discovery-quality notebooks.
3 figure 04_candidate_variable_correlation_map.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/04_candidate_variable_correlation_map.png PNG figure generated by the discovery-quality notebooks.
4 figure 05_metric_distributions.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/05_metric_distributions.png PNG figure generated by the discovery-quality notebooks.
5 figure 06_same_day_metric_correlation_heatmap.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/06_same_day_metric_correlation_heatmap.png PNG figure generated by the discovery-quality notebooks.
6 figure 07_metric_decile_future_outcomes.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/07_metric_decile_future_outcomes.png PNG figure generated by the discovery-quality notebooks.
7 figure 08_metric_validation_summary.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/08_metric_validation_summary.png PNG figure generated by the discovery-quality notebooks.
8 figure 09_mediation_design_dag.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/09_mediation_design_dag.png PNG figure generated by the discovery-quality notebooks.
9 figure 10_propensity_overlap_high_discovery.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/10_propensity_overlap_high_discovery.png PNG figure generated by the discovery-quality notebooks.
10 figure 11_covariate_balance_high_discovery.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/11_covariate_balance_high_discovery.png PNG figure generated by the discovery-quality notebooks.
11 figure 12_mediation_pathway_ingredients.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/12_mediation_pathway_ingredients.png PNG figure generated by the discovery-quality notebooks.
12 figure 13_primary_effect_decomposition.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/13_primary_effect_decomposition.png PNG figure generated by the discovery-quality notebooks.
13 figure 14_effect_decomposition_by_outcome.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/14_effect_decomposition_by_outcome.png PNG figure generated by the discovery-quality notebooks.
14 figure 15_controlled_direct_effects.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/15_controlled_direct_effects.png PNG figure generated by the discovery-quality notebooks.
15 figure 16_bootstrap_effect_distributions.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/16_bootstrap_effect_distributions.png PNG figure generated by the discovery-quality notebooks.
16 figure 17_threshold_sensitivity.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/17_threshold_sensitivity.png PNG figure generated by the discovery-quality notebooks.
17 figure 18_mediator_sensitivity.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/18_mediator_sensitivity.png PNG figure generated by the discovery-quality notebooks.
18 figure 19_model_sensitivity.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/19_model_sensitivity.png PNG figure generated by the discovery-quality notebooks.
19 figure 20_placebo_preperiod_checks.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/20_placebo_preperiod_checks.png PNG figure generated by the discovery-quality notebooks.
20 figure 21_crossfit_ml_effect_comparison.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/21_crossfit_ml_effect_comparison.png PNG figure generated by the discovery-quality notebooks.
21 figure 21_sem_path_decomposition.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/21_sem_path_decomposition.png PNG figure generated by the discovery-quality notebooks.
22 figure 22_crossfit_nuisance_performance.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/22_crossfit_nuisance_performance.png PNG figure generated by the discovery-quality notebooks.
23 figure 23_lightgbm_heterogeneous_effects.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/23_lightgbm_heterogeneous_effects.png PNG figure generated by the discovery-quality notebooks.
24 figure 24_lightgbm_feature_importance.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/24_lightgbm_feature_importance.png PNG figure generated by the discovery-quality notebooks.
25 figure 25_advanced_model_summary.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/25_advanced_model_summary.png PNG figure generated by the discovery-quality notebooks.
26 figure 26_final_effect_decomposition.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/26_final_effect_decomposition.png PNG figure generated by the discovery-quality notebooks.
27 figure 27_final_robustness_ranges.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/27_final_robustness_ranges.png PNG figure generated by the discovery-quality notebooks.
28 figure 28_final_advanced_model_comparison.png notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/28_final_advanced_model_comparison.png PNG figure generated by the discovery-quality notebooks.
29 markdown_report final_project_summary.md notebooks/projects/project_5_discovery_quality_mediation/writeup/final_project_summary.md Standalone markdown report artifact.
Artifact index rows: 102

The artifact index is the navigation layer for the completed project. It is especially useful once the folder contains many figures and intermediate tables.

15. Save Final Report Tables

This cell saves the final curated tables used in the report. These are smaller and cleaner than the full intermediate outputs.

executive_summary.to_csv(FINAL_EXECUTIVE_TABLE_OUTPUT, index=False)
final_effects.to_csv(FINAL_MAIN_EFFECTS_OUTPUT, index=False)
robustness_snapshot.to_csv(FINAL_ROBUSTNESS_OUTPUT, index=False)
advanced_snapshot.to_csv(FINAL_ADVANCED_OUTPUT, index=False)
final_limitations.to_csv(FINAL_LIMITATIONS_OUTPUT, index=False)
metric_roles.to_csv(TABLE_DIR / "final_metric_roles.csv", index=False)

saved_final_outputs = pd.DataFrame(
    {
        "artifact": [
            "final_project_summary",
            "resume_bullets",
            "artifact_index",
            "final_executive_summary",
            "final_main_effects",
            "final_robustness_snapshot",
            "final_advanced_model_snapshot",
            "final_limitations",
            "final_metric_roles",
            "final_effect_decomposition_figure",
            "final_robustness_ranges_figure",
            "final_advanced_model_comparison_figure",
        ],
        "path": [
            str(FINAL_SUMMARY_OUTPUT),
            str(RESUME_BULLETS_OUTPUT),
            str(ARTIFACT_INDEX_OUTPUT),
            str(FINAL_EXECUTIVE_TABLE_OUTPUT),
            str(FINAL_MAIN_EFFECTS_OUTPUT),
            str(FINAL_ROBUSTNESS_OUTPUT),
            str(FINAL_ADVANCED_OUTPUT),
            str(FINAL_LIMITATIONS_OUTPUT),
            str(TABLE_DIR / "final_metric_roles.csv"),
            str(final_effect_figure),
            str(final_robustness_figure),
            str(final_advanced_figure),
        ],
    }
)

display(saved_final_outputs)
artifact path
0 final_project_summary /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/final_project_summary.md
1 resume_bullets /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/resume_bullets.md
2 artifact_index /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/artifact_index.csv
3 final_executive_summary /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_executive_summary.csv
4 final_main_effects /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_main_effects.csv
5 final_robustness_snapshot /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_robustness_snapshot.csv
6 final_advanced_model_snapshot /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_advanced_model_snapshot.csv
7 final_limitations /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_limitations.csv
8 final_metric_roles /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/tables/final_metric_roles.csv
9 final_effect_decomposition_figure /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/26_final_effect_decomposition.png
10 final_robustness_ranges_figure /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/27_final_robustness_ranges.png
11 final_advanced_model_comparison_figure /home/apex/Documents/ranking_sys/notebooks/projects/project_5_discovery_quality_mediation/writeup/figures/28_final_advanced_model_comparison.png

The final report assets are now saved. These files are the clean handoff for a portfolio page, README section, or interview discussion.

16. Final Takeaways

This final cell states the project conclusion in concise form.

final_takeaways = pd.DataFrame(
    [
        {
            "takeaway": "High discovery exposure is positively associated with future user value.",
            "evidence": f"Primary g-computation total effect is +{primary_total['estimate']:.1f} future interactions with bootstrap interval [{primary_total['ci_95_lower']:.1f}, {primary_total['ci_95_upper']:.1f}].",
        },
        {
            "takeaway": "The future-interaction effect is mostly direct in the baseline mediation setup.",
            "evidence": f"Direct effect is +{primary_direct['estimate']:.1f}; indirect effect through satisfaction depth is {primary_indirect['estimate']:.1f}.",
        },
        {
            "takeaway": "Robustness checks support the positive direction but not a large mediated pathway.",
            "evidence": "Threshold, mediator, and model sensitivity checks keep total effects positive while indirect effects remain small.",
        },
        {
            "takeaway": "Advanced models make the magnitude more conservative.",
            "evidence": f"Cross-fitted LightGBM and XGBoost estimate smaller positive total effects: +{lightgbm_row['total_effect']:.1f} and +{xgboost_row['total_effect']:.1f}.",
        },
        {
            "takeaway": "The correct business reading is cautious and measurement-focused.",
            "evidence": "Use discovery-quality metrics alongside satisfaction and future-value validation; do not optimize short-term clicks alone.",
        },
    ]
)

display(final_takeaways)
takeaway evidence
0 High discovery exposure is positively associated with future user value. Primary g-computation total effect is +37.3 future interactions with bootstrap interval [32.2, 42.8].
1 The future-interaction effect is mostly direct in the baseline mediation setup. Direct effect is +38.6; indirect effect through satisfaction depth is -1.3.
2 Robustness checks support the positive direction but not a large mediated pathway. Threshold, mediator, and model sensitivity checks keep total effects positive while indirect effects remain small.
3 Advanced models make the magnitude more conservative. Cross-fitted LightGBM and XGBoost estimate smaller positive total effects: +3.2 and +2.7.
4 The correct business reading is cautious and measurement-focused. Use discovery-quality metrics alongside satisfaction and future-value validation; do not optimize short-term clicks alone.

This closes the workflow. The discovery-quality mediation project now has setup, metrics, estimands, main effects, robustness, advanced models, and final report artifacts.