diff --git a/Beauty_XS_Bs2DsPi/README.md b/Beauty_XS_Bs2DsPi/README.md
deleted file mode 100644
index 769ed08b696e40389437865e22cb34a3bab2cfa9..0000000000000000000000000000000000000000
--- a/Beauty_XS_Bs2DsPi/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Production of Bs -> Ds pi to run on 2024 collision data to include both up and down polarities
-
-This is for use in early measurements of Beauty XS
diff --git a/Beauty_XS_Bs2DsPi/dv_simple.py b/Beauty_XS_Bs2DsPi/dv_simple.py
deleted file mode 100644
index b4254db88035c1df0041355ffc4ef5974a6b8c2f..0000000000000000000000000000000000000000
--- a/Beauty_XS_Bs2DsPi/dv_simple.py
+++ /dev/null
@@ -1,252 +0,0 @@
-# Based on:
-# https://gitlab.cern.ch/lhcb/DaVinci/-/blob/v63r2/DaVinciExamples/python/DaVinciExamples/tupling/option_davinci_tupling_from_hlt2.py
-
-import Functors as F
-import FunTuple.functorcollections as FC
-from FunTuple import FunctorCollection, FunTuple_Particles as Funtuple
-from PyConf.reading import get_particles, get_pvs, get_rec_summary, get_odin
-#from PyConf.Algorithms import AdvancedCloneKiller, Run2SSPionTagger
-from GaudiKernel.SystemOfUnits import GeV
-from Hlt2Conf.algorithms_thor import ParticleFilter
-#from DaVinci.common_particles import make_long_pions
-from DaVinci.algorithms import create_lines_filter
-from DaVinci import Options, make_config
-from DaVinciMCTools import MCTruthAndBkgCat
-from DecayTreeFitter import DecayTreeFitter
-from Hlt2Conf.flavourTagging import run2_all_taggers
-# specific for the B2OC SigmaNet
-import Functors.math as fmath
-import os
-
-
-def alg_config(options: Options):
-
-    line = "Hlt2B2OC_BdToDsmPi_DsmToKpKmPim" 
-    line_data = get_particles(f"/Event/HLT2/{line}/Particles")
-    my_filter = create_lines_filter("Hlt2Line_Filter",
-                                    lines=[f"{line}"])
-    Hlt1_decisions = [
-        "Hlt1TrackMVADecision",
-        "Hlt1TwoTrackMVADecision",
-    ]
-    Hlt2_decisions = ['Hlt2B2OC_BdToDsmPi_DsmToKpKmPimDecision',
-                      'Hlt2Topo2BodyDecision',
-                      'Hlt2Topo3BodyDecision']
-
-    fields = {
-        "lab0": "[[B0]CC -> (D_s- -> K+ K- pi-) pi+]CC",
-        "lab1": "[[B0]CC -> (D_s- -> K+ K- pi-) ^pi+]CC",
-        "lab2": "[[B0]CC -> ^(D_s- -> K+ K- pi-) pi+]CC",
-        "lab3": "[[B0]CC -> (D_s- -> ^K+ K- pi-) pi+]CC",
-        "lab4": "[[B0]CC -> (D_s- -> K+ ^K- pi-) pi+]CC",
-        "lab5": "[[B0]CC -> (D_s- -> K+ K- ^pi-) pi+]CC",
-    }
-
-    pvs = get_pvs()
-
-    DTF_MassFitConsD = DecayTreeFitter(name="DTF_MassFitConsD",
-                                       input_particles=line_data,
-                                       mass_constraints=["D_s-"])
-    DTF_LifetimeFit = DecayTreeFitter(name="DTF_LifetimeFit",
-                                      input_particles=line_data,
-                                      input_pvs=pvs)
-
-    all_tagging = run2_all_taggers(line_data)
-
-    # define helper functors
-    get_child = F.CHILD(1, F.FORWARDARG0) # change here the index of the child
-    get_SV =  F.ENDVERTEX @ F.FORWARDARG0
-    get_SV_pos = F.TOLINALG @ F.POSITION @ get_SV # only if composite (i.e. has vertex)
-    get_child_endvtx_pos = F.ENDVERTEX_POS  @ get_child
-    get_fdvec_child = get_child_endvtx_pos - get_SV_pos
-
-    # define observables
-    IP_wrt_SV = F.IP.bind(get_SV_pos , get_child)
-    IPCHI2_wrt_SV = F.IPCHI2.bind(get_SV , get_child) # only if child is composite (i.e. has vertex)
-    FD_wrt_SV = F.MAGNITUDE @ get_fdvec_child
-    FDCHI2_wrt_SV = F.VTX_FDCHI2.bind(get_SV, get_child)
-
-    B_variables = FunctorCollection(
-        {
-        "ID": F.PARTICLE_ID,
-        "PT": F.PT,
-        "ETA": F.ETA,
-        "P": F.P,
-        "SUMPT": F.SUM(F.PT),
-        "MASS": F.MASS,
-        "BPVDIRA": F.BPVDIRA(pvs),
-        "CHI2DOF": F.CHI2DOF,
-        "BPVIPCHI2": F.BPVIPCHI2(pvs),
-        "BPVIP": F.BPVIP(pvs),
-        "BPVFDCHI2": F.BPVFDCHI2(pvs),
-        "BPVLTIME": F.BPVLTIME(pvs),
-        "BPVFD": F.BPVFD(pvs),
-        "CHILD1_IPwrtSV": IP_wrt_SV,
-        "CHILD1_IPCHI2wrtSV": IPCHI2_wrt_SV,
-        "CHILD1_FDwrtSV": FD_wrt_SV,
-        "CHILD1_FDCHI2wrtSV": FDCHI2_wrt_SV,
-        "DTF_MassFitConsD_MASS": DTF_MassFitConsD(F.MASS),
-        "DTF_MassFitConsD_CHI2DOF": DTF_MassFitConsD(F.CHI2DOF), # track or vertex chi2/ndf
-        "DTF_MassFitConsD_P": DTF_MassFitConsD(F.P),
-        "DTF_LifetimeFit_MASS": DTF_LifetimeFit(F.MASS),
-        "DTF_LifetimeFit_CHI2DOF": DTF_LifetimeFit(F.CHI2DOF), # track or vertex chi2/ndf
-        "DTF_LifetimeFit_CTAU": DTF_LifetimeFit.CTAU,
-        "DTF_LifetimeFit_CTAUERR": DTF_LifetimeFit.CTAUERR,
-        "PX": F.PX,
-        "PY": F.PY,
-        "PZ": F.PZ,
-        "BPVX": F.BPVX(pvs),
-        "BPVY": F.BPVY(pvs),
-        "BPVZ": F.BPVZ(pvs),
-        "END_VX": F.END_VX,
-        "END_VY": F.END_VY,
-        "END_VZ": F.END_VZ,
-        "END_VCHI2DOF": F.CHI2DOF @ F.ENDVERTEX,
-        "BPVCHI2DOF": F.CHI2DOF @ F.BPV(pvs),
-        # B2OC generic B hadron NN Hlt2 algorithm,
-        # not planning to use it directly for B2OC EM
-        "MVA": F.MVA(
-            MVAType="SigmaNet",
-            Config={
-                "File":
-                "paramfile://data/Hlt2B2OC_B_SigmaNet_Run3-v2.json",
-                "Name":
-                "B2OC_SigmaNet_Generic",
-                "Lambda":
-                "2.0",
-                "NLayers":
-                "3",
-                "InputSize":
-                "6",
-                "Monotone_Constraints":
-                "[1,-1,-1,-1,-1,-1]",
-                "Variables":
-                "log_B_PT,B_ETA,log_B_DIRA,log_B_ENDVERTEX_CHI2,log_B_IPCHI2_OWNPV,log_B_IP_OWNPV",
-            },
-            Inputs={
-                "log_B_PT": fmath.log(F.PT),
-                "B_ETA": F.ETA,
-                "log_B_DIRA": fmath.log(1. +1.e-6 - F.BPVDIRA(pvs)),
-                "log_B_ENDVERTEX_CHI2": fmath.log(F.CHI2DOF),
-                "log_B_IPCHI2_OWNPV": fmath.log(F.BPVIPCHI2(pvs)),
-                "log_B_IP_OWNPV": fmath.log(F.BPVIP(pvs)),
-            }),
-        }
-    )
-    B_variables+=FC.HltTisTos(selection_type="Hlt1", trigger_lines=Hlt1_decisions, data=line_data)
-    B_variables+=FC.FlavourTaggingResults(all_tagging)
-
-    C_variables = FunctorCollection(
-        {
-            "ID": F.PARTICLE_ID,
-            "PT": F.PT,
-            "ETA": F.ETA,
-            "P": F.P,
-            "SUMPT": F.SUM(F.PT),
-            "MASS": F.MASS,
-            "DOCA12": F.DOCA(1, 2),
-            "DOCA13": F.DOCA(1, 3),
-            "DOCA23": F.DOCA(2, 3),
-            "BPVDIRA": F.BPVDIRA(pvs),
-            "CHI2DOF": F.CHI2DOF,
-            "BPVIP": F.BPVIP(pvs),
-            "BPVIPCHI2": F.BPVIPCHI2(pvs),
-            "BPVFD": F.BPVFD(pvs),
-            "BPVFDCHI2": F.BPVFDCHI2(pvs),
-            "MINIPCHI2": F.MINIPCHI2(pvs),
-            "PX": F.PX,
-            "PY": F.PY,
-            "PZ": F.PZ,
-            "BPVX": F.BPVX(pvs),
-            "BPVY": F.BPVY(pvs),
-            "BPVZ": F.BPVZ(pvs),
-            "END_VX": F.END_VX,
-            "END_VY": F.END_VY,
-            "END_VZ": F.END_VZ,
-            "END_VCHI2DOF": F.CHI2DOF @ F.ENDVERTEX,
-        }
-    )
-
-    fs_variables = FunctorCollection(
-        {
-            "ID": F.PARTICLE_ID,
-            "PT": F.PT,
-            "ETA": F.ETA,
-            "PHI": F.PHI,
-            "P": F.P,
-            "MASS": F.MASS,
-            "CHI2DOF": F.CHI2DOF,
-            "MINIPCHI2": F.MINIPCHI2(pvs),
-            "BPVIPCHI2": F.BPVIPCHI2(pvs),
-            "PX": F.PX,
-            "PY": F.PY,
-            "PZ": F.PZ,
-            "hasRICH": F.PPHASRICH() @ F.PROTOPARTICLE(),
-            "PIDK": F.PID_K,
-            "PIDp": F.PID_P,
-            "PIDe": F.PID_E,
-            "PIDmu": F.PID_MU,
-            "isMuon": F.ISMUON,
-            "TRACK_GhostProb": F.GHOSTPROB,
-            "ProbNNp": F.PROBNN_P,
-            "NHITS": F.VALUE_OR(-1) @ F.NHITS @ F.TRACK,
-            "NVPHITS": F.VALUE_OR(-1) @ F.NVPHITS @ F.TRACK, # VeloPixel hits
-            "NUTHITS": F.VALUE_OR(-1) @ F.NUTHITS @ F.TRACK, # UpstreamTracker hits
-            "NFTHITS": F.VALUE_OR(-1) @ F.NFTHITS @ F.TRACK, # ForwardTracker hits
-            "TRACKHASVELO": F.VALUE_OR(-1) @ F.TRACKHASVELO @ F.TRACK,
-        }
-    )
-
-    variables = {
-        "lab0": B_variables,
-        "lab1": fs_variables,
-        "lab2": C_variables,
-        "lab3": fs_variables,
-        "lab4": fs_variables,
-        "lab5": fs_variables,
-    }
-
-    if options.simulation:
-        mctruth = MCTruthAndBkgCat(line_data, name="MCTruthAndBkgCat")
-        trueid_bkgcat_info = {
-            "TRUEID": F.VALUE_OR(0) @ mctruth(F.PARTICLE_ID),
-            "TRUEKEY": F.VALUE_OR(-1) @ mctruth(F.OBJECT_KEY),
-            "TRUEPT": mctruth(F.PT),
-            "TRUEPX": mctruth(F.PX),
-            "TRUEPY": mctruth(F.PY),
-            "TRUEPZ": mctruth(F.PZ),
-            "TRUEENERGY": mctruth(F.ENERGY),
-            "TRUEP": mctruth(F.P),
-            "TRUEFOURMOMENTUM": mctruth(F.FOURMOMENTUM),
-            "TRUETAU": mctruth(F.MC_LIFETIME),
-            "BKGCAT": mctruth.BkgCat,
-        }
-        for field in variables.keys():
-            variables[field] += FunctorCollection(trueid_bkgcat_info)
-
-
-    odin = get_odin()
-    rec_summary = get_rec_summary()
-    # define event level variables
-    evt_variables = FunctorCollection({
-	"RUNNUMBER": F.RUNNUMBER(odin),
-	"EVENTNUMBER": F.EVENTNUMBER(odin),
-	"nPVs": F.VALUE_OR(-1) @ F.RECSUMMARY_INFO(rec_summary, "nPVs"),
-	"nLongTracks": F.VALUE_OR(-1) @ F.RECSUMMARY_INFO(rec_summary, "nLongTracks"),
-    })
-    evt_variables+=FC.SelectionInfo(selection_type="Hlt1", trigger_lines=Hlt1_decisions)
-    evt_variables+=FC.SelectionInfo(selection_type="Hlt2", trigger_lines=Hlt2_decisions)
-
-    # define FunTuple instance
-    my_tuple = Funtuple(
-        name="Tuple",
-        tuple_name="DecayTree",
-        fields=fields,
-        variables=variables,
-        event_variables=evt_variables,
-        inputs=line_data,
-        store_multiple_cand_info=True,
-    )
-
-    return make_config(options, [my_filter, my_tuple])
-
diff --git a/Beauty_XS_Bs2DsPi/hlt1.py b/Beauty_XS_Bs2DsPi/hlt1.py
deleted file mode 100644
index d40c23bc29209d15b3ced30b1959c81368527495..0000000000000000000000000000000000000000
--- a/Beauty_XS_Bs2DsPi/hlt1.py
+++ /dev/null
@@ -1,31 +0,0 @@
-###############################################################################
-# (c) Copyright 2023 CERN for the benefit of the LHCb Collaboration           #
-#                                                                             #
-# This software is distributed under the terms of the GNU General Public      #
-# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   #
-#                                                                             #
-# In applying this licence, CERN does not waive the privileges and immunities #
-# granted to it by virtue of its status as an Intergovernmental Organization  #
-# or submit itself to any jurisdiction.                                       #
-###############################################################################
-"""
-Configures running HLT1 via Moore.
-"""
-
-from Moore import Options
-from Moore.config import allen_control_flow
-from RecoConf.hlt1_allen import allen_gaudi_config, get_allen_line_names
-from PyConf.application import configure_input, configure
-
-def alg_config(options: Options):
-    """
-    Configures algorithm running of HLT1 via Moore to be passed to Analysis Productions.
-    """
-
-    config = configure_input(options)
-    with allen_gaudi_config.bind(sequence="hlt1_pp_matching_no_ut_1000KHz"):
-        line_names = get_allen_line_names()
-        allen_node = allen_control_flow(options)
-        config.update(configure(options, allen_node))
-
-    return config
diff --git a/Beauty_XS_Bs2DsPi/hlt2.py b/Beauty_XS_Bs2DsPi/hlt2.py
deleted file mode 100644
index 86070c155cc3219e654c040a0f7dd22cda4b9dd7..0000000000000000000000000000000000000000
--- a/Beauty_XS_Bs2DsPi/hlt2.py
+++ /dev/null
@@ -1,68 +0,0 @@
-###############################################################################
-# (c) Copyright 2023 CERN for the benefit of the LHCb Collaboration           #
-#                                                                             #
-# This software is distributed under the terms of the GNU General Public      #
-# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   #
-#                                                                             #
-# In applying this licence, CERN does not waive the privileges and immunities #
-# granted to it by virtue of its status as an Intergovernmental Organization  #
-# or submit itself to any jurisdiction.                                       #
-###############################################################################
-"""
-Configures running HLT2 via Moore.
-"""
-
-from Moore import options, Options, run_moore
-from Hlt2Conf.lines.b_to_open_charm import all_lines
-#
-from RecoConf.global_tools import stateProvider_with_simplified_geom, trackMasterExtrapolator_with_simplified_geom
-from RecoConf.reconstruction_objects import reconstruction
-from RecoConf.hlt2_global_reco import reconstruction as hlt2_reconstruction, make_light_reco_pr_kf_without_UT
-from RecoConf.hlt2_tracking import (
-    make_TrackBestTrackCreator_tracks,
-    make_PrKalmanFilter_noUT_tracks,
-    make_PrKalmanFilter_Velo_tracks,
-    make_PrKalmanFilter_Seed_tracks,
-)
-from RecoConf.decoders import default_VeloCluster_source
-from RecoConf.event_filters import require_gec
-from Hlt2Conf.settings.defaults import get_default_hlt1_filter_code_for_hlt2
-import sys
-
-all_lines = {}
-from Hlt2Conf.lines.b_to_open_charm.hlt2_b2oc import make_hlt2_lines
-default_lines = [
-    'BdToDsmPi_DsmToKpKmPim',
-]
-extra_config = {
-    'flavour_tagging': [
-        'BdToDsmPi_DsmToKpKmPim',
-    ]
-}
-make_hlt2_lines(
-    line_dict=all_lines,
-    all_lines=default_lines,
-    extra_config=extra_config)
-
-def make_lines():
-    lines = [builder() for builder in all_lines.values()]
-    return lines
-
-def alg_config(options: Options):
-
-    public_tools = [
-        trackMasterExtrapolator_with_simplified_geom(),
-        stateProvider_with_simplified_geom()
-    ]
-
-    # require_gec.bind(skipUT=True),\ # B2OC lines do not have require_gec()
-    with reconstruction.bind(from_file=False),\
-        hlt2_reconstruction.bind(make_reconstruction=make_light_reco_pr_kf_without_UT),\
-        default_VeloCluster_source.bind(bank_type="VPRetinaCluster"),\
-        make_TrackBestTrackCreator_tracks.bind(max_ghost_prob=0.7, max_chi2ndof=sys.float_info.max),\
-        make_PrKalmanFilter_Velo_tracks.bind(max_chi2ndof=5.),\
-        make_PrKalmanFilter_noUT_tracks.bind(max_chi2ndof=4.),\
-        make_PrKalmanFilter_Seed_tracks.bind(max_chi2ndof=6.):
-        config = run_moore(options, make_lines, public_tools)
-
-    return config
diff --git a/Beauty_XS_Bs2DsPi/info.yaml b/Beauty_XS_Bs2DsPi/info.yaml
deleted file mode 100644
index b903958273f8fca7b499470ad64cf523c5cc60e2..0000000000000000000000000000000000000000
--- a/Beauty_XS_Bs2DsPi/info.yaml
+++ /dev/null
@@ -1,79 +0,0 @@
-
-defaults:
-  wg: B2OC
-  automatically_configure: no
-  inform:
-    - alessandro.bertolin@pd.infn.it
-
-{%- set mc_datasets = [
-('13264021', 'Down', 'sim10-2024.Q1.2-v1.0-md100', 'dddb-20240427'),
-('13264021', 'Up',   'sim10-2024.Q1.2-v1.0-mu100', 'dddb-20240427'),
-]%}
-
-{%- for evttype, polarity, conddb, dddb in mc_datasets %}
-
-MC_Bs2DsPi_{{ polarity }}_Nu4d3_hlt1:
-  application: Moore/v55r9@x86_64_v2-el9-gcc13+detdesc-opt
-  input:
-    bk_query: "/MC/Dev/Beam6800GeV-2024.Q1.2-Mag{{ polarity }}-VeloDrift-Nu4.3-25ns-Pythia8/Sim10d/{{ evttype }}/DIGI"
-    dq_flags:
-      - OK
-    n_test_lfns: 1
-  options:
-    entrypoint: Beauty_XS_Bs2DsPi.hlt1:alg_config
-    extra_options:
-      input_raw_format: 0.5
-      conddb_tag: {{ conddb }}
-      dddb_tag: {{ dddb }}
-      input_type: "ROOT"
-      output_type: "ROOT"
-      simulation: True
-      data_type: "Upgrade"
-      scheduler_legacy_mode: False
-      compression:
-        algorithm: ZSTD
-        level: 1
-        max_buffer_size: 1048576
-  output: hlt1.dst
-
-MC_Bs2DsPi_{{ polarity }}_Nu4d3_hlt2:
-  application: Moore/v55r9@x86_64_v2-el9-gcc13+detdesc-opt
-  input:
-    job_name: MC_Bs2DsPi_{{ polarity }}_Nu4d3_hlt1
-  options: 
-    entrypoint: Beauty_XS_Bs2DsPi.hlt2:alg_config
-    extra_options:
-      input_raw_format: 0.5
-      persistreco_version: 0.0
-      conddb_tag: {{ conddb }}
-      dddb_tag: {{ dddb }}
-      input_type: "ROOT"
-      output_type: "ROOT"
-      simulation: True
-      data_type: "Upgrade"
-      output_manifest_file: hlt2_{{ evttype }}_{{ polarity }}.tck.json
-      scheduler_legacy_mode: False
-      compression:
-        algorithm: ZSTD
-        level: 1
-        max_buffer_size: 1048576
-  output: hlt2.dst
-
-MC_Bs2DsPi_{{ polarity }}_Nu4d3_DV:
-  application: DaVinci/v64r6@x86_64_v2-el9-gcc13+detdesc-opt
-  input:
-    job_name: MC_Bs2DsPi_{{ polarity }}_Nu4d3_hlt2
-  output: dv_{{ polarity }}.root
-  options:
-    entrypoint: Beauty_XS_Bs2DsPi.dv_simple:alg_config
-    extra_options:
-      input_raw_format: 0.5
-      input_type: "ROOT"
-      simulation: True
-      data_type: "Upgrade"
-      conddb_tag: {{ conddb }}
-      dddb_tag: {{ dddb }}
-      input_process: "Hlt2"
-
-{%- endfor %}
-
diff --git a/btracking_b2dpipi/dv_b2dpipi_options.py b/btracking_b2dpipi/dv_b2dpipi_options.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a44ec21525992cd0b4529d9744564c2078ebf7a
--- /dev/null
+++ b/btracking_b2dpipi/dv_b2dpipi_options.py
@@ -0,0 +1,475 @@
+###############################################################################
+# (c) Copyright 2024 CERN for the benefit of the LHCb Collaboration           #
+#                                                                             #
+# This software is distributed under the terms of the GNU General Public      #
+# Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   #
+#                                                                             #
+# In applying this licence, CERN does not waive the privileges and immunities #
+# granted to it by virtue of its status as an Intergovernmental Organization  #
+# or submit itself to any jurisdiction.                                       #
+###############################################################################
+
+from PyConf import configurable
+from PyConf.reading import get_particles, get_pvs
+from GaudiKernel.SystemOfUnits import GeV, MeV, mm
+from Hlt2Conf.algorithms_thor import ParticleFilter, ParticleCombiner, ParticleContainersMerger
+from Hlt2Conf.standard_particles import make_long_pions
+from Hlt2Conf.lines.semileptonic.builders.base_builder import make_candidate
+from Hlt2Conf.lines.semileptonic.HbToTauNu_BTracking import get_heavyflavourtrack_table, make_pions as hlt2_make_pions, make_parts_with_btracking
+from RecoConf.reconstruction_objects import reconstruction
+from RecoConf.decoders import default_VeloCluster_source
+
+import Functors as F
+from FunTuple import FunctorCollection
+from FunTuple import FunTuple_Particles as Funtuple
+import FunTuple.functorcollections as FC
+from Functors.math import in_range
+from DaVinci.algorithms import create_lines_filter
+from DaVinci import Options, make_config
+from DecayTreeFitter import DecayTreeFitter
+from IsolationTools import VertexAndConeIsolation
+
+
+# data getters
+@configurable
+def make_pions_iso(line_name="", add_rich_filter=False):
+    pions = get_particles(
+        f"/Event/Spruce/{line_name}/Bu_LongTrackIsolation/Particles")
+    return ParticleFilter(
+        pions,
+        F.FILTER(F.PPHASRICH @ F.PROTOPARTICLE),
+        name="pions_with_rich_info") if add_rich_filter else pions
+
+
+def make_pions(line_name, name="pions_for_Bp2Dmpipip", from_persistreco=True):
+    if from_persistreco:
+        with reconstruction.bind(from_file=True):
+            return hlt2_make_pions()
+    else:
+        with make_candidate.bind(make_particles=make_pions_iso),\
+                make_pions_input.bind(line_name=line_name):
+            return hlt2_make_pions()
+
+
+def make_bps(
+        dms,
+        pions,
+        name="Bps_for_Bp2Dmpipi",
+        vchi2_max=16,
+        m_min=5.0 * GeV,
+        m_max=5.7 * GeV,
+        dira_min=0.9995,
+        twopi_doca_max=0.1 * mm,
+        twopi_mass_max=3.5 * GeV,
+):
+    # combine to form full B with some additional requirements on pi+pi+ combination
+    combination_code = F.require_all(
+        F.SUBCOMB(Functor=F.MAXSDOCACUT(twopi_doca_max), Indices=(2, 3)),
+        F.SUBCOMB(Functor=(F.MASS < twopi_mass_max), Indices=(2, 3)),
+        in_range(m_min - 50 * MeV, F.MASS, m_max + 50 * MeV))
+
+    vertex_code = F.require_all(
+        in_range(m_min, F.MASS, m_max), F.CHI2 < vchi2_max,
+        F.OWNPVDIRA > dira_min)
+
+    bps = ParticleCombiner([dms, pions, pions],
+                           DecayDescriptor="[B+ -> D- pi+ pi+]cc",
+                           CombinationCut=combination_code,
+                           CompositeCut=vertex_code,
+                           name=name)
+    return bps
+
+
+## isolation
+def get_isolation_variables(line_name, ref_parts, name, vertex_decay_os,
+                            vertex_decay_ss, vertex_decay_twotrack):
+    charged_data = get_particles(
+        f"/Event/Spruce/{line_name}/Bu_LongTrackIsolation/Particles")
+    neutral_data = get_particles(
+        f"/Event/Spruce/{line_name}/Bu_NeutralIsolation/Particles")
+    # default cone isolation
+    b_charged_iso = VertexAndConeIsolation(
+        name=f"ChargedIsofor_{name}",
+        reference_particles=ref_parts,
+        related_particles=charged_data,
+        cut=F.SQRT @ F.DR2 < 0.5)
+    b_neutral_iso = VertexAndConeIsolation(
+        name=f"NeutralIso_for_{name}",
+        reference_particles=ref_parts,
+        related_particles=neutral_data,
+        cut=F.SQRT @ F.DR2 < 0.5)
+    iso_vars = FC.ConeIsolation(
+        charged_cone_isolation_alg=b_charged_iso,
+        neutral_cone_isolation_alg=b_neutral_iso,
+        array_indx_name=f"{name}_cone_indx")
+    # isolation with one/two tracks combined with reference particle
+    # two types:
+    # - DOCA (distance of closest approach)-based selection
+    #   DOCA is important for isolation against signal related tracks not coming from the same vertex
+    #   like if you have a charm with lifetime coming from B
+    #   especially important for decays like B+ -> tau+ ( -> pi+ pi- pi+ ) X
+    #   where one wants to reject X, as these would still come clone to the line of vertex + direction/momentum
+    # - vertex selection
+    #   isolation against track coming from same vertex
+    b_vertex_iso_onetrack_data_os = ParticleCombiner(
+        [ref_parts, charged_data],
+        name=f"OneTrack_OS_combination_for_isolation_for_{name}",
+        DecayDescriptor=vertex_decay_os,
+        CombinationCut=F.MAXSDOCACUT(0.2 * mm),
+        CompositeCut=F.ALL)
+    b_vertex_iso_onetrack_data_ss = ParticleCombiner(
+        [ref_parts, charged_data],
+        name=f"OneTrack_SS_combination_for_isolation_for_{name}",
+        DecayDescriptor=vertex_decay_ss,
+        CombinationCut=F.MAXSDOCACUT(0.2 * mm),
+        CompositeCut=F.ALL)
+    b_vertex_iso_onetrack_data = ParticleContainersMerger(
+        [b_vertex_iso_onetrack_data_os, b_vertex_iso_onetrack_data_ss],
+        name=f"OneTrack_combination_for_isolation_for_{name}")
+    b_vertex_iso_twotrack_data = ParticleCombiner(
+        [ref_parts, charged_data, charged_data],
+        name=f"Two_extra_tracks_combination_for_isolation_for_{name}",
+        DecayDescriptor=vertex_decay_twotrack,
+        CombinationCut=F.MAXSDOCACUT(0.2 * mm),
+        CompositeCut=F.ALL)
+    b_vertex_iso_onetrack_alg = VertexAndConeIsolation(
+        name=f"OneTrackVertexIso_for_{name}",
+        reference_particles=ref_parts,
+        related_particles=b_vertex_iso_onetrack_data,
+        cut=F.CHI2() @ F.FORWARDARG1 < 9)
+    b_vertex_iso_twotrack_alg = VertexAndConeIsolation(
+        name=f"TwoTrackVertexIso_for_{name}",
+        reference_particles=ref_parts,
+        related_particles=b_vertex_iso_twotrack_data,
+        cut=F.CHI2() @ F.FORWARDARG1 < 16)
+    b_doca_iso_onetrack_alg = VertexAndConeIsolation(
+        name=f"OneTrackDocaIso_for_{name}",
+        reference_particles=ref_parts,
+        related_particles=b_vertex_iso_onetrack_data,
+        cut=F.ALL)
+    b_doca_iso_twotrack_alg = VertexAndConeIsolation(
+        name=f"TwoTrackDocaIso_for_{name}",
+        reference_particles=ref_parts,
+        related_particles=b_vertex_iso_twotrack_data,
+        cut=F.ALL)
+    iso_vars += FC.ParticleIsolation(
+        isolation_alg=b_doca_iso_onetrack_alg,
+        array_indx_name=f'{name}_doca_onetrack_indx')
+    iso_vars += FC.ParticleIsolation(
+        isolation_alg=b_doca_iso_twotrack_alg,
+        array_indx_name=f'{name}_doca_twotrack_indx')
+    iso_vars += FC.ParticleIsolation(
+        isolation_alg=b_vertex_iso_onetrack_alg,
+        array_indx_name=f'{name}_vtx_onetrack_indx')
+    iso_vars += FC.ParticleIsolation(
+        isolation_alg=b_vertex_iso_twotrack_alg,
+        array_indx_name=f'{name}_vtx_twotrack_indx')
+    iso_vars += FC.VertexIsolation(isolation_alg=b_vertex_iso_onetrack_alg)
+    iso_vars += FC.VertexIsolation(isolation_alg=b_vertex_iso_twotrack_alg)
+    return iso_vars
+
+
+def run_b2dpipi(options: Options, MC=False):
+    fields = {
+        "Bp_0": "[B+ -> (D- -> K+ pi- pi- ) pi+ pi+]CC",
+        "Dm_0": "[B+ -> ^(D- -> K+ pi- pi- ) pi+ pi+]CC",
+        "pip_0": "[B+ -> (D- -> K+ pi- pi- ) ^pi+ pi+]CC",
+        "pip_1": "[B+ -> (D- -> K+ pi- pi- ) pi+ ^pi+]CC",
+        "Kp_0": "[B+ -> (D- -> ^K+ pi- pi- ) pi+ pi+]CC",
+        "pim_0": "[B+ -> (D- -> K+ ^pi- pi- ) pi+ pi+]CC",
+        "pim_1": "[B+ -> (D- -> K+ pi- ^pi- ) pi+ pi+]CC",
+    }
+
+    # data getters
+    line_name = "SpruceSLB_BToDPiPi_DToKPiPi_BTracking"
+
+    btrack_parts = get_particles(
+        f"/Event/Spruce/{line_name}/ParticlesWithHeavyFlavourTracks/Particles")
+    rels, dms = get_heavyflavourtrack_table(btrack_parts)
+
+    pions = make_pions(line_name)
+    bps = make_bps(dms, pions)
+
+    # make extra control B-tracking track with B vertex or D only
+    # NOTE: make sure one has the right raw banks!
+    VP_source = "VPRetinaCluster"
+    with default_VeloCluster_source.bind(bank_type=VP_source):
+        btracks_bp = make_parts_with_btracking(bps)
+    rels_bp = btracks_bp['BTracking'].OutputRelations
+
+    # DecayTree branches
+    variables_all = FunctorCollection({
+        "ID": F.PARTICLE_ID,
+        "KEY": F.OBJECT_KEY,
+        "P": F.P,
+        "PT": F.PT,
+        "PX": F.PX,
+        "PY": F.PY,
+        "PZ": F.PZ,
+        "ENERGY": F.ENERGY,
+        "ETA": F.ETA,
+        "PHI": F.PHI,
+        "IP": F.OWNPVIP,
+        "IPCHI2": F.OWNPVIPCHI2,
+        "CHI2DOF": F.CHI2DOF,
+    })
+
+    variables_basic = FunctorCollection({
+        "GHOSTPROB":
+        F.GHOSTPROB,
+        "CHI2":
+        F.VALUE_OR(-1) @ F.CHI2 @ F.TRACK,
+        "NDOF":
+        F.VALUE_OR(-1) @ F.NDOF @ F.TRACK,
+        "NVPHITS":
+        F.VALUE_OR(-1) @ F.NVPHITS @ F.TRACK,
+        "NUTHITS":
+        F.VALUE_OR(-1) @ F.NUTHITS @ F.TRACK,
+        "NFTHITS":
+        F.VALUE_OR(-1) @ F.NFTHITS @ F.TRACK,
+        "TX":
+        F.VALUE_OR(0) @ F.TX @ F.TRACK,
+        "TY":
+        F.VALUE_OR(0) @ F.TY @ F.TRACK,
+        "FIRSTHIT_X":
+        F.VALUE_OR(0) @ F.X_COORDINATE @ F.POSITION
+        @ F.STATE_AT('FirstMeasurement') @ F.TRACK,
+        "FIRSTHIT_Y":
+        F.VALUE_OR(0) @ F.Y_COORDINATE @ F.POSITION
+        @ F.STATE_AT('FirstMeasurement') @ F.TRACK,
+        "FIRSTHIT_Z":
+        F.VALUE_OR(0) @ F.Z_COORDINATE @ F.POSITION
+        @ F.STATE_AT('FirstMeasurement') @ F.TRACK,
+        })
+    variables_basic += FC.ParticleID(extra_info=True)
+
+    variables_comb = FunctorCollection({
+        "M":
+        F.MASS,
+        "CHI2":
+        F.CHI2,
+        "ENDVTX_X":
+        F.END_VX,
+        "ENDVTX_Y":
+        F.END_VY,
+        "ENDVTX_Z":
+        F.END_VZ,
+        "ENDVTX_COV_XX":
+        F.CALL(0, 0) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "ENDVTX_COV_XY":
+        F.CALL(0, 1) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "ENDVTX_COV_XZ":
+        F.CALL(0, 2) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "ENDVTX_COV_YY":
+        F.CALL(1, 1) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "ENDVTX_COV_YZ":
+        F.CALL(1, 2) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "ENDVTX_COV_ZZ":
+        F.CALL(2, 2) @ F.POS_COV_MATRIX @ F.ENDVERTEX,
+        "OWNPV_X":
+        F.OWNPVX,
+        "OWNPV_Y":
+        F.OWNPVY,
+        "OWNPV_Z":
+        F.OWNPVZ,
+        "OWNPV_NDOF":
+        F.NDOF @ F.OWNPV,
+        "OWNPV_DIRA":
+        F.OWNPVDIRA,
+        "OWNPV_VDRHO":
+        F.OWNPVVDRHO,
+        "OWNPV_FD":
+        F.OWNPVFD,
+        "OWNPV_FDCHI2":
+        F.OWNPVFDCHI2,
+        "OWNPV_CORRM":
+        F.OWNPVCORRM,
+        "OWNPV_CORRMERR":
+        F.OWNPVCORRMERR,
+        "TAU":
+        F.OWNPVLTIME,
+    })
+
+    # include here the BTracking info, as it was run on the D candidate
+    def get_BTracking_FC(rels, name="", state_info=True):
+        if name != "": name += "_"
+        fc = FunctorCollection({
+            f"{name}BTRACKING_NHITS":
+            F.VALUE_OR(-1) @ F.BTRACKING_NHITS(rels),
+            f"{name}BTRACKING_NPRVELO3DEXPECT":
+            F.VALUE_OR(-1) @ F.BTRACKING_NPRVELO3DEXPECT(rels),
+            f"{name}BTRACKING_MCORR":
+            F.BTRACKING_BPVCORRM(rels),
+            f"{name}BTRACKING_TX":
+            F.VALUE_OR(0) @ F.TX @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_TY":
+            F.VALUE_OR(0) @ F.TY @ F.BTRACKING_TRACK(rels),
+        })
+        if state_info:
+            fc += FunctorCollection({
+            f"{name}BTRACKING_FIRSTHIT_X":
+            F.VALUE_OR(0) @ F.X_COORDINATE @ F.POSITION
+            @ F.STATE_AT('FirstMeasurement') @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_FIRSTHIT_Y":
+            F.VALUE_OR(0) @ F.Y_COORDINATE @ F.POSITION
+            @ F.STATE_AT('FirstMeasurement') @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_FIRSTHIT_Z":
+            F.VALUE_OR(0) @ F.Z_COORDINATE @ F.POSITION
+            @ F.STATE_AT('FirstMeasurement') @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_LASTHIT_X":
+            F.VALUE_OR(0) @ F.X_COORDINATE @ F.POSITION
+            @ F.STATE_AT('LastMeasurement') @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_LASTHIT_Y":
+            F.VALUE_OR(0) @ F.Y_COORDINATE @ F.POSITION
+            @ F.STATE_AT('LastMeasurement') @ F.BTRACKING_TRACK(rels),
+            f"{name}BTRACKING_LASTHIT_Z":
+            F.VALUE_OR(0) @ F.Z_COORDINATE @ F.POSITION
+            @ F.STATE_AT('LastMeasurement') @ F.BTRACKING_TRACK(rels),
+            })
+        return fc
+
+    variables_D = get_BTracking_FC(rels)
+
+    trigger_lines_D = [
+        "Hlt1TwoTrackMVADecision", "Hlt1TrackMVADecision", "Hlt1KsToPiPi",
+        "Hlt1D2PiPi", "Hlt1D2KPi", "Hlt1D2KK"
+    ]
+    variables_D += FC.HltTisTos(
+        selection_type="Hlt1", trigger_lines=trigger_lines_D, data=dms)
+
+    variables_D += get_isolation_variables(
+        line_name, dms, "Dm", "[D*(2007)0 -> D- pi+]cc",
+        "[D*(2007)0 -> D- pi-]cc", "[D*(2010)+ -> D+ pi- pi+]cc")
+
+    # Bp variables
+    pvs = get_pvs()
+
+    DTF_default = DecayTreeFitter(
+        name="DTF_default",
+        input_particles=bps,
+    )
+
+    DTF_Dpipi = DecayTreeFitter(
+        name="DTF_Dpipi",
+        input_particles=bps,
+        mass_constraints=["D-"],
+    )
+
+    DTF_PV = DecayTreeFitter(
+        name="DTF_PV",
+        input_particles=bps,
+        mass_constraints=[],
+        input_pvs=pvs,
+        constrain_to_ownpv=True,
+    )
+
+    DTF_Dpipi_PV = DecayTreeFitter(
+        name="DTF_Dpipi_PV",
+        input_particles=bps,
+        mass_constraints=["D-"],
+        input_pvs=pvs,
+        constrain_to_ownpv=True,
+    )
+
+    variables_B = FunctorCollection({
+        "Dmpip_0_M":
+        F.SUBCOMB(Functor=F.MASS, Indices=(1, 2)),
+        "Dmpip_1_M":
+        F.SUBCOMB(Functor=F.MASS, Indices=(1, 3)),
+        "pippip_M":
+        F.SUBCOMB(Functor=F.MASS, Indices=(2, 3)),
+        "pippip_SDOCA":
+        F.SUBCOMB(Functor=F.MAXSDOCA, Indices=(2, 3)),
+        "pippip_SDOCACHI2":
+        F.SUBCOMB(Functor=F.MAXSDOCACHI2, Indices=(2, 3)),
+        "VTX_DLS_to_Dm":
+        F.VTX_DLS.bind(F.CHILD(1, F.ENDVERTEX), F.FORWARDARGS),
+        "OWNPV_COV_XX":
+        F.CALL(0, 0) @ F.POS_COV_MATRIX @ F.OWNPV,
+        "OWNPV_COV_XY":
+        F.CALL(0, 1) @ F.POS_COV_MATRIX @ F.OWNPV,
+        "OWNPV_COV_XZ":
+        F.CALL(0, 2) @ F.POS_COV_MATRIX @ F.OWNPV,
+        "OWNPV_COV_YY":
+        F.CALL(1, 1) @ F.POS_COV_MATRIX @ F.OWNPV,
+        "OWNPV_COV_YZ":
+        F.CALL(1, 2) @ F.POS_COV_MATRIX @ F.OWNPV,
+        "OWNPV_COV_ZZ":
+        F.CALL(2, 2) @ F.POS_COV_MATRIX @ F.OWNPV,
+    })
+
+    DTFs = [("DTF", DTF_default), ("DTF_Dpipi", DTF_Dpipi), ("DTF_PV", DTF_PV),
+            ("DTF_Dpipi_PV", DTF_Dpipi_PV)]
+    for name, dtf in DTFs:
+        variables_B += FunctorCollection({
+            f"{name}_M":
+            dtf(F.MASS),
+            f"{name}_CHI2":
+            dtf(F.CHI2),
+            f"{name}_ENDVTX_X":
+            dtf(F.END_VX),
+            f"{name}_ENDVTX_Y":
+            dtf(F.END_VY),
+            f"{name}_ENDVTX_Z":
+            dtf(F.END_VZ),
+            f"{name}_Dm_ENDVTX_X":
+            dtf(F.CHILD(1, F.END_VX)),
+            f"{name}_Dm_ENDVTX_Y":
+            dtf(F.CHILD(1, F.END_VY)),
+            f"{name}_Dm_ENDVTX_Z":
+            dtf(F.CHILD(1, F.END_VZ)),
+        })
+
+    variables_B += get_BTracking_FC(rels_bp, name="BOnly")
+
+    trigger_lines_B = [
+        "Hlt1TwoTrackMVADecision", "Hlt1TrackMVADecision", "Hlt1KsToPiPi",
+        "Hlt1D2PiPi", "Hlt1D2KPi", "Hlt1D2KK"
+    ]
+    variables_B += FC.HltTisTos(
+        selection_type="Hlt1", trigger_lines=trigger_lines_B, data=bps)
+
+    variables_B += get_isolation_variables(
+        line_name, bps, "Bp", "[B*0 -> B+ pi-]cc", "[B*0 -> B+ pi+]cc",
+        "[B*+ -> B+ pi- pi+]cc")
+
+    # declare variables for each branch
+    variables = {
+        "ALL": variables_all,
+        "Dm_0": variables_comb + variables_D,
+        "Bp_0": variables_comb + variables_B,
+        "pip_0": variables_basic,
+        "pip_1": variables_basic,
+        "Kp_0": variables_basic,
+        "pim_0": variables_basic,
+        "pim_1": variables_basic,
+    }
+
+    # global/event variables
+    evt_vars = FC.EventInfo()
+    evt_vars += FC.RecSummary(detector_info=True)
+    evt_vars += FC.SelectionInfo(
+        selection_type="Hlt1", trigger_lines=trigger_lines_B)
+
+    # sequence
+    filter_bs = create_lines_filter(
+        name="HDRFilter_Bdpipi", lines=[f"{line_name}"])
+
+    tuple_bs = Funtuple(
+        name="BDpipi_Tuple",
+        tuple_name="DecayTree",
+        fields=fields,
+        variables=variables,
+        event_variables=evt_vars,
+        inputs=bps,
+    )
+
+    algs = {
+        "SpruceSLB_BToDPiPi_DToKPiPi_BTracking": [filter_bs, tuple_bs],
+    }
+
+    return make_config(options, algs)
+
+
+def run_data(options: Options):
+    return run_b2dpipi(options, MC=False)
diff --git a/btracking_b2dpipi/info.yaml b/btracking_b2dpipi/info.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a07a6a58b4dbb580d62f4c8ff02638d95b10d677
--- /dev/null
+++ b/btracking_b2dpipi/info.yaml
@@ -0,0 +1,34 @@
+defaults:
+  inform:
+    - maarten.vanveghel@cern.ch
+    - mick.mulder@cern.ch
+  wg: SL
+
+
+{%- set datasets = [
+ ( '2024Data_noUT', 'MagDown', '-Excl-UT/Real Data/Sprucing24c1', false, 'v64r8'),
+ ( '2024Data_noUT', 'MagUp',   '-Excl-UT/Real Data/Sprucing24c1', false, 'v64r8'),
+ ( '2024Data_withUT', 'MagDown', '/Real Data/Sprucing24c2', true, 'v64r8'),
+]%}
+{%- for evttype, polarity, campaign, keep_running, dv_version in datasets %}
+b2dpipi_btracking_{{ evttype }}_{{ polarity }}:
+  application: DaVinci/{{ dv_version }}
+  input:
+    bk_query: "/LHCb/Collision24/Beam6800GeV-VeloClosed-{{ polarity }}{{ campaign }}/90000000/SL.DST"
+    dq_flags:
+      - UNCHECKED
+      - OK
+    keep_running: {{ keep_running }}
+    n_test_lfns: 3
+  output: data.root
+  options:
+    entrypoint: btracking_b2dpipi.dv_b2dpipi_options:run_data
+    extra_options:
+      input_raw_format: 0.5
+      input_type: ROOT
+      simulation: False
+      geometry_version: run3/2024.Q1.2-v00.00
+      conditions_version: master
+      input_process: "Spruce"
+      input_stream: "sl"
+{%- endfor %}