1. Data Setup and Key Assumptions

1.1 Financial Data

# Core financial data (all figures in Million EUR)
ulysse <- tribble(
  ~year, ~revenue, ~net_income, ~total_assets, ~equity,
  2020,   342,       8,           135,           90,
  2021,   376,       9,           133,           96,
  2022,   376,       9,           131,           99,
  2023,   363,       9,           131,          101,
  2024,   375,       5,           135,          101
)

1.2 Exchange Rate Data

# EUR/TND exchange rates (annual averages)
# Source: European Central Bank reference rates
# Note: Higher rate = TND weakening vs EUR (favorable for EUR-reported revenues)
exchange_rates <- tribble(
  ~year, ~eur_tnd_rate,
  2020,   3.45,
  2021,   3.29,
  2022,   3.35,
  2023,   3.33,
  2024,   3.38
)

# 2020 used as base year for constant currency calculations
BASE_FX_RATE <- 3.45

1.3 Analysis Parameters

# Define key periods for boycott analysis
PRE_BOYCOTT_YEARS <- 2021:2022  # Baseline period
BOYCOTT_START_YEAR <- 2023       # Q4 2023
POST_BOYCOTT_YEAR <- 2024        # Full year impact

# Calculate baseline metrics
baseline_metrics <- ulysse %>%
  filter(year %in% PRE_BOYCOTT_YEARS) %>%
  summarise(
    baseline_revenue = mean(revenue),
    baseline_income = mean(net_income),
    baseline_margin = mean(net_income / revenue * 100),
    .groups = 'drop'
  )

2. Calculated Metrics and Ratios

# Join with exchange rates and calculate basic metrics
ulysse_analysis <- ulysse %>%
  left_join(exchange_rates, by = "year") %>%
  arrange(year) %>%
  mutate(
    # Profitability ratios
    net_margin = (net_income / revenue) * 100,
    roe = (net_income / equity) * 100,
    roa = (net_income / total_assets) * 100,
    
    # Balance sheet metrics
    debt = total_assets - equity,
    debt_to_equity = debt / equity,
    equity_ratio = (equity / total_assets) * 100,
    
    # Asset efficiency
    asset_turnover = revenue / total_assets,
    
    # Constant currency calculations
    revenue_constant_fx = revenue * (eur_tnd_rate / BASE_FX_RATE),
    net_income_constant_fx = net_income * (eur_tnd_rate / BASE_FX_RATE),
    
    # FX impact
    fx_impact_revenue = revenue - revenue_constant_fx,
    fx_impact_pct = ((revenue / revenue_constant_fx) - 1) * 100,
    
    # Period classification
    period = case_when(
      year < BOYCOTT_START_YEAR ~ "Pre-Boycott",
      year == BOYCOTT_START_YEAR ~ "Boycott Start",
      year >= POST_BOYCOTT_YEAR ~ "Post-Boycott"
    ),
    period = factor(period, levels = c("Pre-Boycott", "Boycott Start", "Post-Boycott"))
  )

# Calculate growth rates manually (avoiding lag() issues)
# Create vectors for prior year values
n_rows <- nrow(ulysse_analysis)

ulysse_analysis <- ulysse_analysis %>%
  mutate(
    # Manual growth calculations
    revenue_growth = c(NA, 
                       diff(revenue) / head(revenue, -1) * 100),
    
    income_growth = c(NA, 
                      diff(net_income) / head(net_income, -1) * 100),
    
    revenue_growth_constant_fx = c(NA, 
                                   diff(revenue_constant_fx) / head(revenue_constant_fx, -1) * 100),
    
    # Organic growth is same as constant currency growth
    organic_growth = revenue_growth_constant_fx,
    
    # FX contribution
    fx_contribution = revenue_growth - revenue_growth_constant_fx
  )

# Display sample of calculated metrics
ulysse_analysis %>%
  dplyr::select(year, revenue, revenue_growth, organic_growth, fx_contribution, net_margin) %>%
  knitr::kable(digits = 2, caption = "Sample of Calculated Metrics")
Sample of Calculated Metrics
year revenue revenue_growth organic_growth fx_contribution net_margin
2020 342 NA NA NA 2.34
2021 376 9.94 4.84 5.10 2.39
2022 376 0.00 1.82 -1.82 2.39
2023 363 -3.46 -4.03 0.58 2.48
2024 375 3.31 4.86 -1.55 1.33

3. Financial Overview

3.1 Summary Table

ulysse_analysis %>%
  dplyr::select(year, revenue, net_income, net_margin, roe, roa, debt_to_equity) %>%
  gt() %>%
  tab_header(
    title = "Ulysse (Tunisia) - Key Financial Metrics",
    subtitle = "2020-2024 Performance Summary"
  ) %>%
  cols_label(
    year = "Year",
    revenue = "Revenue",
    net_income = "Net Income",
    net_margin = "Net Margin",
    roe = "ROE",
    roa = "ROA",
    debt_to_equity = "D/E Ratio"
  ) %>%
  fmt_number(
    columns = c(revenue, net_income),
    decimals = 0,
    suffix = "M€"
  ) %>%
  fmt_number(
    columns = c(net_margin, roe, roa),
    decimals = 1,
    suffix = "%"
  ) %>%
  fmt_number(
    columns = debt_to_equity,
    decimals = 2
  ) %>%
  tab_style(
    style = cell_fill(color = "#f0f0f0"),
    locations = cells_body(rows = year == 2024)
  ) %>%
  tab_style(
    style = cell_fill(color = "#ffe6e6"),
    locations = cells_body(
      columns = c(net_margin, roe),
      rows = year == 2024
    )
  )
Ulysse (Tunisia) - Key Financial Metrics
2020-2024 Performance Summary
Year Revenue Net Income Net Margin ROE ROA D/E Ratio
2020 342 8 2.3 8.9 5.9 0.50
2021 376 9 2.4 9.4 6.8 0.39
2022 376 9 2.4 9.1 6.9 0.32
2023 363 9 2.5 8.9 6.9 0.30
2024 375 5 1.3 5.0 3.7 0.34

4. Revenue Analysis

4.1 Revenue Trend and Growth

p1 <- ggplot(ulysse_analysis, aes(x = year, y = revenue)) +
  geom_line(color = colors$primary, linewidth = 1.2) +
  geom_point(size = 3, color = colors$primary) +
  geom_text(aes(label = paste0(revenue, "M€")), 
            vjust = -1, size = 3.5) +
  labs(
    title = "Revenue Trend 2020-2024",
    x = "Year",
    y = "Revenue (Million €)"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  scale_y_continuous(limits = c(320, 390))

p2 <- ulysse_analysis %>%
  filter(!is.na(revenue_growth)) %>%
  ggplot(aes(x = year, y = revenue_growth, fill = revenue_growth > 0)) +
  geom_col(width = 0.6) +
  geom_text(aes(label = sprintf("%.1f%%", revenue_growth),
                vjust = ifelse(revenue_growth > 0, -0.5, 1.5)), 
            size = 3.5) +
  scale_fill_manual(values = c(colors$negative, colors$positive), guide = "none") +
  labs(
    title = "Year-over-Year Revenue Growth",
    x = "Year",
    y = "Growth Rate (%)"
  ) +
  scale_x_continuous(breaks = unique(ulysse_analysis$year[!is.na(ulysse_analysis$revenue_growth)])) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "gray50")

p1 + p2


5. Profitability Analysis

5.2 Return Metrics (ROE and ROA)

ulysse_analysis %>%
  dplyr::select(year, roe, roa) %>%
  pivot_longer(-year, names_to = "metric", values_to = "value") %>%
  ggplot(aes(x = year, y = value, color = metric, group = metric)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  geom_text(aes(label = paste0(round(value, 1), "%")), 
            vjust = -1, size = 3) +
  scale_color_manual(
    values = c("roe" = "#9b59b6", "roa" = "#16a085"),
    labels = c("Return on Equity (ROE)", "Return on Assets (ROA)")
  ) +
  labs(
    title = "Return on Equity vs Return on Assets",
    x = "Year",
    y = "Return (%)",
    color = NULL
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year)


6. Balance Sheet Analysis

6.1 Capital Structure Evolution

p5 <- ulysse_analysis %>%
  dplyr::select(year, total_assets, equity, debt) %>%
  pivot_longer(-year, names_to = "item", values_to = "value") %>%
  mutate(item = factor(item, 
                       levels = c("total_assets", "equity", "debt"),
                       labels = c("Total Assets", "Equity", "Debt"))) %>%
  ggplot(aes(x = year, y = value, color = item, group = item)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("#34495e", colors$positive, colors$negative)) +
  labs(
    title = "Balance Sheet Components",
    x = "Year",
    y = "Amount (Million €)",
    color = NULL
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year)

p6 <- ggplot(ulysse_analysis, aes(x = year, y = equity_ratio)) +
  geom_line(color = "#16a085", linewidth = 1.2) +
  geom_point(size = 3, color = "#16a085") +
  geom_text(aes(label = paste0(round(equity_ratio, 1), "%")), 
            vjust = -1, size = 3.5) +
  labs(
    title = "Equity Ratio (Financial Strength)",
    x = "Year",
    y = "Equity / Total Assets (%)"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  scale_y_continuous(limits = c(60, 80))

p5 + p6

6.2 Leverage Trend

ggplot(ulysse_analysis, aes(x = year, y = debt_to_equity)) +
  geom_col(fill = "#c0392b", width = 0.6) +
  geom_text(aes(label = round(debt_to_equity, 2)), 
            vjust = -0.5, size = 3.5) +
  labs(
    title = "Debt-to-Equity Ratio",
    subtitle = "Lower values indicate less financial leverage and lower risk",
    x = "Year",
    y = "Debt / Equity Ratio"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  geom_hline(yintercept = 0.5, linetype = "dashed", color = "gray50", linewidth = 0.5) +
  annotate("text", x = 2020.3, y = 0.52, label = "0.50 Reference Line", 
           size = 3, hjust = 0)


7. Operational Efficiency

ggplot(ulysse_analysis, aes(x = year, y = asset_turnover)) +
  geom_line(color = "#8e44ad", linewidth = 1.2) +
  geom_point(size = 3, color = "#8e44ad") +
  geom_text(aes(label = round(asset_turnover, 2)), 
            vjust = -1, size = 3.5) +
  labs(
    title = "Asset Turnover Ratio",
    subtitle = "Revenue generated per euro of assets (higher is better)",
    x = "Year",
    y = "Asset Turnover (x)"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  scale_y_continuous(limits = c(2.3, 3.0))


8. Boycott Campaign Impact Analysis

Context

Following the events of October 7, 2023, Carrefour faced boycott campaigns in several markets, including Tunisia. The boycott began in Q4 2023, with full-year impact visible in 2024 results.

8.1 Revenue Impact Timeline

p_boycott1 <- ggplot(ulysse_analysis, aes(x = year, y = revenue, fill = period)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = paste0(revenue, "M€")), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = c("Pre-Boycott" = colors$info, 
                                "Boycott Start" = colors$warning,
                                "Post-Boycott" = colors$negative)) +
  labs(
    title = "Revenue Before and After October 7 Boycott Campaign",
    subtitle = "Boycott began Q4 2023, full-year impact visible in 2024",
    x = "Year",
    y = "Revenue (Million €)",
    fill = "Period"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  annotate("segment", x = 2023.5, xend = 2023.5, y = 0, yend = 380,
           linetype = "dashed", color = "red", linewidth = 0.8) +
  annotate("text", x = 2023.5, y = 385, label = "Boycott Starts", 
           color = "red", size = 3, hjust = 0.5)

# Calculate deviation from baseline
p_boycott2 <- ulysse_analysis %>%
  filter(year >= 2021) %>%
  mutate(
    revenue_gap = revenue - baseline_metrics$baseline_revenue
  ) %>%
  ggplot(aes(x = year, y = revenue_gap, fill = revenue_gap < 0)) +
  geom_col(width = 0.6) +
  geom_text(aes(label = paste0(ifelse(revenue_gap > 0, "+", ""), 
                                round(revenue_gap, 0), "M€"),
                vjust = ifelse(revenue_gap > 0, -0.5, 1.5)), 
            size = 3.5) +
  scale_fill_manual(values = c(colors$negative, colors$positive), guide = "none") +
  labs(
    title = "Revenue Gap vs Pre-Boycott Baseline",
    subtitle = paste0("Baseline: ", round(baseline_metrics$baseline_revenue, 0), "M€ (2021-2022 average)"),
    x = "Year",
    y = "Deviation from Baseline (Million €)"
  ) +
  scale_x_continuous(breaks = seq(2021, 2024, 1)) +
  geom_hline(yintercept = 0, linetype = "solid", color = "black")

p_boycott1 / p_boycott2

8.2 Profitability Collapse

p_profit1 <- ggplot(ulysse_analysis, aes(x = year, y = net_income, fill = period)) +
  geom_col(width = 0.7) +
  geom_text(aes(label = paste0(net_income, "M€")), vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = c("Pre-Boycott" = colors$info, 
                                "Boycott Start" = colors$warning,
                                "Post-Boycott" = colors$negative)) +
  labs(
    title = "Net Income: Dramatic Decline Post-Boycott",
    x = "Year",
    y = "Net Income (Million €)",
    fill = "Period"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  annotate("segment", x = 2023.5, xend = 2023.5, y = 0, yend = 9.5,
           linetype = "dashed", color = "red", linewidth = 0.8)

p_margin1 <- ggplot(ulysse_analysis, aes(x = year, y = net_margin, color = period, group = 1)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 4) +
  geom_text(aes(label = paste0(round(net_margin, 1), "%")), 
            vjust = -1.5, size = 3.5, color = "black") +
  scale_color_manual(values = c("Pre-Boycott" = colors$info, 
                                 "Boycott Start" = colors$warning,
                                 "Post-Boycott" = colors$negative)) +
  labs(
    title = "Net Margin Erosion",
    x = "Year",
    y = "Net Margin (%)",
    color = "Period"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  annotate("segment", x = 2023.5, xend = 2023.5, y = 0, yend = 3,
           linetype = "dashed", color = "red", linewidth = 0.8)

p_profit1 + p_margin1

8.3 Estimated Profit Loss

# Calculate expected vs actual performance
revenue_2024 <- ulysse_analysis$revenue[ulysse_analysis$year == 2024]
expected_income_2024 <- revenue_2024 * (baseline_metrics$baseline_margin / 100)
actual_income_2024 <- ulysse_analysis$net_income[ulysse_analysis$year == 2024]
profit_loss <- expected_income_2024 - actual_income_2024

loss_summary <- tribble(
  ~Description, ~Value,
  "2024 Revenue", paste0(revenue_2024, "M€"),
  "Expected Net Margin (2021-2022 avg)", paste0(round(baseline_metrics$baseline_margin, 2), "%"),
  "Expected Net Income at Historical Margin", paste0(round(expected_income_2024, 1), "M€"),
  "Actual Net Income 2024", paste0(actual_income_2024, "M€"),
  "Estimated Profit Loss", paste0(round(profit_loss, 1), "M€"),
  "Percentage Loss vs Expected", paste0(round((profit_loss/expected_income_2024)*100, 1), "%")
)

loss_summary %>%
  gt() %>%
  tab_header(
    title = "Estimated Financial Impact of Boycott on 2024 Results",
    subtitle = "Assuming margins would have remained at pre-boycott levels"
  ) %>%
  cols_label(
    Description = "",
    Value = "Amount"
  ) %>%
  tab_style(
    style = cell_fill(color = "#fff3cd"),
    locations = cells_body(rows = Description == "Estimated Profit Loss")
  ) %>%
  tab_style(
    style = cell_text(weight = "bold", size = px(14)),
    locations = cells_body(rows = Description %in% c("Estimated Profit Loss", "Percentage Loss vs Expected"))
  )
Estimated Financial Impact of Boycott on 2024 Results
Assuming margins would have remained at pre-boycott levels
Amount
2024 Revenue 375M€
Expected Net Margin (2021-2022 avg) 2.39%
Expected Net Income at Historical Margin 9M€
Actual Net Income 2024 5M€
Estimated Profit Loss 4M€
Percentage Loss vs Expected 44.3%

9. Currency vs Operational Performance

This section separates foreign exchange translation effects from true operational performance to assess the real impact of the boycott.

9.1 EUR/TND Exchange Rate Evolution

ggplot(ulysse_analysis, aes(x = year, y = eur_tnd_rate)) +
  geom_line(linewidth = 1.2, color = colors$primary) +
  geom_point(size = 4, color = colors$primary) +
  geom_text(aes(label = round(eur_tnd_rate, 2)), vjust = -1, size = 3.5) +
  labs(
    title = "EUR/TND Exchange Rate Evolution",
    subtitle = "Higher rate = TND weakening vs EUR (favorable for EUR-reported revenues)",
    x = "Year",
    y = "EUR/TND Rate"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  geom_hline(yintercept = BASE_FX_RATE, 
             linetype = "dashed", color = "red") +
  annotate("text", x = 2020.3, y = BASE_FX_RATE, 
           label = paste0("Base Rate (2020): ", BASE_FX_RATE), 
           vjust = -0.5, hjust = 0, size = 3, color = "red")

9.2 Reported vs Constant Currency Revenue

revenue_comparison <- ulysse_analysis %>%
  dplyr::select(year, revenue, revenue_constant_fx) %>%
  pivot_longer(cols = c(revenue, revenue_constant_fx), 
               names_to = "type", 
               values_to = "value") %>%
  mutate(type = factor(type, 
                       levels = c("revenue", "revenue_constant_fx"),
                       labels = c("Reported (Actual EUR)", "Constant Currency (2020 FX)")))

p_rev_comp <- ggplot(revenue_comparison, aes(x = year, y = value, color = type, group = type)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 3) +
  scale_color_manual(values = c("Reported (Actual EUR)" = colors$info, 
                                 "Constant Currency (2020 FX)" = "#e67e22")) +
  labs(
    title = "Reported vs Constant Currency Revenue",
    subtitle = "Separating FX effects from operational performance",
    x = "Year",
    y = "Revenue (Million €)",
    color = NULL
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year)

p_fx_impact <- ggplot(ulysse_analysis, aes(x = year, y = fx_impact_revenue)) +
  geom_col(aes(fill = fx_impact_revenue > 0), width = 0.6) +
  geom_text(aes(label = paste0(ifelse(fx_impact_revenue > 0, "+", ""), 
                                round(fx_impact_revenue, 1), "M€"),
                vjust = ifelse(fx_impact_revenue > 0, -0.5, 1.5)),
            size = 3.5) +
  scale_fill_manual(values = c(colors$negative, colors$positive), guide = "none") +
  labs(
    title = "FX Translation Impact on Revenue",
    subtitle = "Positive = FX benefit, Negative = FX headwind",
    x = "Year",
    y = "FX Impact (Million €)"
  ) +
  scale_x_continuous(breaks = ulysse_analysis$year) +
  geom_hline(yintercept = 0, linetype = "solid", color = "black")

p_rev_comp / p_fx_impact

9.3 Growth Decomposition: Reported vs Organic

growth_data <- ulysse_analysis %>%
  filter(!is.na(revenue_growth)) %>%
  dplyr::select(year, revenue_growth, organic_growth, fx_contribution) %>%
  pivot_longer(cols = c(organic_growth, fx_contribution),
               names_to = "component",
               values_to = "value") %>%
  mutate(component = factor(component,
                            levels = c("organic_growth", "fx_contribution"),
                            labels = c("Organic Growth (Constant FX)", "FX Impact")))

ggplot(growth_data, aes(x = year, y = value, fill = component)) +
  geom_col(position = "stack", width = 0.6) +
  geom_hline(yintercept = 0, linetype = "solid", color = "black") +
  scale_fill_manual(values = c("Organic Growth (Constant FX)" = colors$info, 
                                "FX Impact" = colors$neutral)) +
  labs(
    title = "Revenue Growth Decomposition: Organic vs FX Effects",
    subtitle = "Stacked bars show contribution from operations vs currency",
    x = "Year",
    y = "Growth Contribution (%)",
    fill = "Component"
  ) +
  scale_x_continuous(breaks = unique(growth_data$year)) +
  geom_text(data = ulysse_analysis %>% filter(!is.na(revenue_growth)),
            aes(x = year, y = revenue_growth, label = paste0(round(revenue_growth, 1), "%")),
            inherit.aes = FALSE, vjust = -0.5, size = 3.5, fontface = "bold")

9.4 Growth Analysis Table

ulysse_analysis %>%
  filter(!is.na(revenue_growth)) %>%
  dplyr::select(year, revenue_growth, organic_growth, fx_contribution, eur_tnd_rate) %>%
  gt() %>%
  tab_header(
    title = "Revenue Growth Decomposition by Year",
    subtitle = "Separating operational performance from currency effects"
  ) %>%
  cols_label(
    year = "Year",
    revenue_growth = "Reported Growth",
    organic_growth = "Organic Growth",
    fx_contribution = "FX Contribution",
    eur_tnd_rate = "EUR/TND Rate"
  ) %>%
  fmt_number(
    columns = c(revenue_growth, organic_growth, fx_contribution),
    decimals = 1,
    suffix = "%"
  ) %>%
  fmt_number(
    columns = eur_tnd_rate,
    decimals = 2
  ) %>%
  tab_style(
    style = cell_fill(color = "#e8f4f8"),
    locations = cells_body(columns = organic_growth)
  ) %>%
  tab_style(
    style = cell_fill(color = "#fff3cd"),
    locations = cells_body(columns = fx_contribution)
  ) %>%
  tab_footnote(
    footnote = "Organic growth shows operational performance at constant 2020 exchange rates",
    locations = cells_column_labels(columns = organic_growth)
  )
Revenue Growth Decomposition by Year
Separating operational performance from currency effects
Year Reported Growth Organic Growth1 FX Contribution EUR/TND Rate
2021 9.9 4.8 5.1 3.29
2022 0.0 1.8 −1.8 3.35
2023 −3.5 −4.0 0.6 3.33
2024 3.3 4.9 −1.6 3.38
1 Organic growth shows operational performance at constant 2020 exchange rates

9.5 Boycott Impact: Organic Revenue Performance

# Calculate constant currency baseline
baseline_revenue_constant <- ulysse_analysis %>%
  filter(year %in% PRE_BOYCOTT_YEARS) %>%
  pull(revenue_constant_fx) %>%
  mean()

organic_comparison <- ulysse_analysis %>%
  dplyr::select(year, revenue_constant_fx, period) %>%
  mutate(
    deviation = revenue_constant_fx - baseline_revenue_constant
  )

p_organic <- ggplot(organic_comparison, aes(x = year, y = revenue_constant_fx, fill = period)) +
  geom_col(width = 0.7) +
  geom_hline(yintercept = baseline_revenue_constant, 
             linetype = "dashed", color = "black", linewidth = 0.8) +
  geom_text(aes(label = paste0(round(revenue_constant_fx, 0), "M€")), 
            vjust = -0.5, size = 3.5) +
  scale_fill_manual(values = c("Pre-Boycott" = colors$info, 
                                "Boycott Start" = colors$warning,
                                "Post-Boycott" = colors$negative)) +
  labs(
    title = "Organic Revenue Performance (Constant Currency)",
    subtitle = "Removes FX effects to show true operational performance",
    x = "Year",
    y = "Revenue at Constant 2020 FX (Million €)",
    fill = "Period"
  ) +
  scale_x_continuous(breaks = organic_comparison$year) +
  annotate("segment", x = 2023.5, xend = 2023.5, y = 330, yend = 380,
           linetype = "dashed", color = "red", linewidth = 0.8) +
  annotate("text", x = 2020.3, y = baseline_revenue_constant, 
           label = paste0("Baseline: ", round(baseline_revenue_constant, 0), "M€"),
           hjust = 0, vjust = -0.5, size = 3)

p_organic_gap <- organic_comparison %>%
  filter(year >= 2021) %>%
  ggplot(aes(x = year, y = deviation, fill = deviation < 0)) +
  geom_col(width = 0.6) +
  geom_text(aes(label = paste0(ifelse(deviation > 0, "+", ""), 
                                round(deviation, 0), "M€"),
                vjust = ifelse(deviation > 0, -0.5, 1.5)),
            size = 3.5) +
  scale_fill_manual(values = c(colors$negative, colors$positive), guide = "none") +
  labs(
    title = "Organic Revenue Gap vs Pre-Boycott Baseline",
    subtitle = "FX-adjusted deviation from 2021-2022 average",
    x = "Year",
    y = "Deviation (Million €)"
  ) +
  scale_x_continuous(breaks = seq(2021, 2024, 1)) +
  geom_hline(yintercept = 0, linetype = "solid", color = "black")

p_organic / p_organic_gap

9.6 Complete Impact Breakdown: Operations + Currency

# Calculate 2024 impact components
baseline_revenue_reported <- baseline_metrics$baseline_revenue
actual_2024_constant <- ulysse_analysis$revenue_constant_fx[ulysse_analysis$year == POST_BOYCOTT_YEAR]
actual_2024_reported <- ulysse_analysis$revenue[ulysse_analysis$year == POST_BOYCOTT_YEAR]

organic_impact <- actual_2024_constant - baseline_revenue_constant
fx_impact_2024 <- actual_2024_reported - actual_2024_constant
total_impact <- actual_2024_reported - baseline_revenue_reported

impact_breakdown <- tribble(
  ~Component, ~Impact_MEur, ~Percentage,
  "2021-2022 Average Revenue (Baseline)", baseline_revenue_reported, 100.0,
  "Organic/Operational Change (Constant FX)", organic_impact, (organic_impact/baseline_revenue_reported)*100,
  "Currency Translation Effect", fx_impact_2024, (fx_impact_2024/baseline_revenue_reported)*100,
  "Total Change (Reported)", total_impact, (total_impact/baseline_revenue_reported)*100,
  "2024 Actual Revenue", actual_2024_reported, (actual_2024_reported/baseline_revenue_reported)*100
)

impact_breakdown %>%
  gt() %>%
  tab_header(
    title = "2024 Revenue Change Breakdown: Operational vs Currency Effects",
    subtitle = "Comparing 2024 to Pre-Boycott Average (2021-2022)"
  ) %>%
  cols_label(
    Component = "",
    Impact_MEur = "Amount (M€)",
    Percentage = "% of Baseline"
  ) %>%
  fmt_number(
    columns = Impact_MEur,
    decimals = 1
  ) %>%
  fmt_number(
    columns = Percentage,
    decimals = 1,
    suffix = "%"
  ) %>%
  tab_style(
    style = cell_fill(color = "#f0f0f0"),
    locations = cells_body(rows = Component %in% c("2021-2022 Average Revenue (Baseline)", "2024 Actual Revenue"))
  ) %>%
  tab_style(
    style = cell_fill(color = "#ffe6e6"),
    locations = cells_body(rows = Component == "Organic/Operational Change (Constant FX)")
  ) %>%
  tab_style(
    style = cell_fill(color = "#fff3cd"),
    locations = cells_body(rows = Component == "Currency Translation Effect")
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(rows = Component == "Total Change (Reported)")
  )
2024 Revenue Change Breakdown: Operational vs Currency Effects
Comparing 2024 to Pre-Boycott Average (2021-2022)
Amount (M€) % of Baseline
2021-2022 Average Revenue (Baseline) 376.0 100.0
Organic/Operational Change (Constant FX) 5.6 1.5
Currency Translation Effect 7.6 2.0
Total Change (Reported) −1.0 −0.3
2024 Actual Revenue 375.0 99.7

10. Key Findings and Strategic Implications

10.1 Summary of Financial Impact

# Calculate key impact metrics
organic_decline_pct <- (organic_impact / baseline_revenue_constant) * 100
fx_benefit_pct <- (fx_impact_2024 / baseline_revenue_reported) * 100
margin_decline_bps <- (baseline_metrics$baseline_margin - 
                        ulysse_analysis$net_margin[ulysse_analysis$year == POST_BOYCOTT_YEAR])

summary_metrics <- tribble(
  ~Metric, ~Value, ~Interpretation,
  "Organic Revenue Decline", paste0(round(abs(organic_decline_pct), 1), "%"), "True operational impact",
  "FX Translation Benefit", paste0("+", round(fx_benefit_pct, 1), "%"), "Currency tailwind masking decline",
  "Net Reported Revenue Change", paste0(round((total_impact/baseline_revenue_reported)*100, 1), "%"), "Appears stable but misleading",
  "Net Income Decline", "44%", "Severe profitability crisis",
  "Margin Compression", paste0(round(margin_decline_bps, 1), " pp"), "From 2.4% to 1.3%",
  "ROE Decline", "47%", "From 9.4% to 5.0%",
  "Estimated Profit Loss", paste0(round(profit_loss, 1), "M€"), "vs historical margin baseline"
)

summary_metrics %>%
  gt() %>%
  tab_header(
    title = "Boycott Campaign Impact: Summary Metrics",
    subtitle = "Comprehensive assessment of 2024 vs 2021-2022 baseline"
  ) %>%
  cols_label(
    Metric = "Metric",
    Value = "2024 Impact",
    Interpretation = "Interpretation"
  ) %>%
  tab_style(
    style = cell_fill(color = "#ffe6e6"),
    locations = cells_body(rows = Metric %in% c("Net Income Decline", "Estimated Profit Loss"))
  ) %>%
  tab_style(
    style = cell_fill(color = "#fff3cd"),
    locations = cells_body(rows = Metric == "FX Translation Benefit")
  )
Boycott Campaign Impact: Summary Metrics
Comprehensive assessment of 2024 vs 2021-2022 baseline
Metric 2024 Impact Interpretation
Organic Revenue Decline 1.5% True operational impact
FX Translation Benefit +2% Currency tailwind masking decline
Net Reported Revenue Change -0.3% Appears stable but misleading
Net Income Decline 44% Severe profitability crisis
Margin Compression 1.1 pp From 2.4% to 1.3%
ROE Decline 47% From 9.4% to 5.0%
Estimated Profit Loss 4M€ vs historical margin baseline

Appendix: Data Sources and Methodology

Data Sources

  • Financial data: Company financial statements 2020-2024
  • Exchange rates: European Central Bank reference rates (annual averages)
  • Boycott timeline: Public reports of October 7, 2023 campaign initiation

Methodology Notes

Constant Currency Calculations: - Base year: 2020 (EUR/TND rate of 3.45) - Formula: Revenue (constant FX) = Revenue (reported) × (Current rate / Base rate) - This approach isolates operational performance from currency translation effects

Baseline Period Selection: - Pre-boycott baseline: 2021-2022 average - Rationale: 2020 excluded due to potential COVID-19 distortions - 2023 excluded as boycott began in Q4

Growth Calculations: - Reported growth: Year-over-year change in EUR-reported figures - Organic growth: Year-over-year change in constant currency figures - FX contribution: Difference between reported and organic growth

Limitations: - Annual data only; quarterly granularity would improve boycott impact precision - Limited to 5 years of data; longer time series would strengthen trend analysis - No external market data for competitive context - Exchange rates are annual averages; intra-year volatility not captured