diff --git a/CommonTools/doc/example.info b/CommonTools/doc/example.info
index 5fbf80d521e5bfdbd9b775d0affc28e39eb5ebef..c10f87d6c82e8c56058c4880de5feac0736d8c31 100644
--- a/CommonTools/doc/example.info
+++ b/CommonTools/doc/example.info
@@ -52,6 +52,7 @@ corrections ; for event-by-event corrections
         tables ${DARWIN_TABLES}/JER/tables.json
         campaign Summer20UL16
         core_width 2
+        smear /dev/null
     }
     normalisation
     {
diff --git a/JEC/CMakeLists.txt b/JEC/CMakeLists.txt
index 798c500daa899a84e64ac134c0ad8806be7b4742..e20c2a790374849a9edb21d33ca9aaf435884f48 100644
--- a/JEC/CMakeLists.txt
+++ b/JEC/CMakeLists.txt
@@ -9,13 +9,13 @@ core_add_library(
         JMEmatching)
 core_add_executable(applyJERsmearing LIBRARIES correctionlib)
 core_add_executable(applyJEScorrections LIBRARIES correctionlib)
-core_add_executable(fitJetResolution NO_EXAMPLE)
-core_add_executable(fitJetResponse NO_EXAMPLE)
+core_add_executable(fitJetResolution NO_EXAMPLE LIBRARIES correctionlib)
+core_add_executable(fitJetResponse NO_EXAMPLE LIBRARIES correctionlib)
 core_add_executable(getJMEtables NO_EXAMPLE LIBRARIES correctionlib)
-core_add_executable(getJetResponse NO_EXAMPLE LIBRARIES Objects)
-core_add_executable(getHLTJetResponse NO_EXAMPLE LIBRARIES Objects)
-core_add_executable(getPtBalance NO_EXAMPLE)
-core_add_executable(getDijetBalance NO_EXAMPLE)
+core_add_executable(getJetResponse NO_EXAMPLE LIBRARIES correctionlib Objects)
+core_add_executable(getHLTJetResponse NO_EXAMPLE LIBRARIES correctionlib Objects)
+core_add_executable(getPtBalance NO_EXAMPLE LIBRARIES correctionlib)
+core_add_executable(getDijetBalance NO_EXAMPLE LIBRARIES correctionlib)
 #core_add_executable(getZjetBalance NO_EXAMPLE)
 
 add_subdirectory(test)
diff --git a/JEC/bin/applyJERsmearing.cc b/JEC/bin/applyJERsmearing.cc
index 95c93348096bbe0af464bba6b9ae25c615249b5e..75d7f7793463493ae24a20980148589dfc9fb75b 100644
--- a/JEC/bin/applyJERsmearing.cc
+++ b/JEC/bin/applyJERsmearing.cc
@@ -63,6 +63,7 @@ void applyJERsmearing
     auto R = GetR(metainfo);
     if (!isMC) BOOST_THROW_EXCEPTION( DE::BadInput("Only MC may be used as input.",
                                       make_unique<TFile>(inputs.front().c_str() )) );
+    auto year = metainfo.Get<int>("flags", "year");
     JMEmatching<vector<RecJet>, vector<GenJet>>::maxDR = (R/10.)/2;
 
     auto genEvt = flow.GetBranchReadOnly<GenEvent>("genEvent");
@@ -74,7 +75,7 @@ void applyJERsmearing
     const auto& tables = config.get<fs::path>("corrections.JER.tables");
     metainfo.Set<fs::path>("corrections", "JER", "tables", tables);
     if (!fs::exists(tables))
-        BOOST_THROW_EXCEPTION( fs::filesystem_error("File does not exists.",
+        BOOST_THROW_EXCEPTION( fs::filesystem_error("File does not exist.",
                     tables, make_error_code(errc::no_such_file_or_directory)) );
     const auto campaign = config.get<string>("corrections.JER.campaign"); // correction campaign
     metainfo.Set<string>("corrections", "JER", "campaign", campaign);
@@ -110,26 +111,43 @@ void applyJERsmearing
          JER = GetCorrection<correction::Correction::Ref>(
                             *cset, campaign, "MC", "PtResolution", suffix);
 
+    const auto& smear_tables = config.get<fs::path>("corrections.JER.smear");
+    metainfo.Set<fs::path>("corrections", "JER", "smear", smear_tables);
+
+    // preparing the JER smearing from JME
+    if (!fs::exists(smear_tables))
+        BOOST_THROW_EXCEPTION( fs::filesystem_error("File does not exist.",
+                    smear_tables, make_error_code(errc::no_such_file_or_directory)) );
+    correction::Correction::Ref smear;
+    bool JME_smearing = smear_tables != "/dev/null";
+    if (JME_smearing) {
+        auto cset_smear = correction::CorrectionSet::from_file(smear_tables.string());
+        smear = cset_smear->at("JERSmear");
+    }
+
     // a few lambda functions
-    auto getSFs = [&SF,&vars](const RecJet& recjet) {
+    auto getSFs = [year,&SF,&vars](const RecJet& recJet, ostream& cout) {
         vector<float> sfs;
-        for (const string var: vars)
-            sfs.push_back(SF->evaluate({recjet.p4.Eta(), var}));
+        sfs.reserve(vars.size());
+        for (const string& var: vars)
+            sfs.push_back(Evaluate(SF, cout, recJet, {}, {}, {}, var));
         return sfs;
     };
-    auto applySFs = [applySyst](RecJet& recjet, const vector<float>& sfs) {
-        float nom_scale = recjet.scales.front();
-        for (float& scale: recjet.scales)
+    auto applySFs = [applySyst](RecJet& recJet, const vector<float>& sfs, ostream& cout) {
+        cout << "applying SFs: " << recJet.CorrPt();
+        float nom_scale = recJet.scales.front();
+        for (float& scale: recJet.scales)
             scale *= sfs.front();
+        float recpt = recJet.CorrPt();
+        cout << " -> " << recpt;
 
         if (applySyst) {
-            recjet.scales.push_back(nom_scale*sfs[1]);
-            recjet.scales.push_back(nom_scale*sfs[2]);
+            recJet.scales.push_back(nom_scale*sfs[1]);
+            recJet.scales.push_back(nom_scale*sfs[2]);
+            cout << ' ' << recpt * sfs[1]
+                 << ' ' << recpt * sfs[2] << endl;
         }
     };
-    auto getJER = [pu,&JER](const RecJet& recjet) {
-        return JER->evaluate({recjet.p4.Eta(), recjet.CorrPt(), pu->rho});
-    };
 
     for (DT::Looper looper(tIn); looper(); ++looper) {
         [[ maybe_unused ]]
@@ -143,6 +161,23 @@ void applyJERsmearing
         JMEmatching matching(*recJets, *genJets);
         auto fake_its = matching.fake_its; // we extract it because we will modify it
 
+        cout << "GenJets:";
+        for (const auto& genJet: *genJets)
+            cout << '\t' << genJet.p4.Pt();
+        cout << "\nRecJets:";
+        for (const auto& recJet: *recJets)
+            cout << '\t' << recJet.CorrPt();
+        cout << "\nCouples:";
+        for (const auto& [recJet,genJet]: matching.match_its)
+            cout << '\t' << genJet->p4.Pt() << ',' << recJet->CorrPt();
+        cout << "\nMisses:";
+        for (const auto& genJet: matching.miss_its)
+            cout << '\t' << genJet->p4.Pt();
+        cout << "\nFakes:";
+        for (const auto& recJet: matching.fake_its)
+            cout << '\t' << recJet->CorrPt();
+        cout << endl;
+
         cout << "Applying scaling smearing for jets matched in the core of the response" << endl;
         for (const auto& [rec_it, gen_it]: matching.match_its) {
             cout << "---------------" << endl;
@@ -151,59 +186,65 @@ void applyJERsmearing
                   recpt = rec_it->CorrPt();
 
             float response = recpt / genpt,
-                  resolution = getJER(*rec_it);
-
-            cout << "|- genpt = " << genpt << '\n'
-                 << "|- recpt = " << recpt << '\n'
-                 << "|- response = " << response << '\n'
-                 << "|- resolution = " << resolution << endl;
+                  resolution = Evaluate(JER, cout, *rec_it, pu->rho);;
 
-            vector<float> sfs = getSFs(*rec_it);
             if (abs(response - 1) > core_width * resolution) {
-                cout << "|- match in the tail" << endl;
+                cout << "match in the tail" << endl;
                 fake_its.push_back(rec_it); // left for later, with stochastic smearing
                 continue;
             }
-            cout << "|- match in the core" << endl;
+            cout << "match in the core" << endl;
 
+            vector<float> sfs = getSFs(*rec_it, cout);
             for (float& sf: sfs) {
-                cout << "|- " << sf;
+                cout << "res sf = " << sf;
                 sf = max(0.f, 1+(sf-1)*(recpt-genpt)/recpt);
-                cout << " -> " << sf << endl;
+                cout << " -> pt sf = " << sf << endl;
             }
 
-            cout << "|- applying SFs" << endl;
-            applySFs(*rec_it, sfs);
+            applySFs(*rec_it, sfs, cout);
         }
 
+        cout << "Fakes after identification of matches in tails: ";
+        for (const auto& recJet: fake_its)
+            cout << '\t' << recJet->CorrPt();
+        cout << endl;
+
         cout << "Applying stochastic smearing for all other jets "
                 "(either unmatched, or matched in the tails of the response)" << endl;
         for (auto& fake_it: fake_its) {
+            cout << "---------------" << endl;
 
-            float resolution = getJER(*fake_it);
-            normal_distribution<float> d{0,resolution};
-            static mt19937 m_random_generator(metainfo.Seed<20394242>(slice));
-            float response = d(m_random_generator);
-
-            cout << "|- recpt = " << fake_it->CorrPt() << '\n'
-                 << "|- response = " << response << '\n'
-                 << "|- resolution = " << resolution << endl;
+            float resolution = Evaluate(JER, cout, *fake_it, pu->rho);;
+            vector<float> sfs = getSFs(*fake_it, cout);
 
-            vector<float> sfs = getSFs(*fake_it);
             for (float& sf: sfs) {
-                cout << "|- " << sf;
-                sf = max(0.f, 1+response*sqrt(max(0.f, sf*sf-1)));
-                cout << " -> " << sf << endl;
+                if (JME_smearing) // recJet, rho, genJet, recEvt, syst, JER, JERSF
+                    sf = Evaluate(smear, cout, *fake_it, pu->rho, {}, *recEvt, {}, resolution, sf);
+                else {
+                    normal_distribution<float> d{0,resolution};
+                    static mt19937 m_random_generator(metainfo.Seed<20394242>(slice));
+                    float response = d(m_random_generator);
+                    sf = max(0.f, 1+response*sqrt(max(0.f, sf*sf-1)));
+                    cout << "smearing "
+                         << "RecPt=" << fake_it->CorrPt() << ' '
+                         << "JER=" << resolution << ' '
+                         << "response = " << response << ' '
+                         << "-> " << sf << endl;
+                }
             }
 
-            cout << "|- applying SFs" << endl;
-
-            applySFs(*fake_it, sfs);
+            applySFs(*fake_it, sfs, cout);
         }
 
         cout << "Sorting by descending pt" << endl;
         sort(recJets->begin(), recJets->end(), pt_sort);
 
+        cout << "RecJets after smearing:";
+        for (const auto& recJet: *recJets)
+            cout << '\t' << recJet.CorrPt();
+        cout << endl;
+
         for (size_t i = 0; i < calib.size(); ++i) {
             calib.at(i)(*genJets, genEvt->weights.front());
             calib.at(i)(*recJets, genEvt->weights.front() * recEvt->weights.front(), i); /// \todo fix in case of offset (e.g. JES applied on MC)
@@ -244,10 +285,13 @@ int main (int argc, char * argv[])
                .arg<fs::path>("tables", "corrections.JER.tables",
                               "JSON file containing the corrections")
                .arg<string>("campaign", "corrections.JER.campaign",
-                            "campaign (e.g. `Summer19UL18_RunA`)")
+                            "campaign (e.g. `Summer19UL18`)")
                .arg<float>("core_width", "corrections.JER.core_width",
                            "width of the Gaussian core in units of sigma "
-                           "(for the matching algorithm)");
+                           "(for the matching algorithm)")
+               .arg<fs::path>("smear", "corrections.JER.smear",
+                              "path to JER smearing correctionlib table "
+                              "(use /dev/null to rely on a built-in smearing)");
 
         const auto& config = options(argc, argv);
         const auto& slice = options.slice();
diff --git a/JEC/bin/applyJEScorrections.cc b/JEC/bin/applyJEScorrections.cc
index 6978db869af2f519921eee09c0821c1f3b8fb6dc..30b8c7932a393ae45ae2dd26e7ecf4e4d594cd5d 100644
--- a/JEC/bin/applyJEScorrections.cc
+++ b/JEC/bin/applyJEScorrections.cc
@@ -51,6 +51,7 @@ void applyJEScorrections
     metainfo.Check(config);
     auto isMC = metainfo.Get<bool>("flags", "isMC");
     auto R = GetR(metainfo);
+    auto year = metainfo.Get<int>("flags", "year");
 
     auto genEvt = isMC ? flow.GetBranchReadOnly<GenEvent>("genEvent") : nullptr;
     auto recEvt = flow.GetBranchReadOnly<RecEvent>("recEvent");
@@ -125,8 +126,7 @@ void applyJEScorrections
                 BOOST_THROW_EXCEPTION( DE::AnomalousEvent("Unexpected JES corrections", tIn) );
 
             // nominal value
-            auto corr = nomSF->evaluate({recJet.area, recJet.p4.Eta(), recJet.p4.Pt(), pu->rho});
-            cout << recJet.p4.Pt() << ' ' << corr << '\n';
+            float corr = Evaluate(nomSF, cout, recJet, pu->rho, {}, *recEvt);
             recJet.scales.front() = corr;
 
             if (!applySyst) continue;
@@ -134,12 +134,11 @@ void applyJEScorrections
             // load uncertainties in JECs
             recJet.scales.reserve(JES_variations.size()*2+1);
             for (const correction::Correction::Ref& varSF: varSFs) {
-                auto var = varSF->evaluate({recJet.p4.Eta(), recJet.p4.Pt()}); /// \todo CorrPt?
+                float var = Evaluate(varSF, cout, recJet);
                 recJet.scales.push_back(corr*(1-var));
                 recJet.scales.push_back(corr*(1+var));
             }
         }
-        cout << flush;
 
         // CP after corrections
         for (size_t i = 0; i < calib.size(); ++i)
diff --git a/JEC/bin/common.h b/JEC/bin/common.h
index 478b82db3e1c78e048f486a7be9b465b7be7d061..fbbe77ffeef2cdabc9e1850c2bfdee4d0bfef80c 100644
--- a/JEC/bin/common.h
+++ b/JEC/bin/common.h
@@ -2,14 +2,19 @@
 
 #include <cassert>
 #include <iostream>
+#include <optional>
 #include <regex>
 #include <string>
 #include <sstream>
 
 #include "Core/CommonTools/interface/binnings.h"
+#include "Core/Objects/interface/Jet.h"
+#include "Core/Objects/interface/Event.h"
 
 #include <boost/algorithm/string.hpp>
+#include <boost/assert/source_location.hpp>
 
+#include <correction.h>
 #include <darwin.h>
 
 namespace DAS::JetEnergy {
@@ -26,20 +31,17 @@ inline static const std::vector<TString> absetaBins = MakeTitle(abseta_edges, "|
 inline static const std::map<int,std::vector<double>> rho_edges = {
     { 2016, {0, 6.69, 12.39, 18.09, 23.79, 29.49, 35.19, 40.9, 999} }, // EOY16
     { 2017, {0, 7.47, 13.49, 19.52, 25.54, 31.57, 37.59, 999} }, // UL17
-    { 2018, {0, 7.35, 13.26, 19.17, 25.08, 30.99, 36.9, 999} } // UL18
-};
+    { 2018, {0, 7.35, 13.26, 19.17, 25.08, 30.99, 36.9, 999} }, // UL18
 
-inline static const std::map<int,int> nRhoBins = {
-    {2016, rho_edges.at(2016).size()-1 },
-    {2017, rho_edges.at(2017).size()-1 },
-    {2018, rho_edges.at(2018).size()-1 }
+    { 2024, {0.0, 8.23, 14.49, 20.75, 27.01, 33.27, 39.53, 45.79, 52.05, 999} }, // Winter24 (copy of Summer23)
 };
 
-inline static const std::map<int,std::vector<TString>> rhoBins = {
-    { 2016, MakeTitle(rho_edges.at(2016), "#rho", false, false, [](double v) { return Form("%.2f", v);}) },
-    { 2017, MakeTitle(rho_edges.at(2017), "#rho", false, false, [](double v) { return Form("%.2f", v);}) },
-    { 2018, MakeTitle(rho_edges.at(2018), "#rho", false, false, [](double v) { return Form("%.2f", v);}) }
-};
+inline int GetNRhoBins (int year) { return rho_edges.at(year).size()-1; }
+inline std::vector<TString> rhoBins (int year)
+{
+    return MakeTitle(rho_edges.at(year), "#rho", false, false,
+                        [](double v) { return Form("%.2f", v); });
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 /// binning for JES uncertainties (taken from JES files)
@@ -153,12 +155,74 @@ CorrectionType GetCorrection (const auto& corrections, //!< from `correctionlib`
     BOOST_THROW_EXCEPTION(
             invalid_argument(
                 "No `"s + campaign + "*"s + type + "*" + level + "*"s + suffix
-                + "` correction can be found in the given tables."s
+                + "` correction can be found in the given tables. "s
                 + ScanCorrections(corrections)
             )
         );
 }
 
+////////////////////////////////////////////////////////////////////////////////
+/// \brief Wrapper to evaluate scale factors with DAS objects from
+///        correctionlib with exceptions.
+///
+/// The implementation is generic, namely for all years, corrections, and
+/// versions of the corrections, which may depend on various variables. The
+/// variables names (e.g. JetPt) are only applicable to JERC. This brute-force
+/// approach is necessary because of the unpredictable changes of conventions.
+float Evaluate (const auto& correction, //!< simple or compound correction
+                std::ostream& cout, //!< output stream, possible redirected
+                const RecJet& recJet, //!< for all corrections
+                const std::optional<float>& rho = {}, //!< e.g. for smearing
+                const std::optional<GenJet>& genJet = {}, //!< for stoch smearing
+                const std::optional<RecEvent>& recEvt = {}, //!< for the run or event number
+                const std::optional<std::string>& systematic = {}, //!< e.g. for JER
+                const std::optional<float>& JER = {}, //!< e.g. for stoch smearing
+                const std::optional<float>& JERSF = {}  //!< e.g. for stoch smearing
+               )
+try {
+    using namespace std;
+
+    cout << correction->name();
+    vector<correction::Variable::Type> inputs;
+    inputs.reserve(10);
+
+    auto push_back = [&cout,&inputs](const auto& v) {
+        inputs.push_back(v);
+        cout << '=' << v;
+    };
+
+    for (const correction::Variable& input: correction->inputs()) {
+        const string& n = input.name();
+        cout << ' ' << n;
+        if      (n == "JetPt"     ) push_back(recJet.CorrPt());
+        else if (n == "JetEta"    ) push_back(recJet.p4.Eta());
+        else if (n == "JetPhi"    ) push_back(recJet.p4.Phi());
+        else if (n == "JetEta"    ) push_back(recJet.p4.Phi());
+        else if (n == "JetA"      ) push_back(recJet.area);
+        else if (n == "Rho"       ) push_back(*rho);
+        else if (n == "systematic") push_back(*systematic);
+        else if (n == "GenPt"     ) push_back(genJet ? genJet->CorrPt() : -1);
+        else if (n == "EventID"   ) push_back((int) recEvt->evtNo);
+        else if (n == "run"       ) push_back((float) recEvt->runNo);
+        else if (n == "JER"       ) push_back(*JER);
+        else if (n == "JERSF"     ) push_back(*JERSF);
+        else BOOST_THROW_EXCEPTION( invalid_argument("`"s + n + "` is needed by "s
+                                    + correction->name() + " but not recognized."s) );
+        /// \exception A `std::invalid_argument` is thrown when an unrecognised variable
+        ///            is requested by the tables. It should then be sufficient to edit
+        ///            function.
+    }
+    auto corr = correction->evaluate(inputs);
+    cout << " -> " << corr << endl;
+    return corr;
+}
+catch (std::runtime_error& e) {
+    const char * name = typeid(*correction).name();
+    /// \todo Use `boost::core::demangle()` to provide a more readable function name?
+    auto location = boost::source_location(__FILE__, __LINE__, name);
+    boost::throw_exception(e, location);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 /// Determine the R values for which JetMET provides corrections.
 int GetR (const Darwin::Tools::UserInfo& metainfo)
diff --git a/JEC/bin/getJMEtables.cc b/JEC/bin/getJMEtables.cc
index ee41b3f419c4f3ec8280550d6e31c05f5af39b12..4c8b1f432d9ec1f0015d191af0d1c820bd9aea16 100644
--- a/JEC/bin/getJMEtables.cc
+++ b/JEC/bin/getJMEtables.cc
@@ -4,8 +4,7 @@
 #include <iostream>
 #include <string>
 
-#include <TH3D.h>
-#include <TF1.h>
+#include <TH3.h>
 #include <TFile.h>
 
 #include "Core/CommonTools/interface/binnings.h"
@@ -41,24 +40,21 @@ void getJMEtable
 
     auto fOut = DT::GetOutputFile(output);
 
+    auto isMC = config.get<bool>("flags.isMC");
     auto R = config.get<int>("flags.R");
     auto year = config.get<int>("flags.year");
-    string algo = "chs"; /// \todo identify algo
+    DT::UserInfo userinfo(config);
+    string algo = GetAlgo(userinfo);
     cout << R << ' ' << year << ' ' << algo << endl;
 
-    const auto tag = config.get<string>("corrections.JES.tag"), // correction tag
-               level = config.get<string>("corrections.JES.level"); // correction level
-    string key = tag + "_" + level + "_AK" + to_string(R) + "PF" + algo;
-    cout << key << endl;
+    auto campaign = config.get<string>("corrections.JME.campaign"); // calibration campaign
+    auto level = config.get<string>("corrections.JME.level"); // calibration level
+    string suffix = "AK" + to_string(R) + "PF" + algo;
 
     auto cset = correction::CorrectionSet::from_file(input.string());
-    correction::Correction::Ref sf;
-    try {
-        sf = cset->at(key);
-    }
-    catch (std::out_of_range& e) {
-        BOOST_THROW_EXCEPTION( std::invalid_argument("Nothing corresponding to that key") );
-    }
+    auto sf = GetCorrection<correction::Correction::Ref>(
+                    *cset, campaign, isMC ? "MC" : "DATA", level, suffix);
+    /// \todo Compound corrections?
 
     cout << "Declaring histogram" << endl;
     unique_ptr<TH1> h;
@@ -70,17 +66,79 @@ void getJMEtable
                                    rho_edges.at(year).size()-1,
                                    rho_edges.at(year).data());
         named_inputs["JetA"] = M_PI * pow(R*0.1, 2); // Jet area estimation
+        named_inputs["JetPhi"] = 0; /// \todo Use `THn` to get one more dimension
     }
     else if (level == "L2Relative" || level == "L2L3Residual")
         h = make_unique<TH2D>(level.c_str(), level.c_str(),
                                    nAbsEtaBins, abseta_edges .data(),
                                    nPtJERCbins, pt_JERC_edges.data());
+    else if (level == "PtResolution") {
+    // ├── 📈 Summer23BPixPrompt23_RunD_JRV1_MC_PtResolution_AK4PFPuppi (v1)
+    // │   PtResolution for AK4PFPuppi jets, created from Summer23BPixPrompt23_RunD_JRV1_MC by using
+    // │   https://gitlab.cern.ch/cms-jetmet/jerc2json
+    // │   Node counts: Binning: 39, Formula: 305
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ JetEta (real)                                                                                │
+    // │   │ pseudorapidity of the jet                                                                    │
+    // │   │ Range: [-5.191, 5.191), overflow ok                                                          │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ JetPt (real)                                                                                 │
+    // │   │ pT of the jet before specific correction (for JER and uncertainties: after all corrections   │
+    // │   │ applied)                                                                                     │
+    // │   │ Range: [-inf, inf), overflow ok                                                              │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ Rho (real)                                                                                   │
+    // │   │ energy density rho (as measure of PU)                                                        │
+    // │   │ Range: [0.0, 52.05), overflow ok                                                             │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭──── ◀ output ─────╮
+    // │   │ correction (real) │
+    // │   │ No description    │
+    // │   ╰───────────────────╯
+        h = make_unique<TH3D>(level.c_str(), level.c_str(),
+                                   nAbsEtaBins, abseta_edges .data(), /// \todo check binning
+                                   nPtJERCbins, pt_JERC_edges.data(),
+                                   rho_edges.at(year).size()-1,
+                                   rho_edges.at(year).data());
+    }
     else if (level == "ScaleFactor") {
-        h = make_unique<TH1D>(level.c_str(), level.c_str(),
-                                   nAbsEtaBins, abseta_edges .data()); /// \todo check binning
+    // ├── 📈 Summer23BPixPrompt23_RunD_JRV1_MC_ScaleFactor_AK4PFPuppi (v1)
+    // │   ScaleFactor for AK4PFPuppi jets, created from Summer23BPixPrompt23_RunD_JRV1_MC by using
+    // │   https://gitlab.cern.ch/cms-jetmet/jerc2json
+    // │   Node counts: Binning: 43, Category: 1470
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ JetEta (real)                                                                                │
+    // │   │ pseudorapidity of the jet                                                                    │
+    // │   │ Range: [-5.191, 5.191), overflow ok                                                          │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ JetPt (real)                                                                                 │
+    // │   │ pT of the jet before specific correction (for JER and uncertainties: after all corrections   │
+    // │   │ applied)                                                                                     │
+    // │   │ Range: [10.0, 5373.0), overflow ok                                                           │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭────────────────────────────────────────── ▶ input ───────────────────────────────────────────╮
+    // │   │ systematic (string)                                                                          │
+    // │   │ systematics: nom, up, down                                                                   │
+    // │   │ Values: down, nom, up                                                                        │
+    // │   ╰──────────────────────────────────────────────────────────────────────────────────────────────╯
+    // │   ╭──── ◀ output ─────╮
+    // │   │ correction (real) │
+    // │   │ No description    │
+    // │   ╰───────────────────╯
+    //
+        if (year < 2020) // run 2
+            h = make_unique<TH1D>(level.c_str(), level.c_str(),
+                                   nAbsEtaBins, abseta_edges.data()); /// \todo check binning
+        else // run 3
+            h = make_unique<TH2D>(level.c_str(), level.c_str(),
+                                   nAbsEtaBins, abseta_edges.data(), /// \todo check binning
+                                   nPtJERCbins, pt_JERC_edges.data());
         named_inputs["systematic"] = "nom";
     }
-    else // assuming uncertainties
+    else // assuming JES uncertainties
         h = make_unique<TH2D>(level.c_str(), level.c_str(),
                                    nEtaUncBins, eta_unc_edges.data(),
                                    nPtJERCbins, pt_JERC_edges.data());
@@ -89,6 +147,9 @@ void getJMEtable
     for (int etabin = 1; etabin <= h->GetNbinsX(); ++etabin)
     for (int  ptbin = 1;  ptbin <= h->GetNbinsY(); ++ ptbin)
     for (int rhobin = 1; rhobin <= h->GetNbinsZ(); ++rhobin) {
+        [[ maybe_unused ]]
+        static auto& cout = steering & DT::verbose ? ::cout : DT::dev_null;
+
         // set values by name
         named_inputs["JetEta"] = h->GetXaxis()->GetBinCenter(etabin);
         named_inputs["JetPt" ] = h->GetYaxis()->GetBinCenter( ptbin);
@@ -100,11 +161,12 @@ void getJMEtable
             inputs.push_back(named_inputs.at(input.name()));
 
         // get and fill the value
+
         double value = sf->evaluate(inputs);
 
         cout << setw(5) << ptbin << setw(5) << etabin << setw(5) << rhobin << setw(15) << value << endl;
 
-        if (level == "L1FastJet")
+        if (level == "L1FastJet" || level == "PtResolution")
             h->SetBinContent(etabin, ptbin, rhobin, value);
         else if (level == "ScaleFactor")
             h->SetBinContent(etabin, value);
@@ -129,10 +191,16 @@ int main (int argc, char * argv[])
         auto options = DAS::Options("Obtain JES corrections JetMET tables in ROOT histograms");
         options.input ("input" , &input , "JSON file containing the corrections")
                .output("output", &output, "output ROOT file")
-               .arg<string>("tag", "corrections.JES.tag", "tag (e.g. Summer19UL18_V5_MC)")
-               .arg<string>("level", "corrections.JES.level", "level (e.g. L2Relative)")
+               .arg<string>("campaign", "corrections.JME.campaign",
+                            "campaign (e.g. Summer19UL18_V5)")
+               .arg<string>("level", "corrections.JME.level", "level (e.g. L2Relative)")
+               /// \todo JES and JER campaigns are often different, but it would be nice to 
+               ///       1/ extract the config from the metainfo of a n-tuple
+               ///       2/ use this config as argument to the present executable to get a plot of the tables
+               .arg<bool>("isMC", "flags.isMC", "boolean flag")
                .arg<int>("R", "flags.R", "R (x10) parameter in jet clustering algorithm")
-               .arg<int>("year", "flags.year", "year (4 digits)");
+               .arg<int>("year", "flags.year", "year (4 digits)")
+               .args     ("labels", "flags.labels", "e.g. CHS or PUPPI");
 
         const auto& config = options(argc, argv);
         const int steering = options.steering();
diff --git a/JEC/bin/getJetResponse.cc b/JEC/bin/getJetResponse.cc
index 5b759e1d55d7f33259b658a18d57bdc646eae30f..175a6c45f11d6a06e04818b6bcd34b9b5a2292b3 100644
--- a/JEC/bin/getJetResponse.cc
+++ b/JEC/bin/getJetResponse.cc
@@ -76,10 +76,11 @@ void getJetResponse
                             mubinsResp;  // Response in bins of mu (true pileup)
 
     const auto year = metainfo.Get<int>("flags", "year");
+    const size_t nRhoBins = GetNRhoBins(year);
     if (hiPU) {
-        rhobinsResp.reserve(nRhoBins.at(year));
-         mubinsResp.reserve(nRhoBins.at(year));
-        for (int rhobin = 1; rhobin <= nRhoBins.at(year); ++rhobin) {
+        rhobinsResp.reserve(nRhoBins);
+         mubinsResp.reserve(nRhoBins);
+        for (int rhobin = 1; rhobin <= nRhoBins; ++rhobin) {
             rhobinsResp.push_back( makeRespHist( Form("rhobin%d", rhobin) , "JER") );
              mubinsResp.push_back( makeRespHist( Form( "mubin%d", rhobin) , "JER") );
         }
@@ -92,11 +93,12 @@ void getJetResponse
         auto recWgt = rEv->weights.front();
         optional<size_t> irho, imu;
         if (hiPU) {
-            static const size_t nrho = static_cast<size_t>(nRhoBins.at(year));
-            for (irho = 0; pileUp->rho > rho_edges.at(year).at(*irho+1) && *irho < nrho; ++*irho);
-            if (*irho >= nrho)
-                BOOST_THROW_EXCEPTION( DE::AnomalousEvent(Form("irho = %lu > nRhoBins.at(%d) = %d",
-                                                            *irho, year, nRhoBins.at(year)), tIn) );
+            for (irho = 0; pileUp->rho > rho_edges.at(year).at(*irho+1)
+                    && *irho < nRhoBins; ++*irho);
+            if (*irho >= nRhoBins)
+                BOOST_THROW_EXCEPTION(
+                        DE::AnomalousEvent(Form("irho = %lu > nRhoBins = %zu",
+                                                *irho, nRhoBins), tIn) );
             static const size_t imuMax = mubinsResp.size()-1;
             imu = static_cast<size_t>(pileUp->GetTrPU())/10;
             *imu = min(*imu,imuMax);
@@ -129,7 +131,7 @@ void getJetResponse
     inclResp->SetTitle("Response (inclusive)");
     inclResp->Write("Response");
     if (hiPU)
-    for (int irho = 0; irho < nRhoBins.at(year); ++irho) {
+    for (int irho = 0; irho < nRhoBins; ++irho) {
 
         /// \todo remove upper boundaries from titles of last bins
 
diff --git a/JEC/interface/JMEmatching.h b/JEC/interface/JMEmatching.h
index ba9789dabb8e573c389f6e91b1a33484432e6e3d..bab02c670696b3167ad333a9cd53c4e0d084d375 100644
--- a/JEC/interface/JMEmatching.h
+++ b/JEC/interface/JMEmatching.h
@@ -49,11 +49,11 @@ template<typename RecContainer = std::vector<RecJet>,
 
         // 2) declare a few utilities
 
-        typedef pair<size_t,size_t> Indices;
+        using Indices = pair<size_t,size_t>;
 
         auto DeltaR2 = [&recJets,&genJets](const Indices& indices) {
             const FourVector& recJet = recJets[indices.first ].p4,
-                              genJet = genJets[indices.second].p4;
+                            & genJet = genJets[indices.second].p4;
             return ROOT::Math::VectorUtil::DeltaR2(recJet, genJet);
         };
 
diff --git a/JEC/test/CMakeLists.txt b/JEC/test/CMakeLists.txt
index 8331da218a6a6d79f96b1b6f91de5645342fc651..1db38186f7a89a8823bfb44b94b5d880d845fc88 100644
--- a/JEC/test/CMakeLists.txt
+++ b/JEC/test/CMakeLists.txt
@@ -10,6 +10,6 @@ foreach(X
 )
     add_executable(${X} ${X}.cc)
     target_include_directories(${X} PRIVATE "${CMAKE_SOURCE_DIR}/interface")
-    target_link_libraries(${X} ROOT::Hist ROOT::Graf ROOT::HistPainter Darwin::Darwin JEC)
+    target_link_libraries(${X} ROOT::Hist ROOT::Graf ROOT::HistPainter Darwin::Darwin JEC correctionlib)
     add_test(NAME ${X} COMMAND ${X})
 endforeach()
diff --git a/Ntupliser/test/runAnalysis.sh b/Ntupliser/test/runAnalysis.sh
index bffcba6923cc04d1d0ef713055bf05ee698c5f86..ed89c9243acb8ecf6db2b25bef8851f5dfbcc518 100755
--- a/Ntupliser/test/runAnalysis.sh
+++ b/Ntupliser/test/runAnalysis.sh
@@ -43,7 +43,7 @@ if [ "$isMC" = "yes" ]; then
     getJetResponse applyJEScorrections.root getJetResponse.root false
     fitJetResponse getJetResponse.root fitJetResponse.root
     fitJetResolution fitJetResponse.root fitJetResolution.root
-    applyJERsmearing applyJEScorrections.root applyJERsmearing.root $JEC_TABLES Summer19UL18 1 -f
+    applyJERsmearing applyJEScorrections.root applyJERsmearing.root $JEC_TABLES Summer19UL18 1 /dev/null -f
 
     getBTagBinnedDiscriminant applyJERsmearing.root getBTagBinnedDiscriminant.root
     getBTagPerformance getBTagBinnedDiscriminant.root getBTagPerformance.root