diff --git a/Lb2Lc3Pi/Lb2Lc3Pi.py b/Lb2Lc3Pi/Lb2Lc3Pi.py
new file mode 100644
index 0000000000000000000000000000000000000000..431f7d253b32a6035d0177ed6347f05c74137e67
--- /dev/null
+++ b/Lb2Lc3Pi/Lb2Lc3Pi.py
@@ -0,0 +1,321 @@
+import Functors as F
+import Functors.math as fmath
+from DaVinci import Options, make_config
+from DaVinci.algorithms import create_lines_filter
+from PyConf.reading import get_particles, get_pvs
+from Functors.math import log
+from FunTuple import FunctorCollection, FunTuple_Particles as FunTuple
+from PyConf.application import metainfo_repos
+from FunTuple.functorcollections import DecayTreeFitterResults, Kinematics, ParticleID, EventInfo, HltTisTos, SelectionInfo
+
+
+
+Hlt1_decisions = [
+    "Hlt1TrackMVA",
+    "Hlt1TwoTrackMVA",
+    "Hlt1TwoTrackMVACharmXSec",
+    "Hlt1TwoTrackKs",
+    "Hlt1KsToPiPi",
+    "Hlt1GECPassthrough"
+    'Hlt1D2KK',
+    'Hlt1D2KPi',
+    'Hlt1D2PiPi',
+    'Hlt1DiMuonHighMass',
+    'Hlt1DiMuonLowMass',
+    'Hlt1DiMuonSoft',
+    'Hlt1LowPtMuon',
+    'Hlt1LowPtDiMuon',
+    'Hlt1SingleHighPtMuon',
+    'Hlt1TrackMuonMVA'
+]
+
+Hlt2_decisions = [
+    "Hlt2Topo2Body","Hlt2Topo3Body","Hlt2Topo4Body"
+]
+
+
+def make_tistos_hlt1(data):
+    return HltTisTos(
+        selection_type="Hlt1",
+        trigger_lines=[
+            f"{x}Decision" for x in Hlt1_decisions if "GECPassthrough" not in x
+        ],
+        data=data,
+    )
+
+
+# Only for Sprucing -- needs to be reconfigured
+def make_tistos_hlt2(data):
+    return HltTisTos(
+        selection_type="Hlt2",
+        trigger_lines=[f"{x}Decision" for x in Hlt2_decisions],
+        data=data,
+    )
+
+
+def make_comp_variables(data):
+    v2_pvs = get_pvs()
+
+    variables = FunctorCollection({
+        "MAX_PT":
+        F.MAX(F.PT),
+        "MIN_PT":
+        F.MIN(F.PT),
+        "SUM_PT":
+        F.SUM(F.PT),
+        "MAX_P":
+        F.MAX(F.P),
+        "MIN_P":
+        F.MIN(F.P),
+        "BPVDIRA":
+        F.BPVDIRA(v2_pvs),
+        "BPVFDIR":
+        F.BPVFDIR(v2_pvs),
+        "CHI2VXNDOF":
+        F.CHI2DOF,
+        "BPVFDCHI2":
+        F.BPVFDCHI2(v2_pvs),
+        "BPVFD":
+        F.BPVFD(v2_pvs),
+        "BPVFDVEC":
+        F.BPVFDVEC(v2_pvs),
+        "BPVVDRHO":
+        F.BPVVDRHO(v2_pvs),
+        "BPVVDX":
+        F.BPVVDX(v2_pvs),
+        "BPVVDY":
+        F.BPVVDY(v2_pvs),
+        "BPVVDZ":
+        F.BPVVDZ(v2_pvs),
+        "BPVDLS":
+        F.BPVDLS(v2_pvs),
+        "BPVIPCHI2":
+        F.BPVIPCHI2(v2_pvs),
+        "BPVCORRM":
+        F.BPVCORRM(v2_pvs),
+        "BPVCORRMERR":
+        F.BPVCORRMERR(v2_pvs),
+        "BPVIP":
+        F.BPVIP(v2_pvs),
+        "LOGBPVIPCHI2":
+        log(F.BPVIPCHI2(v2_pvs)),
+        "BPVLTIME":
+        F.BPVLTIME(v2_pvs),
+        "MAX_BPVIPCHI2":
+        F.MAX(F.BPVIPCHI2(v2_pvs)),
+        "MIN_BPVIPCHI2":
+        F.MIN(F.BPVIPCHI2(v2_pvs)),
+        "MAXDOCACHI2":
+        F.MAXDOCACHI2,
+        "MAXDOCA":
+        F.MAXDOCA,
+        "MAXSDOCACHI2":
+        F.MAXSDOCACHI2,
+        "MAXSDOCA":
+        F.MAXSDOCA,
+        "MM12":
+        F.SUBCOMB(Functor=F.MASS, Indices=(1, 2)),
+        "MM13":
+        F.SUBCOMB(Functor=F.MASS, Indices=(1, 3)),
+        "ETA":
+        F.ETA,
+        "PHI":
+        F.PHI,
+        "END_VX":
+        F.END_VX,
+        "END_VY":
+        F.END_VY,
+        "END_VZ":
+        F.END_VZ,
+        "BPVX":
+        F.BPVX(v2_pvs),
+        "BPVY":
+        F.BPVY(v2_pvs),
+        "BPVZ":
+        F.BPVZ(v2_pvs),
+    })
+
+    variables += Kinematics()
+    variables += ParticleID(extra_info=True)
+    variables += make_tistos_hlt1(data)
+    variables += make_tistos_hlt2(data) #-- Only for Sprucing
+
+    return variables
+
+
+def make_basic_variables(data):
+    v2_pvs = get_pvs()
+
+    variables = FunctorCollection({
+        "TRCHI2DOF":
+        F.CHI2DOF,
+        "TRGHOSTPROB":
+        F.GHOSTPROB,
+        "BPVIPCHI2":
+        F.BPVIPCHI2(v2_pvs),
+        "PIDK":
+        F.PID_K,
+        "PIDe":
+        F.PID_E,
+        "PIDmu":
+        F.PID_MU,
+        "PIDp":
+        F.PID_P,
+        "ISMUON":
+        F.ISMUON,
+        "BPVIP":
+        F.BPVIP(v2_pvs),
+        "ETA":
+        F.ETA,
+        "PHI":
+        F.PHI,
+        #some track-related variables
+        "NDOF":
+        F.VALUE_OR(-1) @ F.NDOF @ F.TRACK,
+        "NFTHITS":
+        F.VALUE_OR(-1) @ F.NFTHITS @ F.TRACK,
+        "NHITS":
+        F.VALUE_OR(-1) @ F.NHITS @ F.TRACK,
+        "NUTHITS":
+        F.VALUE_OR(-1) @ F.NUTHITS @ F.TRACK,
+        "NVPHITS":
+        F.VALUE_OR(-1) @ F.NVPHITS @ F.TRACK,
+        "TRACKHASVELO":
+        F.VALUE_OR(-1) @ F.TRACKHASVELO @ F.TRACK,
+        "TRACKHISTORY":
+        F.VALUE_OR(-1) @ F.TRACKHISTORY @ F.TRACK,
+        "TRACKPT":
+        F.TRACK_PT,
+        "ISMUON":
+        F.ISMUON,
+        "INMUON":
+        F.INMUON,
+        "INECAL":
+        F.INECAL,
+        "INHCAL":
+        F.INHCAL,
+    })
+
+    variables += Kinematics()
+    variables += ParticleID(extra_info=True)
+    variables += make_tistos_hlt1(data)
+
+    return variables
+
+
+def make_hlt2_event_variables(line_name, line_data):
+    v2_pvs = get_pvs()
+
+    evt_variables = EventInfo()
+    evt_variables += SelectionInfo(
+        selection_type="Hlt1", trigger_lines=Hlt1_decisions)
+    evt_variables += SelectionInfo(
+        selection_type="Hlt2", trigger_lines=Hlt2_decisions)
+    evt_variables += FunctorCollection({
+        "NPV": F.SIZE(v2_pvs),
+        "ALLPVX[NPVs]": F.ALLPVX(v2_pvs),
+        "ALLPVY[NPVs]": F.ALLPVY(v2_pvs),
+        "ALLPVZ[NPVs]": F.ALLPVZ(v2_pvs),
+    })
+    return evt_variables
+
+
+def DecayTreeFitter(data,
+                    pv_fit=True,
+                    mass_fit=False,
+                    prefix="DTF",
+                    mass_constraints=[]):
+    from DecayTreeFitter import DecayTreeFitter
+    pvs = get_pvs()
+    variables = FunctorCollection()
+    variables += Kinematics()
+    if pv_fit:
+        DTF = DecayTreeFitter(
+            name='PV_Fit_{hash}',
+            input_particles=data,
+            input_pvs=pvs,
+        )
+        variables += DecayTreeFitterResults(
+            DTF=DTF,
+            prefix=prefix + "_PV_",
+            decay_origin=True,
+            with_lifetime=True,
+            with_kinematics=True)
+
+    if mass_fit:
+        DTFM_excl = DecayTreeFitter(
+            name='PVandMass_Fit_{hash}',
+            input_particles=data,
+            input_pvs=pvs,
+            mass_constraints=mass_constraints)
+        variables += DecayTreeFitterResults(
+            DTF=DTFM_excl,
+            prefix=prefix + "_PVandMass_",
+            decay_origin=True,
+            with_lifetime=True,
+            with_kinematics=True)
+
+    return variables
+
+def SpruceB2OC_LbToLcpPiPiPi_LcpToPKPi_Tuple():
+
+    ###################################################
+
+    spruce_line = "SpruceB2OC_LbToLcpPiPiPi_LcpToPKPi"
+
+    input_data = get_particles(f"/Event/Spruce/{spruce_line}/Particles")
+
+    evt_vars = make_hlt2_event_variables(spruce_line, line_data=input_data)
+
+    fields = {
+        'Lb': '[Lambda_b0 -> (Lambda_c+ -> p+ K- pi+) pi- pi+ pi-]CC',
+        'LbPi1': '[Lambda_b0 -> (Lambda_c+ -> p+ K- pi+) ^pi- pi+ pi-]CC',
+        'LbPi2': '[Lambda_b0 -> (Lambda_c+ -> p+ K- pi+) pi- ^pi+ pi-]CC',
+        'LbPi3': '[Lambda_b0 -> (Lambda_c+ -> p+ K- pi+) pi- pi+ ^pi-]CC',
+        'Lc': '[Lambda_b0 -> ^(Lambda_c+ -> p+ K- pi+) pi- pi+ pi-]CC',
+        'LcP': '[Lambda_b0 -> (Lambda_c+ -> ^p+ K- pi+) pi- pi+ pi-]CC',
+        'LcK': '[Lambda_b0 -> (Lambda_c+ -> p+ ^K- pi+) pi- pi+ pi-]CC',
+        'LcPi': '[Lambda_b0 -> (Lambda_c+ -> p+ K- ^pi+) pi- pi+ pi-]CC',
+    }
+
+
+    variables = {
+        "Lb":make_comp_variables(input_data) + DecayTreeFitter(input_data),
+        "LbPi1":make_basic_variables(input_data),
+        "LbPi2":make_basic_variables(input_data),
+        "LbPi3":make_basic_variables(input_data),
+        "Lc":make_comp_variables(input_data),
+        "LcP": make_basic_variables(input_data),
+        "LcK": make_basic_variables(input_data),
+        "LcPi": make_basic_variables(input_data),
+    }
+
+    my_tuple = FunTuple(
+        name="Tuple",
+        tuple_name="DecayTree",
+        inputs=input_data,
+        fields=fields,
+        variables=variables,
+        event_variables=evt_vars)
+
+    my_filter = create_lines_filter(
+        name=f"HDRFilter_{spruce_line}", lines=[f'{spruce_line}'])
+
+    ###################################################
+
+    user_algorithms = {}
+    user_algorithms.update({"LbToLcpPiPiPi": [my_filter, my_tuple]})
+
+    return user_algorithms
+
+
+
+### CONFIG definition ###
+
+
+def main(options: Options):
+
+    all_user_algorithms = {}
+    all_user_algorithms.update(SpruceB2OC_LbToLcpPiPiPi_LcpToPKPi_Tuple())
+
+    return make_config(options, all_user_algorithms)
diff --git a/Lb2Lc3Pi/info.yaml b/Lb2Lc3Pi/info.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..60163a1ebab4ac1e2dccdda59dada04dcfae4721
--- /dev/null
+++ b/Lb2Lc3Pi/info.yaml
@@ -0,0 +1,41 @@
+defaults:
+  inform:
+    - jiaqi.yang@cern.ch
+  wg: B2OC
+
+ {%- set datasets = [
+ ('2024Data', 'MagUp', '24c2'),
+ ('2024Data', 'MagUp', '24c3'),
+ ('2024Data', 'MagUp', '24c4'),
+ ('2024Data', 'MagDown', '24c2'),
+ ('2024Data', 'MagDown', '24c3'),
+ ('2024Data', 'MagDown', '24c4'),
+('2024Data', 'MagDown-Excl-UT', '24c1'),
+ ('2024Data', 'MagDown-Excl-UT', '24c2'),
+ ('2024Data', 'MagUp-Excl-UT', '24c1'),
+ ('2024Data', 'MagUp-Excl-UT', '24c2'),
+]%}
+
+  {%- for evttype, polarity, campain in datasets %}
+Lb2Lc3Pi_{{ evttype }}_{{ polarity }}_{{ campain }}:
+  application: DaVinci/v64r9
+  input:
+    bk_query: "/LHCb/Collision24/Beam6800GeV-VeloClosed-{{ polarity }}/Real Data/Sprucing{{ campain }}/90000000/B2OC.DST"
+    dq_flags:
+      - OK
+      - UNCHECKED
+    keep_running: true
+    n_test_lfns: 1
+  output: DATA.ROOT
+  options:
+    entrypoint: Lb2Lc3Pi.Lb2Lc3Pi:main
+    extra_options:
+      input_raw_format: 0.5
+      input_type: ROOT
+      simulation: False
+      data_type: "Upgrade"
+      geometry_version: run3/2024.Q1.2-v00.00
+      conditions_version: master
+      input_process: "Spruce"
+      input_stream: "b2oc"
+{%- endfor %}