diff --git a/ew_projects_run3/ftuple.py b/ew_projects_run3/ftuple.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2d4025c819a529706d05df190febd3fa78fe0b9
--- /dev/null
+++ b/ew_projects_run3/ftuple.py
@@ -0,0 +1,128 @@
+from PyConf.reading import (get_particles, get_pvs, get_rec_summary,
+                            get_mc_particles)
+from RecoConf.event_filters import require_pvs
+from DaVinci import make_config, Options
+from DaVinci.algorithms import create_lines_filter
+from FunTuple import FunTuple_Particles as Funtuple
+from FunTuple import FunTuple_MCParticles as MCFuntuple
+from FunTuple import FunTuple_Event as EventFuntuple
+from FunTuple import FunctorCollection
+import FunTuple.functorcollections as FC
+import Functors as F
+from .ftuple_helpers import (
+    decaytree_variables, mcdecaytree_selection_to_variables, extra_event_info,
+    selection_to_decay_descriptor, mcdtt_selections_to_decay_descriptor, Spec,
+    rec_summary_variables, selection_dependent_filtering, trkeff_probes,
+    decaytreefitter_variables)
+
+hlt1_tistos_lines = ["Hlt1SingleHighPtMuon", "Hlt1SingleHighPtMuonNoMuID"]
+hlt2_tistos_lines = [
+    "Hlt2QEE_SingleHighPtMuonFull", "Hlt2QEE_SingleHighPtMuonNoMuIDFull",
+    "Hlt2QEE_ZToMuMuFull", "Hlt2QEE_ZToMuMu_SingleNoMuIDFull"
+]
+
+
+def hlt2_decision_lines(trkeff_methods):
+    return hlt2_tistos_lines + [
+        f"Hlt2TrackEff_ZToMuMu_{trkeff_method}_{probe}_{decision_type}"
+        for trkeff_method in trkeff_methods for probe in trkeff_probes
+        for decision_type in ["Tag", "Match"]
+    ]
+
+
+def make_DTT(selection, line, pvs, spec):
+    dec_desc = selection_to_decay_descriptor(
+        trkeff_methods=spec.trkeff_methods)[selection]
+    data = selection_dependent_filtering(
+        data=get_particles(f"/Event/{spec.dataLocProcess}/{line}/Particles"),
+        selection=selection)
+
+    variables = decaytree_variables(
+        selection=selection, pvs=pvs, data=data, spec=spec)
+    evtinfo = FC.EventInfo()
+    evtinfo += extra_event_info(spec=spec)
+    evtinfo += FC.SelectionInfo(
+        selection_type="Hlt1", trigger_lines=hlt1_tistos_lines)
+
+    evtinfo += rec_summary_variables(rec_summary=get_rec_summary())
+    evtinfo += FC.SelectionInfo(
+        selection_type="Hlt2",
+        trigger_lines=hlt2_decision_lines(spec.trkeff_methods))
+
+    variables["ALL"] += FC.HltTisTos(
+        selection_type="Hlt1",
+        trigger_lines=[f"{line}Decision" for line in hlt1_tistos_lines],
+        data=data)
+
+    if spec.isFull:
+        variables["ALL"] += FC.HltTisTos(
+            selection_type="Hlt2",
+            trigger_lines=[f"{line}Decision" for line in hlt2_tistos_lines],
+            data=data)
+
+    if spec.isTurCal:
+        dtf_variables = decaytreefitter_variables(
+            selection=selection, pvs=pvs, data=data)
+        for k in dtf_variables.keys():
+            variables[k] = variables.get(k, FunctorCollection(
+                {})) + dtf_variables[k]
+
+    funtuple = Funtuple(
+        name=selection,
+        tuple_name="DecayTree",
+        fields=dec_desc,
+        variables=variables,
+        inputs=data,
+        store_multiple_cand_info=True,
+        event_variables=evtinfo)
+
+    return funtuple
+
+
+def make_MCDTT(selection, spec):
+    dec_desc = mcdtt_selections_to_decay_descriptor[selection]
+    mc_particles = get_mc_particles("/Event/MC/Particles")
+
+    variables = mcdecaytree_selection_to_variables[selection]
+    variables["ALL"] = FunctorCollection({"ID": F.PARTICLE_ID})
+    evtinfo = extra_event_info(spec=spec)
+
+    return MCFuntuple(
+        name=f"{selection}_mcdtt",
+        tuple_name="DecayTree",
+        fields=dec_desc,
+        variables=variables,
+        event_variables=evtinfo,
+        inputs=mc_particles,
+    )
+
+
+def make_ET(spec):
+    return EventFuntuple(
+        name="EventTuple",
+        tuple_name="EventTuple",
+        variables=extra_event_info(spec=spec))
+
+
+def main(options: Options, stream: str, campaign: str, polarity: str,
+         sample: str):
+    spec = Spec(stream, campaign, polarity, sample)
+
+    algs = {}
+
+    for selection, line in spec.selection_line_map.items():
+        line_prefilter = create_lines_filter(
+            name=f"PreFilter_{line}", lines=[line])
+        pvs = get_pvs()
+        algs[selection] = [
+            line_prefilter,
+            require_pvs(pvs),
+            make_DTT(selection=selection, line=line, pvs=pvs, spec=spec)
+        ]
+    if spec.isMC: algs["EventTuple"] = [make_ET(spec=spec)]
+    if spec.isMC and spec.isFull:
+        for mcdtt_selection in mcdtt_selections_to_decay_descriptor.keys():
+            algs[f"{mcdtt_selection}_MCDecayTree"] = [
+                make_MCDTT(selection=mcdtt_selection, spec=spec)
+            ]
+    return make_config(options, algs)
diff --git a/ew_projects_run3/ftuple_helpers.py b/ew_projects_run3/ftuple_helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..89685f8139f35cdc99923cc083ebb2c220e99801
--- /dev/null
+++ b/ew_projects_run3/ftuple_helpers.py
@@ -0,0 +1,410 @@
+from PyConf.reading import get_charged_protoparticles
+from PyConf.Algorithms import WeightedRelTableAlg, ThOrParticleSelection
+from DaVinciMCTools import MCTruthAndBkgCat
+from DecayTreeFitter import DecayTreeFitter
+from Hlt2Conf.lines.qee import single_high_pt_muon
+from Hlt2Conf.standard_particles import make_ismuon_long_muon
+from Hlt2Conf.standard_jets import make_onlytrack_particleflow, make_charged_particles
+from Hlt2Conf.algorithms_thor import ParticleFilter
+from GaudiKernel.SystemOfUnits import GeV
+from FunTuple import FunctorCollection
+import Functors as F
+from enum import Enum
+
+_Z_decay_descriptor = {
+    "Z0": "^(Z0 -> mu- mu+)",
+    "mum": "Z0 -> ^mu- mu+",
+    "mup": "Z0 -> mu- ^mu+",
+}
+_Jpsi_decay_descriptor = {
+    "Jpsi": "^(J/psi(1S) -> mu- mu+)",
+    "mum": "J/psi(1S) -> ^mu- mu+",
+    "mup": "J/psi(1S) -> mu- ^mu+",
+}
+_Upsilon_decay_descriptor = {
+    "U1S": "^(Upsilon(1S) -> mu- mu+)",
+    "mum": "Upsilon(1S) -> ^mu- mu+",
+    "mup": "Upsilon(1S) -> mu- ^mu+",
+}
+_Wp_decay_descriptor = {
+    "mu": '^mu+',
+}
+_Wm_decay_descriptor = {
+    "mu": '^mu-',
+}
+trkeff_probes = ["mum", "mup"]
+
+FSR_electron_descriptor = ' {e+} {e-} {e+} {e-} '
+mcdtt_selections_to_decay_descriptor = {
+    "Z": {
+        "Z": f"^(Z0 => mu- mu+ {FSR_electron_descriptor})",
+        "mum": f"Z0 => ^mu- mu+ {FSR_electron_descriptor}",
+        "mup": f"Z0 => mu- ^mu+ {FSR_electron_descriptor}",
+    },
+    "W": {
+        "W": f"^[W+ => mu+ nu_mu {FSR_electron_descriptor}]CC",
+        "mu": f"[W+ => ^mu+ nu_mu {FSR_electron_descriptor}]CC",
+    },
+    "Ztau": {
+        "Z":
+        f"^(Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor}))",
+        "taup":
+        f"Z0 => ^(tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})",
+        "mup":
+        f"Z0 => (tau+ => ^mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})",
+        "taum":
+        f"Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) ^(tau- => mu- nu_mu~ nu_tau {FSR_electron_descriptor})",
+        "mum":
+        f"Z0 => (tau+ => mu+ nu_mu nu_tau~ {FSR_electron_descriptor}) (tau- => ^mu- nu_mu~ nu_tau {FSR_electron_descriptor})",
+    },
+    "Wtau": {
+        "W":
+        f"^[W+ =>  (tau+ =>  mu+ nu_mu nu_tau~ {FSR_electron_descriptor})  nu_tau]CC",
+        "tau":
+        f"[W+ =>  ^(tau+ =>  mu+ nu_mu nu_tau~ {FSR_electron_descriptor})  nu_tau]CC",
+        "mu":
+        f"[W+ =>  (tau+ => ^mu+ nu_mu nu_tau~ {FSR_electron_descriptor})  nu_tau]CC",
+    }
+}
+
+_rapidity_functor = 0.5 * F.math.log((F.ENERGY + F.PZ) / (F.ENERGY - F.PZ))
+_mcdecaytree_lepton_variables = FunctorCollection({
+    "PT": F.PT,
+    "ETA": F.ETA,
+    "PHI": F.PHI
+})
+_mcdecaytree_boson_variables = FunctorCollection({
+    "M": F.MASS,
+    "PT": F.PT,
+    "Y": _rapidity_functor
+})
+mcdecaytree_selection_to_variables = {
+    "W": {
+        "mu": _mcdecaytree_lepton_variables
+    },
+    "Wtau": {
+        "mu": _mcdecaytree_lepton_variables,
+        "tau": _mcdecaytree_lepton_variables
+    },
+    "Z": {
+        "mum": _mcdecaytree_lepton_variables,
+        "mup": _mcdecaytree_lepton_variables,
+        "Z": _mcdecaytree_boson_variables,
+    },
+    "Ztau": {
+        "mum": _mcdecaytree_lepton_variables,
+        "mup": _mcdecaytree_lepton_variables,
+        "taum": _mcdecaytree_lepton_variables,
+        "taup": _mcdecaytree_lepton_variables,
+        "Z": _mcdecaytree_boson_variables,
+    },
+}
+
+
+def selection_to_decay_descriptor(trkeff_methods):
+    return {
+        "Z": _Z_decay_descriptor,
+        "ZMuID": _Z_decay_descriptor,
+        "Jpsi_Detached": _Jpsi_decay_descriptor,
+        "Jpsi_Prompt": _Jpsi_decay_descriptor,
+        "U1S": _Upsilon_decay_descriptor,
+        "Wp": _Wp_decay_descriptor,
+        "Wm": _Wm_decay_descriptor,
+        "WpNoMuID": _Wp_decay_descriptor,
+        "WmNoMuID": _Wm_decay_descriptor,
+        "ZSS": {
+            "Z0": "[Z0 -> mu- mu-]CC",
+            "mum": "[Z0 -> ^mu- mu-]CC",
+            "mup": "[Z0 -> mu- ^mu-]CC",
+        }
+    } | {
+        f"ZTrkEff_{trkeff_method}_{probe}": _Z_decay_descriptor
+        for trkeff_method in trkeff_methods for probe in trkeff_probes
+    }
+
+
+def rec_summary_variables(rec_summary):
+    # as per https://gitlab.cern.ch/lhcb/Rec/-/blob/master/Rec/RecAlgs/src/RecSummaryMaker.cpp?ref_type=heads#L71
+    _rec_summary_vars = [
+        "nPVs", "nLongTracks", "nFTClusters", "nVPClusters", "nEcalClusters",
+        "hCalTot"
+    ]
+    return FunctorCollection({
+        var: F.VALUE_OR(-1) @ F.RECSUMMARY_INFO(rec_summary, var)
+        for var in _rec_summary_vars
+    })
+
+
+def decaytree_variables(selection, pvs, spec, data):
+    variables = {}
+    variables["ALL"] = FunctorCollection({"PID": F.PARTICLE_ID})
+
+    muon_FC = FunctorCollection({
+        "BPVIP": F.BPVIP(pvs),
+        "BPVIPCHI2": F.BPVIPCHI2(pvs),
+        "TRCHI2NDOF": F.CHI2DOF,
+        "PV_X": F.BPVX(pvs),
+        "PV_Y": F.BPVY(pvs),
+        "PV_Z": F.BPVZ(pvs),
+        "Q": F.CHARGE,
+        "PX": F.PX,
+        "PY": F.PY,
+        "PZ": F.PZ,
+        "PERR2": F.PERR2(),
+        "ISMUON": F.ISMUON,
+        "INMUON": F.INMUON,
+        "PIDmu": F.PID_MU,
+        "HCALEOP": F.VALUE_OR(0) @ F.HCALEOP,
+        "ECALEOP": F.VALUE_OR(0) @ F.ELECTRONSHOWEREOP,
+    })
+    for particle in selection_to_decay_descriptor(
+            trkeff_methods=spec.trkeff_methods)[selection].keys():
+        if "mu" not in particle:  # These don't work for muons (no endvertex)
+            variables[particle] = FunctorCollection({
+                "ENDVCHI2":
+                F.CHI2 @ F.ENDVERTEX,
+                "ENDVCHI2DOF":
+                F.CHI2DOF @ F.ENDVERTEX,
+            })
+        else:
+            variables[particle] = muon_FC
+            if spec.doIsolation:
+                variables[particle] += _iso_variables(
+                    data=data, pref=particle, selection=selection)
+        if particle == "mu":  # This needs to only be Single muons (not DiMuons)
+            variables[particle] += _zveto_variables(data=data)
+
+    if spec.isMC:
+        # Necessary to prevent duplicated algorithms with different names
+        unique_selection_name = selection.replace('Wp', 'W').replace('Wm', 'W')
+        MCTRUTH = MCTruthAndBkgCat(
+            data, name=f"MCTruthAndBkgCat__{unique_selection_name}")
+        variables["ALL"] += FunctorCollection({
+            "TRUEPX":
+            MCTRUTH(F.PX),
+            "TRUEPY":
+            MCTRUTH(F.PY),
+            "TRUEPZ":
+            MCTRUTH(F.PZ),
+            "TRUEID":
+            F.VALUE_OR(0) @ MCTRUTH(F.PARTICLE_ID),
+        })
+    return variables
+
+
+def extra_event_info(spec):
+    """
+        Defines several event-level variables for later use.
+    """
+    return FunctorCollection({
+        'year_int': F.ALL() * 2024,
+        'isMC': F.ALL() * int(spec.isMC),
+        'polarity_sign': F.ALL() * spec.magpolInt
+    })
+
+
+def decaytreefitter_variables(selection, pvs, data):
+    v = {}
+    dtf__name = "DTF_AllPV"
+
+    dtf = DecayTreeFitter(
+        input_particles=data,
+        name="__".join([dtf__name, selection]),
+        input_pvs=pvs,
+        fit_all_pvs=True)
+
+    _dtf_FC = lambda d: FunctorCollection({
+        f"_{dtf__name}__{key}": dtf(functor)
+        for key, functor in d.items()
+    })
+    for muon in trkeff_probes:
+        v[muon] = _dtf_FC({
+            "P": F.P,
+            "PERR2": F.PERR2(),
+        })
+    v["Z0"] = _dtf_FC({
+        "MASS": F.MASS,
+        "CHI2DOF": F.VALUE_OR(-999) @ F.CHI2DOF,
+    })
+
+    return v
+
+
+def selection_dependent_filtering(data, selection):
+    if selection in streams_to_map()[Stream.Turbo]:
+        # https://gitlab.cern.ch/lhcb/Moore/-/merge_requests/3418#note_7953141
+        child_pidmu_cut = lambda i: F.CHILD(i, F.PID_MU) > -10
+        return ParticleFilter(
+            data,
+            F.FILTER(F.require_all(child_pidmu_cut(1), child_pidmu_cut(2))),
+            name=f'{selection}__pidmu_filter')
+    return data
+
+
+def _iso_variables(data, pref, selection):
+    """
+    For each candidate muon:
+        generate the particles within a cone with following cuts:
+            - radius 0.5 in (DPHI, DETA) space around signal candidate
+            - ~SHARE_TRACKS i.e. don't count our signal candidate
+        Defines:
+          ISO_C{PT,PX,PY} via _safe_sumcone
+    """
+    # extract muon particlecontainer from the main decay candidate.
+    mu_decdesc = {"mup": "mu+", "mum": "mu-", "mu": "mu"}[pref]
+    isDimuon = "W" not in selection
+    mu_particles = _extract_muons_from_composite_candidate(
+        data=data, decdesc=mu_decdesc) if isDimuon else data
+
+    with make_charged_particles.bind(
+            make_protoparticles=get_charged_protoparticles):
+        input_cands = make_onlytrack_particleflow()
+    alg = WeightedRelTableAlg(
+        ReferenceParticles=mu_particles,
+        InputCandidates=input_cands,
+        Cut=F.require_all(F.DR2 < (0.5**2), ~F.SHARE_TRACKS()))
+    return FunctorCollection({
+        "ISO050_CPT": _safe_sumcone(alg, F.PT),
+        "ISO050_CPX": _safe_sumcone(alg, F.PX),
+        "ISO050_CPY": _safe_sumcone(alg, F.PY),
+    })
+
+
+def _zveto_variables(data):
+    """
+       Only makes sense for W-like candidates, i.e. not Z-like.
+    """
+    min_muon_pt = 0.1 * GeV  # Just a sanity cut
+    input_cands = ParticleFilter(
+        make_ismuon_long_muon(),
+        F.FILTER(F.require_all(F.PT > min_muon_pt)),
+        name='ZVeto_muon_filter')
+
+    alg = WeightedRelTableAlg(
+        ReferenceParticles=data,
+        InputCandidates=input_cands,
+        Cut=F.require_all(~F.SHARE_TRACKS()))
+
+    zveto_muon_variables = {"PX": F.PX, "PY": F.PY, "PZ": F.PZ, "Q": F.CHARGE}
+
+    def _zveto_muon_functor_composition(functor, relations):
+        return F.VALUE_OR(0) @ functor @ F.TO @ F.ENTRY_WITH_MAX_REL_VALUE_OF(
+            F.PT @ F.TO @ F.FORWARDARG0).bind(
+                F.RELATIONS.bind(F.TES(relations), F.FORWARDARGS))
+
+    return FunctorCollection({
+        f"OTHERMUON_{var}": _zveto_muon_functor_composition(
+            functor=functor, relations=alg.OutputRelations)
+        for var, functor in zveto_muon_variables.items()
+    })
+
+
+def _extract_muons_from_composite_candidate(data, decdesc):
+    """
+        Navigates the descendants of a candidate particle
+        Returns the child muons for that candidate.
+    """
+    return ThOrParticleSelection(
+        InputParticles=data,
+        Functor=F.FILTER(F.IS_ID(decdesc))
+        @ F.GET_ALL_DESCENDANTS()).OutputSelection
+
+
+def _safe_sumcone(reltable_alg, functor):
+    """
+        Given a relation table and a functor:
+            Returns SUM(functor) over all particles within that relation table.
+        Safe refers to giving the physical invalid value of 0 if the relation is empty (i.e. instead of NaN)
+    """
+    return F.VALUE_OR(0) @ F.SUMCONE(
+        Functor=functor, Relations=reltable_alg.OutputRelations)
+
+
+class Sample(Enum):
+    MC = 0
+    DATA = 1
+
+
+class Campaign(Enum):
+    s24c1 = 1
+    s24c2 = 2
+    s24c3 = 3
+
+
+class Stream(Enum):
+    Full = 1
+    Turbo = 2
+    TurCal = 3
+
+
+class MagPol(Enum):
+    MagUp = 1
+    MagDown = -1
+
+
+def _data_loc_process(stream_enum):
+    return {
+        Stream.Full: 'Spruce',
+        Stream.Turbo: 'HLT2',
+        Stream.TurCal: 'Spruce'
+    }[stream_enum]
+
+
+def _do_isolation(stream_enum):
+    return {
+        Stream.Full: True,
+        Stream.Turbo: False,
+        Stream.TurCal: True
+    }[stream_enum]
+
+
+def _trkeff_methods(campaign_enum):
+    m_withoutUT = ["VeloMuon", "SeedMuon"]
+    m_withUT = m_withoutUT + ["MuonUT", "Downstream"]
+    return {
+        Campaign.s24c1: m_withoutUT,
+        Campaign.s24c2: m_withUT,
+        Campaign.s24c3: m_withUT
+    }[campaign_enum]
+
+
+def streams_to_map(trkeff_methods=[""]):
+    return {  # Stream : { selection : linename }
+        Stream.Turbo: {
+            "Jpsi_Detached": "Hlt2QEE_JpsiToMuMu_Detached",
+            "Jpsi_Prompt": "Hlt2QEE_JpsiToMuMu_Prompt",
+            "U1S": "Hlt2QEE_Upsilon1SToMuMu",
+        },
+        Stream.Full: {
+            "Z": "SpruceQEE_ZToMuMu",
+            "ZMuID": "SpruceQEE_ZToMuMu_SingleNoMuID",
+            "Wp": "SpruceQEE_SingleHighPtMuon",
+            "Wm": "SpruceQEE_SingleHighPtMuon",
+            "WpNoMuID": "SpruceQEE_SingleHighPtMuonNoMuID",
+            "WmNoMuID": "SpruceQEE_SingleHighPtMuonNoMuID",
+            "ZSS": "SpruceQEE_DiMuonSameSign",
+        },
+        Stream.TurCal: {
+            f"ZTrkEff_{trkeff_method}_{probe}":
+            f"SpruceTurCalTrackEff_ZToMuMu_{trkeff_method}_{probe}_Tag"
+            for trkeff_method in trkeff_methods for probe in trkeff_probes
+        }
+    }
+
+
+class Spec():
+    def __init__(self, stream, campaign, magpol, sample):
+        stream_enum = Stream[stream]
+        campaign_enum = Campaign[f's{campaign}']
+        magpol_enum = MagPol[magpol]
+        sample_enum = Sample[sample]
+
+        self.isFull = stream_enum == Stream.Full
+        self.isTurCal = stream_enum == Stream.TurCal
+        self.isMC = sample_enum == Sample.MC
+        self.magpolInt = magpol_enum.value
+        self.trkeff_methods = _trkeff_methods(campaign_enum)
+        self.selection_line_map = streams_to_map(
+            self.trkeff_methods)[stream_enum]
+        self.dataLocProcess = _data_loc_process(stream_enum)
+        self.doIsolation = _do_isolation(stream_enum)
diff --git a/ew_projects_run3/info.yaml b/ew_projects_run3/info.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..56b3a3f77eac9672a1cac3c56130f866fbfb7983
--- /dev/null
+++ b/ew_projects_run3/info.yaml
@@ -0,0 +1,85 @@
+defaults:
+  inform:
+    - luke.grazette@cern.ch
+  wg: QEE
+
+################
+##### DATA #####
+################
+
+
+# https://twiki.cern.ch/twiki/bin/viewauth/LHCbInternal/OperationSummary2024
+{%- set processing_campaigns = [
+  ('24c2', '/Real Data/Sprucing24c2', 'v64r9')
+]%}
+
+
+##############
+##### MC #####
+##############
+
+{%- set davinci_detdesc_platform = 'x86_64_v2-el9-clang16+detdesc-opt' %} # a `v2` detdesc platform compatible with DV release.
+{%- set dddb_tag                 = 'dddb-20240427' %}
+{%- set candidate_datasets       = {
+  'Turbo': [
+    ('Jpsi', '24142001'),
+    ('U1S' , '18112001'),
+  ],
+  'Full': [
+    ('Z'     , '42112001'),
+    ('Ztau'  , '42100004'),
+    ('Wtau'  , '42300001'),
+    ('W'     , '42311003'),
+    ('DY'    , '42112015'),
+    ('QcdBgd', '49000011'),
+    ('ccbar' , '49011014'),
+    ('bbbar' , '49011015')
+  ],
+  'TurCal': [
+    ('Z'   , '42112001'),
+  ]
+} %}
+{%- set stream_variables = [
+  ('Turbo', 'TurboPass'),
+  ('Full'  , 'Spruce'),
+  ('TurCal', 'Spruce')
+] %}
+{%- set polarity_variables = [
+  ('MagUp'  , 'sim10-2024.Q3.4-v1.3-mu100')
+] %}
+
+
+{%- for polarity, conddb_tag in polarity_variables %}
+  {%- set mc_cond_path = '/MC/2024/Beam6800GeV-2024.W31.34-' + polarity + '-Nu6.3-25ns-Pythia8/Sim10d/HLT2-2024.W31.34' %}
+  {%- for stream, dv_input_process in stream_variables %}
+    {%- for eventtype, eventcode in candidate_datasets[stream] %}
+      {%- for campaign, _, tuple_v in processing_campaigns %}
+
+
+MC{{campaign}}_{{polarity}}_{{eventtype}}_{{stream}}_FTuple:
+  application: "DaVinci/{{tuple_v}}@{{davinci_detdesc_platform}}"
+  input:
+    bk_query: '{{mc_cond_path}}/{{eventcode}}/DST'
+  output: FTupleQEE.root
+  options:
+    entrypoint: ew_projects_run3.ftuple:main
+    extra_options:
+      input_raw_format: 0.5
+      input_type: ROOT
+      simulation: True
+      data_type: "Upgrade"
+      conddb_tag: {{conddb_tag}}
+      dddb_tag: {{dddb_tag}}
+      input_process: {{dv_input_process}}
+      geometry_version: run3/2024.Q1.2-v00.00
+    extra_args:
+      - --
+      - {{stream}}
+      - {{campaign}}
+      - {{polarity}}
+      - MC
+
+      {%- endfor %}
+    {%- endfor %}
+  {%- endfor %}
+{%- endfor %}