Forecasts from the ECDC FCH

from matplotlib import pyplot as plt
from tqdm.notebook import tqdm
from pyprojroot import here
from datetime import date

min_date = date(2020, 10, 12)
max_date = date(2021, 9, 15)
import pandas as pd


def forecast_long_to_wide(df: pd.DataFrame) -> pd.DataFrame:
    # Quantile column mapping
    forecast_quantiles = [
        0.01,
        0.025,
        *[round(0.05 * q, 3) for q in range(1, 20)],
        0.975,
        0.99,
    ]
    col_map = {q: f"{q * 100:.1f} %" for q in forecast_quantiles}

    # Pivot quantile rows
    q = df[df["type"] == "quantile"].copy()
    q["quantile"] = pd.to_numeric(q["quantile"])
    piv = (
        q[q["quantile"].isin(col_map)]
        .pivot(index="target_end_date", columns="quantile", values="value")
        .rename(columns=col_map)
    )

    # Add mean and sd (if present)
    res = piv.copy()
    means = (
        df[df["type"] == "mean"][["target_end_date", "value"]]
        .drop_duplicates()
        .set_index("target_end_date")
        .rename(columns={"value": "mean"})
    )
    res = res.join(means)

    # Set final column order and output
    cols = ["mean", *col_map.values()]
    out = res[cols].reset_index().rename(columns={"target_end_date": "date"})
    return out
import os
from pathlib import Path
import subprocess
import pandas as pd


def standardize_forecast_dfs(daily_predictions: pd.DataFrame):

    query_string = (
        "target == '1 wk ahead inc case' and (location == 'DE' or location == 'GM')"
    )

    wide_df = forecast_long_to_wide(
        daily_predictions.query(query_string)[
            ["target_end_date", "type", "quantile", "value"]
        ]
    )

    wide_df["date"] = pd.to_datetime(wide_df["date"], format="%Y-%m-%d").dt.date

    return wide_df[(wide_df["date"] >= min_date) & (wide_df["date"] <= max_date)]


def read_fch_submissions(
    model_dir: str, repo_url: str, clone_dir: Path
) -> pd.DataFrame:
    if not clone_dir.exists():
        subprocess.run(
            ["git", "clone", "--depth", "1", repo_url, str(clone_dir)], check=True
        )

    csv_dir_path = clone_dir / model_dir
    csv_files = [
        csv_dir_path / file
        for file in os.listdir(csv_dir_path)
        if file.endswith(".csv")
    ]

    combined_df = pd.concat(
        [
            pd.read_csv(file_path).assign(filename=file_path.name)
            for file_path in csv_files
        ],
        ignore_index=True,
    )

    return standardize_forecast_dfs(combined_df)
# Define repository and local clone directory
model_names = ["ITWW-county_repro", "EuroCOVIDhub-baseline", "EuroCOVIDhub-ensemble"]

models_ecdc = {
    name: read_fch_submissions(
        f"data-processed/{name}",
        "https://github.com/european-modelling-hubs/covid19-forecast-hub-europe_archive.git",
        Path("/tmp/covid19-forecast-hub-europe_archive"),
    )
    for name in model_names
}

itww_county_repro = models_ecdc["ITWW-county_repro"]
baseline = models_ecdc["EuroCOVIDhub-baseline"]
ensemble = models_ecdc["EuroCOVIDhub-ensemble"]
for model, df in models_ecdc.items():
    df.to_csv(here(f"data/processed/ECDC_FCH_{model}.csv"), index=False)

Forecasts from the German FCH

from datetime import timedelta


fch_repo_url = "https://github.com/KITmetricslab/covid19-forecast-hub-de"


def fch_data_url(model: str, forecast_date: date) -> str:
    submission_date = (forecast_date + timedelta(days=-5)).strftime("%Y-%m-%d")
    return f"https://raw.githubusercontent.com/KITmetricslab/covid19-forecast-hub-de/refs/heads/master/data-processed/{model}/{submission_date}-Germany-{model}-case.csv"


forecast_dates = pd.date_range(
    start=date(2020, 10, 12), end=date(2021, 9, 15), freq="W-SAT"
).date.tolist()

fch_models = ["KIT-baseline", "ITWW-county_repro", "KITCOVIDhub-median_ensemble"]
fch_dfs = {
    model: pd.concat(
        [
            pd.read_csv(fch_data_url(model, fd))
            for fd in tqdm(forecast_dates, desc=f"Reading {model}")
        ]
    )
    for model in fch_models
}
standardize_forecast_dfs(fch_dfs["KITCOVIDhub-median_ensemble"])
date mean 1.0 % 2.5 % 5.0 % 10.0 % 15.0 % 20.0 % 25.0 % 30.0 % ... 60.0 % 65.0 % 70.0 % 75.0 % 80.0 % 85.0 % 90.0 % 95.0 % 97.5 % 99.0 %
0 2020-10-17 NaN 21539.423788 22569.355225 23568.090312 24586.979322 25139.325838 25561.499631 25932.539485 26277.073961 ... 28591.117862 29302.140058 30152.710629 31079.838904 32123.809361 33356.524172 34932.633915 37322.459967 39450.101466 41991.884426
1 2020-10-24 NaN 36259.974108 40010.212500 43312.925000 47192.300000 49605.825000 51872.358536 52952.611707 53923.109962 ... 58291.199381 58716.924259 59153.412378 59671.920685 60831.195953 62635.234335 64701.151489 67693.593562 70602.392253 74215.002332
2 2020-10-31 NaN 71773.589517 73638.165669 75280.502599 77219.523177 79654.000000 83829.000000 87584.000000 90963.000000 ... 107475.000000 110180.000000 112972.000000 116318.000000 119771.000000 123925.000000 126976.861592 130891.717572 133932.255716 138512.637213
3 2020-11-07 NaN 128928.056101 131639.839424 134138.933859 138871.261618 141210.839387 143696.640767 145158.876872 146898.054911 ... 153972.172672 155487.580999 156963.110160 159133.790947 163005.450209 167518.341931 173196.590203 181612.609510 188912.251145 197399.662078
4 2020-11-14 NaN 119952.026330 129980.150000 136067.000000 137503.000000 138904.000000 140194.000000 141949.000000 147334.000000 ... 173572.000000 177833.000000 182631.000000 187491.000000 193217.000000 199890.000000 207928.000000 219977.000000 231903.000000 246066.000000
5 2020-11-21 NaN 97396.242482 104060.481932 110108.458379 115683.488800 119223.418142 120945.000000 122059.000000 123346.000000 ... 136235.901986 139750.219440 146430.900000 153778.250000 163680.600000 170636.000000 177966.000000 187194.000000 197020.000000 208404.000000
6 2020-11-28 NaN 75040.120473 82538.858916 89002.873287 97030.960444 102229.248647 106634.864174 110372.743812 113812.616748 ... 129905.667002 132834.295917 137257.829453 143179.904368 147545.934159 152884.334404 159796.770812 170003.316064 179230.239236 190778.863126
7 2020-12-05 NaN 99073.714364 103654.377174 106429.500825 110397.726206 113170.402772 115296.137416 117413.526398 118612.429230 ... 123892.058085 124821.077343 125814.051913 129138.353260 130552.346895 132309.689481 134185.351891 136810.836639 138873.171844 144911.747438
8 2020-12-12 NaN 102028.092074 106971.000000 110320.900000 115605.200000 118121.700000 120789.500000 123099.200000 125180.900000 ... 136966.944209 137670.389527 141174.700000 145046.100000 147937.200000 151471.400000 154830.700000 162936.000000 168541.900000 174167.200000
9 2020-12-19 NaN 118050.856771 123185.524846 127674.122385 132990.545650 136580.749582 139517.928723 141841.483594 143634.146435 ... 163604.357360 166131.343093 170999.825897 179230.857426 181605.922234 186100.779239 189526.778717 197975.255846 201728.712142 205654.302086
10 2020-12-26 NaN 148252.940110 155455.602883 159761.985631 164009.878798 166702.444583 170163.928050 174883.302868 179017.020334 ... 195915.843768 200000.375000 206957.050000 209845.350721 213586.108320 218212.550535 224385.260867 234773.215769 242729.132301 253027.603250
11 2021-01-02 NaN 132075.689996 136473.511807 140014.768738 145177.816583 147687.264178 149729.869615 151226.278139 154574.085135 ... 178937.000000 182924.500000 188915.000000 194982.000000 201024.000000 214203.000000 221485.500000 229500.050000 237832.600000 244060.800000
12 2021-01-09 NaN 70866.000000 73220.000000 75230.970694 89936.632948 99858.473456 107744.037762 110582.000000 113713.000000 ... 134039.000000 136517.168760 138530.218154 141134.250000 143679.000000 147026.000000 153446.000000 157519.000000 168203.000000 174302.000000
13 2021-01-16 NaN 108226.842253 116490.412037 120290.786069 123473.865113 125682.614486 127926.356360 131371.871120 133982.655333 ... 151166.717929 154642.301674 158108.866648 163738.650933 175791.735578 182563.520762 187019.207026 194751.634072 208596.830790 222159.902957
14 2021-01-23 NaN 90075.738919 91272.394498 92656.279309 97069.706332 99886.312797 101186.952415 102671.828385 104347.425973 ... 117602.352819 122909.187183 128818.443137 133090.953468 137862.039075 143441.468364 150045.428596 153450.667358 159628.766128 166703.621357
15 2021-01-30 NaN 65036.947862 68207.400000 70682.000000 75393.000000 77368.000000 79157.000000 81480.943035 83039.536978 ... 92997.600000 98667.660132 100923.091691 103357.056965 106067.392719 109226.620058 113201.649437 119093.241773 124203.319488 130144.889176
16 2021-02-06 NaN 49254.540013 51787.469208 54078.526834 56861.941243 60818.304772 63168.000000 64366.000000 65382.000000 ... 73209.000000 74270.955065 75502.550201 77455.000000 79177.000000 81298.000000 83850.000000 88527.601794 93628.147307 101838.781549
17 2021-02-13 NaN 37152.562107 42472.500000 46084.000000 48519.000000 50146.000000 51523.500000 52761.000000 53803.000000 ... 60470.000000 61562.500000 62734.500000 65672.473412 66966.335296 67612.915186 68556.088649 71154.904743 74633.539985 77320.857092
18 2021-02-20 NaN 29131.745604 30745.834812 33059.338448 35137.000000 36607.000000 37765.500000 38735.500000 39764.000000 ... 44152.397716 44896.039654 45691.950000 47712.500000 48803.500000 50149.500000 51187.489007 52538.011199 53841.000154 55551.777654
19 2021-02-27 NaN 32440.608145 34672.594315 36640.027133 38773.633470 40211.763804 41419.541312 43029.110674 44420.979548 ... 51590.496672 52336.586061 52842.945027 53569.401678 54279.415587 55359.038862 57116.031617 60116.754495 63417.636241 67299.315994
20 2021-03-06 NaN 38867.021435 41970.813456 44609.398411 47697.406302 49759.328652 51298.660577 52692.324868 54014.288939 ... 62707.542810 64158.636494 65580.181268 67697.940960 69721.834461 72136.862278 75053.685321 78083.349162 81543.116776 84947.224981
21 2021-03-13 NaN 43295.054531 45915.936245 48431.183635 51720.209739 53141.890491 54588.783254 56474.697103 57213.803903 ... 63459.833764 64187.616792 64898.389810 67452.250000 69625.700000 71560.100000 74920.668641 77019.056638 78236.492380 79623.940550
22 2021-03-20 NaN 59698.663030 62695.074834 65462.752913 68562.094894 70906.615580 72896.598992 74840.741240 76018.814346 ... 83223.926699 84573.455680 85900.111288 87375.443817 89225.215183 91389.720700 94271.765642 103324.000000 105626.000000 108030.099231
23 2021-03-27 NaN 91629.429609 95731.128826 97899.520468 101485.813628 104342.062915 105814.731737 107964.297444 109957.877910 ... 119970.956952 122231.000000 124409.500000 127860.750000 131525.526774 133654.754890 137138.000000 142763.000000 147123.000000 152442.046481
24 2021-04-03 NaN 101229.133062 105019.256629 108412.163841 114161.000000 115579.000000 119702.471564 124320.023278 128289.961503 ... 139738.760480 141280.649613 142541.000000 144406.751030 147532.852447 149964.264660 154913.925237 161930.286195 166704.806187 169925.840358

25 rows × 25 columns

for model, df in fch_dfs.items():
    standardize_forecast_dfs(df).to_csv(
        here(f"data/processed/KIT_FCH_{model}.csv"), index=False
    )

RKI truth

truth = (
    pd.read_csv(here("data/processed/RKI_county_weekly.csv"))
    .groupby("date")
    .agg(cases=("cases", "sum"))
)
truth["date"] = pd.to_datetime(truth.index, format="%Y-%m-%d").date
truth.reset_index(drop=True, inplace=True)
truth = truth[(truth["date"] >= min_date) & (truth["date"] <= max_date)]
assert all(truth.date.values == itww_county_repro.date.values)
assert all(truth.date.values == baseline.date.values)
assert all(truth.date.values == ensemble.date.values)
fig, axs = plt.subplots(3, figsize=(10, 10), sharex=True)

for ax, model_name in zip(axs, model_names):
    df = models[model_name]
    ax.plot(df.date, df["50.0 %"])
    ax.fill_between(df.date, df["25.0 %"], df["75.0 %"], color="gray", alpha=0.5)
    ax.fill_between(df.date, df["2.5 %"], df["97.5 %"], color="lightgray", alpha=0.2)
    ax.set_title(model_name)

    ax.plot(truth["date"], truth["cases"], color="black")
    ax.set_ylabel("Cases")
    ax.set_xlabel("Date")

plt.tight_layout()
plt.show()

pd.concat(
    (
        standardize_forecast_dfs(fch_dfs["KITCOVIDhub-median_ensemble"]),
        standardize_forecast_dfs(models_ecdc["EuroCOVIDhub-ensemble"]),
    ),
    ignore_index=True,
)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/scope.py:231, in Scope.resolve(self, key, is_local)
    230 if self.has_resolvers:
--> 231     return self.resolvers[key]
    233 # if we're here that means that we have no locals and we also have
    234 # no resolvers

File ~/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/lib/python3.10/collections/__init__.py:986, in ChainMap.__getitem__(self, key)
    985         pass
--> 986 return self.__missing__(key)

File ~/.local/share/uv/python/cpython-3.10.17-macos-aarch64-none/lib/python3.10/collections/__init__.py:978, in ChainMap.__missing__(self, key)
    977 def __missing__(self, key):
--> 978     raise KeyError(key)

KeyError: 'target'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/scope.py:242, in Scope.resolve(self, key, is_local)
    238 try:
    239     # last ditch effort we look in temporaries
    240     # these are created when parsing indexing expressions
    241     # e.g., df[df > 0]
--> 242     return self.temps[key]
    243 except KeyError as err:

KeyError: 'target'

The above exception was the direct cause of the following exception:

UndefinedVariableError                    Traceback (most recent call last)
Cell In[57], line 4
      1 pd.concat(
      2     (
      3         standardize_forecast_dfs(fch_dfs["KITCOVIDhub-median_ensemble"]),
----> 4         standardize_forecast_dfs(models_ecdc["EuroCOVIDhub-ensemble"]),
      5     ),
      6     ignore_index=True,
      7 )

Cell In[47], line 14, in standardize_forecast_dfs(daily_predictions)
      7 def standardize_forecast_dfs(daily_predictions: pd.DataFrame):
      9     query_string = (
     10         "target == '1 wk ahead inc case' and (location == 'DE' or location == 'GM')"
     11     )
     13     wide_df = forecast_long_to_wide(
---> 14         daily_predictions.query(query_string)[
     15             ["target_end_date", "type", "quantile", "value"]
     16         ]
     17     )
     19     wide_df["date"] = pd.to_datetime(wide_df["date"], format="%Y-%m-%d").dt.date
     21     return wide_df[(wide_df["date"] >= min_date) & (wide_df["date"] <= max_date)]

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/frame.py:4823, in DataFrame.query(self, expr, inplace, **kwargs)
   4821 kwargs["level"] = kwargs.pop("level", 0) + 1
   4822 kwargs["target"] = None
-> 4823 res = self.eval(expr, **kwargs)
   4825 try:
   4826     result = self.loc[res]

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/frame.py:4949, in DataFrame.eval(self, expr, inplace, **kwargs)
   4946     kwargs["target"] = self
   4947 kwargs["resolvers"] = tuple(kwargs.get("resolvers", ())) + resolvers
-> 4949 return _eval(expr, inplace=inplace, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/eval.py:336, in eval(expr, parser, engine, local_dict, global_dict, resolvers, level, target, inplace)
    327 # get our (possibly passed-in) scope
    328 env = ensure_scope(
    329     level + 1,
    330     global_dict=global_dict,
   (...)
    333     target=target,
    334 )
--> 336 parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
    338 if engine == "numexpr" and (
    339     is_extension_array_dtype(parsed_expr.terms.return_type)
    340     or getattr(parsed_expr.terms, "operand_types", None) is not None
   (...)
    344     )
    345 ):
    346     warnings.warn(
    347         "Engine has switched to 'python' because numexpr does not support "
    348         "extension array dtypes. Please set your engine to python manually.",
    349         RuntimeWarning,
    350         stacklevel=find_stack_level(),
    351     )

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:805, in Expr.__init__(self, expr, engine, parser, env, level)
    803 self.parser = parser
    804 self._visitor = PARSERS[parser](self.env, self.engine, self.parser)
--> 805 self.terms = self.parse()

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:824, in Expr.parse(self)
    820 def parse(self):
    821     """
    822     Parse an expression.
    823     """
--> 824     return self._visitor.visit(self.expr)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:417, in BaseExprVisitor.visit_Module(self, node, **kwargs)
    415     raise SyntaxError("only a single expression is allowed")
    416 expr = node.body[0]
--> 417 return self.visit(expr, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:420, in BaseExprVisitor.visit_Expr(self, node, **kwargs)
    419 def visit_Expr(self, node, **kwargs):
--> 420     return self.visit(node.value, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:742, in BaseExprVisitor.visit_BoolOp(self, node, **kwargs)
    739     return self._maybe_evaluate_binop(op, node.op, lhs, rhs)
    741 operands = node.values
--> 742 return reduce(visitor, operands)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:735, in BaseExprVisitor.visit_BoolOp.<locals>.visitor(x, y)
    734 def visitor(x, y):
--> 735     lhs = self._try_visit_binop(x)
    736     rhs = self._try_visit_binop(y)
    738     op, op_class, lhs, rhs = self._maybe_transform_eq_ne(node, lhs, rhs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:731, in BaseExprVisitor._try_visit_binop(self, bop)
    729 if isinstance(bop, (Op, Term)):
    730     return bop
--> 731 return self.visit(bop)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:715, in BaseExprVisitor.visit_Compare(self, node, **kwargs)
    713     op = self.translate_In(ops[0])
    714     binop = ast.BinOp(op=op, left=node.left, right=comps[0])
--> 715     return self.visit(binop)
    717 # recursive case: we have a chained comparison, a CMP b CMP c, etc.
    718 left = node.left

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:531, in BaseExprVisitor.visit_BinOp(self, node, **kwargs)
    530 def visit_BinOp(self, node, **kwargs):
--> 531     op, op_class, left, right = self._maybe_transform_eq_ne(node)
    532     left, right = self._maybe_downcast_constants(left, right)
    533     return self._maybe_evaluate_binop(op, op_class, left, right)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:451, in BaseExprVisitor._maybe_transform_eq_ne(self, node, left, right)
    449 def _maybe_transform_eq_ne(self, node, left=None, right=None):
    450     if left is None:
--> 451         left = self.visit(node.left, side="left")
    452     if right is None:
    453         right = self.visit(node.right, side="right")

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:411, in BaseExprVisitor.visit(self, node, **kwargs)
    409 method = f"visit_{type(node).__name__}"
    410 visitor = getattr(self, method)
--> 411 return visitor(node, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/expr.py:541, in BaseExprVisitor.visit_Name(self, node, **kwargs)
    540 def visit_Name(self, node, **kwargs) -> Term:
--> 541     return self.term_type(node.id, self.env, **kwargs)

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/ops.py:91, in Term.__init__(self, name, env, side, encoding)
     89 tname = str(name)
     90 self.is_local = tname.startswith(LOCAL_TAG) or tname in DEFAULT_GLOBALS
---> 91 self._value = self._resolve_name()
     92 self.encoding = encoding

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/ops.py:115, in Term._resolve_name(self)
    110 if local_name in self.env.scope and isinstance(
    111     self.env.scope[local_name], type
    112 ):
    113     is_local = False
--> 115 res = self.env.resolve(local_name, is_local=is_local)
    116 self.update(res)
    118 if hasattr(res, "ndim") and res.ndim > 2:

File ~/workspace/work/phd/thesis/.venv/lib/python3.10/site-packages/pandas/core/computation/scope.py:244, in Scope.resolve(self, key, is_local)
    242     return self.temps[key]
    243 except KeyError as err:
--> 244     raise UndefinedVariableError(key, is_local) from err

UndefinedVariableError: name 'target' is not defined