diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt b/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a8eefd921932c91ed1c9b78b3b085f08e9901645
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+#######################
+# TruthClassification #
+#######################
+atlas_subdir(TruthClassification)
+
+atlas_add_library(TruthClassificationLib
+  TruthClassification/*.h Root/*.cxx
+  PUBLIC_HEADERS TruthClassification
+  LINK_LIBRARIES AsgTools AsgAnalysisInterfaces xAODBase xAODEgamma xAODMuon
+  PRIVATE_LINK_LIBRARIES MCTruthClassifierLib)
+
+atlas_add_dictionary(TruthClassificationDict
+  TruthClassification/TruthClassificationDict.h
+  TruthClassification/selection.xml
+  LINK_LIBRARIES TruthClassificationLib)
+
+if (NOT XAOD_STANDALONE)
+  atlas_add_component(TruthClassification
+    src/*.h src/*.cxx src/components/*.cxx
+    LINK_LIBRARIES TruthClassificationLib)
+endif ()
+
+# Extra test libraries based on the build environment:
+set(xaod_access_lib)
+set(extra_libs)
+# ... for AnalysisBase
+if (XAOD_STANDALONE)
+  set(xaod_access_lib xAODRootAccess)
+else()
+  # ... for AthAnalysisBase (Athena calls this POOLRootAccess)
+  set(xaod_access_lib POOLRootAccessLib)
+endif()
+
+# Executable(s) in the package:
+atlas_add_executable(testTruthClassificationTool
+  test/test_TruthClassificationTool.cxx
+  LINK_LIBRARIES TruthClassificationLib xAODEventInfo xAODTruth MuonAnalysisInterfacesLib
+  ${xaod_access_lib} ${extra_libs})
+
+# Tests
+# this test currently needs a special input file as the default ASG test files
+# do not contain all necessary information
+atlas_add_test(TruthClassificationToolTest
+  SCRIPT testTruthClassificationTool
+  /cvmfs/atlas-nightlies.cern.ch/repo/data/data-art/SUSYTools/DAOD_PHYSVAL.mc16_13TeV.410470.FS_mc16e_p4017.PHYSVAL.pool.root)
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..b3b3b5a047f0e1097f7b41599ea6479bb2142056
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx
@@ -0,0 +1,622 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include <set>
+
+#include <MCTruthClassifier/MCTruthClassifierDefs.h>
+#include <xAODTruth/TruthParticle.h>
+#include <xAODTruth/xAODTruthHelpers.h>
+
+#include "TruthClassification/TruthClassificationTool.h"
+
+namespace
+{
+  bool isInSet(int origin, const std::set<int> &s)
+  {
+    return s.find(origin) != s.end();
+  }
+}
+
+
+TruthClassificationTool::TruthClassificationTool(const std::string &type)
+  : asg::AsgTool(type)
+{
+  declareProperty ("separateChargeFlipElectrons", m_separateChargeFlipElectrons, "separate prompt charge-flipped electrons");
+}
+
+
+StatusCode TruthClassificationTool::classify(const xAOD::IParticle &particle,
+                                             unsigned int &classification) const
+{
+  Truth::Type type = Truth::Type::Unknown;
+  ANA_CHECK(classify(particle, type));
+  classification = static_cast<int>(type);
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TruthClassificationTool::classify(const xAOD::IParticle &particle,
+                                             Truth::Type &classification) const
+{
+  if (dynamic_cast<const xAOD::Electron *> (&particle))
+  {
+    ANA_CHECK(classify(*dynamic_cast<const xAOD::Electron *> (&particle), classification));
+  }
+  else if (dynamic_cast<const xAOD::Muon *> (&particle))
+  {
+    ANA_CHECK(classify(*dynamic_cast<const xAOD::Muon *> (&particle), classification));
+  }
+  else
+  {
+    ANA_MSG_ERROR("Only electrons and muons are supported.");
+    return StatusCode::FAILURE;
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TruthClassificationTool::classify(const xAOD::Electron &electron,
+                                             Truth::Type &classification) const
+{
+  namespace MC = MCTruthPartClassifier;
+
+  if (!m_truthPdgId.isAvailable(electron))
+  {
+    ANA_MSG_ERROR("Electron does not have the 'truthPdgId' decoration.");
+    return StatusCode::FAILURE;
+  }
+
+  if (!m_firstMotherTruthType.isAvailable(electron)
+    || !m_firstMotherTruthOrigin.isAvailable(electron)
+    || !m_firstMotherPdgId.isAvailable(electron))
+  {
+    ANA_MSG_ERROR("Electron does not have one or more 'firstEgMother' decorations.");
+    return StatusCode::FAILURE;
+  }
+
+  int type = m_truthType(electron);
+  int origin = m_truthOrigin(electron);
+  int pdgId = m_truthPdgId(electron);
+  int firstMotherType = m_firstMotherTruthType(electron);
+  int firstMotherOrigin = m_firstMotherTruthOrigin(electron);
+  int firstMotherPdgId = m_firstMotherPdgId(electron);
+  // not in the smart slimming list, thus only in few derivations
+  int lastMotherType = m_lastMotherTruthType.isAvailable(electron) ? m_lastMotherTruthType(electron) : -1;
+  int lastMotherOrigin = m_lastMotherTruthOrigin.isAvailable(electron) ? m_lastMotherTruthOrigin(electron) : -1;
+  int lastMotherPdgId = m_lastMotherPdgId.isAvailable(electron) ? m_lastMotherPdgId(electron) : -1;
+  // fallback recorations
+  int fallbackType{-1};
+  if (m_fallbackTruthType.isAvailable(electron) && m_fallbackDR.isAvailable(electron))
+  {
+    fallbackType = m_fallbackDR(electron) < 0.05 ? m_fallbackTruthType(electron) : -1;
+  }
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Prompt Photon Conversions
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  // gamma -> e+ e-
+  if (type == MC::BkgElectron
+    && (origin == MC::PhotonConv || origin == MC::ElMagProc)
+    && firstMotherType == MC::IsoPhoton && firstMotherOrigin == MC::PromptPhot)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // H -> gamma gamma, gamma -> e+ e-
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::IsoPhoton && firstMotherOrigin == MC::Higgs)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // bkg electrons from bkg photons
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::UnknownPhoton && firstMotherOrigin == MC::NonDefined)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // bkg photon from UndrPhot; (Here there is a generator level photon (not gen electron ) that later converts)
+  if (type == MC::BkgElectron
+    && (origin == MC::PhotonConv || origin == MC::ElMagProc )
+    && firstMotherType == MC::BkgPhoton && firstMotherOrigin == MC::UndrPhot)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // type = 16 and origin = 38 (again, this is a photon)
+  if (type == MC::BkgPhoton && origin == MC::UndrPhot)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // Is an isolated photon
+  if (type == MC::IsoPhoton && pdgId == 22)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // electrons from ElMagProc
+  // when FSR, a better classification can be made with the fall back vars
+  if (type == MC::BkgElectron && origin == MC::ElMagProc
+    && firstMotherType == MC::UnknownPhoton && firstMotherOrigin == MC::NonDefined)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+  if (type == MC::BkgElectron && origin == MC::ElMagProc
+    && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot)
+  {
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // TODO: Message from Otilia: """
+  // but it's not clear if these electrons are really
+  // "fakes" or they should go in the real category (we don't know from where
+  // this photon is coming...). I would say more truth studies should be done.
+  // """
+  // Hence the warning message...
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::Unknown && firstMotherOrigin == MC::ZBoson)
+  {
+    ANA_MSG_WARNING("Electron identified as from a PromptPhotonConversion, "
+                    "but this type of electron needs further study!");
+    classification = Truth::Type::PromptPhotonConversion;
+    return StatusCode::SUCCESS;
+  }
+
+  // when always a photon (last mum is a photon, even if the truth PDG is 11 and first mum PDG is 11 ):
+  // very likely these are internal conversions;  last_mum_pdgId == 22 important as the cases with last_mum_pdgId == 11 were found to be quite often close to a true electron
+  if (type == MC::BkgElectron && firstMotherType == MC::BkgElectron
+    && origin == MC::PhotonConv && firstMotherOrigin == MC::PhotonConv
+    && std::abs(firstMotherPdgId) == 11 && std::abs(pdgId) == 11)
+  {  // electron
+    if(lastMotherType == -1 || (lastMotherType == MC::GenParticle && (lastMotherPdgId == 22 || std::abs(lastMotherPdgId) == 11)))
+    {
+      // lastMotherType == -1 ==>  when the last mother info is not stored in the derivations
+      classification = Truth::Type::PromptPhotonConversion;
+      return StatusCode::SUCCESS;
+    }
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Is muon reco as electron or ele radiated by muons
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if ((type == MC::NonIsoElectron || type == MC::NonIsoPhoton)
+    && (origin == MC::Mu || firstMotherOrigin == MC::Mu))
+  {
+    classification = Truth::Type::ElectronFromMuon;
+    return StatusCode::SUCCESS;
+  }
+
+  if (type == MC::BkgElectron && firstMotherOrigin == MC::Mu)
+  {
+    classification = Truth::Type::ElectronFromMuon;
+    return StatusCode::SUCCESS;
+  }
+
+  if (type == MC::IsoMuon || type == MC::NonIsoMuon || type == MC::BkgMuon || type == MC::UnknownMuon) {
+    classification = Truth::Type::ElectronFromMuon;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Tau decays
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  // Non-isolated electron/photon from tau decay
+  if ((type == MC::NonIsoElectron || type == MC::NonIsoPhoton)
+    && origin == MC::TauLep)
+  {
+    classification = Truth::Type::TauDecay;
+    return StatusCode::SUCCESS;
+  }
+
+  // tau -> tau gamma, gamma -> e+ e-, etc
+  if ((firstMotherType == MC::NonIsoElectron || firstMotherType == MC::NonIsoPhoton)
+    && firstMotherOrigin == MC::TauLep)
+  {
+    classification = Truth::Type::TauDecay;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Light hadron sources
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type == MC::Hadron || fallbackType == MC::Hadron)
+  {
+    classification = Truth::Type::LightFlavorDecay;
+    return StatusCode::SUCCESS;
+  }
+
+  if (firstMotherType == MC::BkgElectron)
+  {
+    if ((origin == MC::DalitzDec || origin == MC::ElMagProc)
+      && (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin)))
+    {
+      classification = Truth::Type::LightFlavorDecay;
+      return StatusCode::SUCCESS;
+    }
+  }
+
+  if (type == MC::BkgElectron)
+  {
+    if (origin == MC::DalitzDec || firstMotherOrigin == MC::DalitzDec) {
+      classification = Truth::Type::LightFlavorDecay;
+      return StatusCode::SUCCESS;
+    }
+    if (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin)) {
+      classification = Truth::Type::LightFlavorDecay;
+      return StatusCode::SUCCESS;
+    }
+  }
+
+  if (type == MC::BkgPhoton
+    && (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin)))
+  {
+    classification = Truth::Type::LightFlavorDecay;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // From B hadron
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (hasBHadronOrigin(origin) || hasBHadronOrigin(firstMotherOrigin))
+  {
+    classification = Truth::Type::BHadronDecay;
+    return StatusCode::SUCCESS;
+  }
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // From C hadron
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type != MC::IsoElectron && (hasCHadronOrigin(origin) || hasCHadronOrigin(firstMotherOrigin)))
+  {
+    classification = Truth::Type::CHadronDecay;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Prompt / Isolated electrons
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (isPromptElectron(electron))
+  {
+    if (m_separateChargeFlipElectrons && isChargeFlipElectron(electron))
+    {
+      classification = Truth::Type::ChargeFlipIsoElectron;
+      return StatusCode::SUCCESS;
+    }
+
+    classification = Truth::Type::IsoElectron;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Unknown & known Unknown
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  // TODO: See if we want this or not. Now we check if this is something we are
+  // able to classify or not. Note that this might be a bit dangerous because
+  // the reasons for not having origin and status codes might be complex. The
+  // main idea is to weed out things we don't have a hope of classifying due to
+  // missing or unknown information.
+  const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(electron);
+  int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : 0;
+  bool stable = (status == 1);
+
+  if (origin == MC::NonDefined && firstMotherOrigin == MC::NonDefined)
+  {
+    if (!stable)
+    {
+      if ((type == MC::Unknown || type == MC::UnknownPhoton) && firstMotherType == MC::Unknown)
+      {
+        classification = Truth::Type::KnownUnknown;
+        return StatusCode::SUCCESS;
+      }
+    } else {
+      if ((type == MC::Unknown && firstMotherType == MC::Unknown)
+        || (type == MC::UnknownElectron && firstMotherType == MC::UnknownElectron))
+      {
+        classification = Truth::Type::KnownUnknown;
+        return StatusCode::SUCCESS;
+      }
+
+      if (type == MC::UnknownPhoton)
+      {
+        classification = Truth::Type::KnownUnknown;
+        return StatusCode::SUCCESS;
+      }
+    }
+  }
+
+  // non-iso photons with no info available to classify
+  if (type == MC::NonIsoPhoton && origin == MC::FSRPhot && pdgId == 22
+    && firstMotherType == 0 && firstMotherOrigin == 0 && firstMotherPdgId == 0)
+  {
+    if (lastMotherType == -1 || (lastMotherType == 0 && lastMotherOrigin == 0 && lastMotherPdgId == 0))
+    { // last_firstMotherType == -1 ==>  when the last_mum info is not stored in the derivations
+      classification = Truth::Type::KnownUnknown;
+      return StatusCode::SUCCESS;
+    }
+  }
+
+  ANA_MSG_WARNING("Electron type unknown: type = " << type << ", origin = " << origin);
+
+  // debug printout
+  if (truthParticle != nullptr)
+  {
+    const xAOD::TruthParticle *parent = truthParticle;
+    ATH_MSG_DEBUG("Unknown particle decay chain:");
+    std::string out = "\t";
+    while (parent != nullptr)
+    {
+      out.append(std::to_string(parent->pdgId()));
+      parent = parent->parent();
+      if (parent) out.append(" -> ");
+    }
+    ATH_MSG_DEBUG(out);
+  }
+
+  classification = Truth::Type::Unknown;
+  return StatusCode::SUCCESS;
+}
+
+
+StatusCode TruthClassificationTool::classify(const xAOD::Muon &muon,
+                                             Truth::Type &classification) const
+{
+  namespace MC = MCTruthPartClassifier;
+
+  int type = m_truthType(muon);
+  int origin = m_truthOrigin(muon);
+  // fallback recorations
+  int fallbackType{-1};
+  if (m_fallbackTruthType.isAvailable(muon) && m_fallbackDR.isAvailable(muon))
+  {
+    fallbackType = m_fallbackDR(muon) < 0.05 ? m_fallbackTruthType(muon) : -1;
+  }
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // muons from taus
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type == MC::NonIsoMuon && origin == MC::TauLep)
+  {
+    classification = Truth::Type::TauDecay;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Light hadron sources
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type == MC::BkgMuon && hasLightHadronOrigin(origin))
+  {
+    classification = Truth::Type::LightFlavorDecay;
+    return StatusCode::SUCCESS;
+  }
+
+  if (type == MC::Hadron || fallbackType == MC::Hadron )
+  {
+    classification = Truth::Type::LightFlavorDecay;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // From B hadron
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (hasBHadronOrigin(origin))
+  {
+    classification = Truth::Type::BHadronDecay;
+    return StatusCode::SUCCESS;
+  }
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // From C hadron
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type != MC::IsoMuon && hasCHadronOrigin(origin))
+  {
+    classification = Truth::Type::CHadronDecay;
+    return StatusCode::SUCCESS;
+  }
+  // TODO:: There is a comment in the example code about J/psi but there is a
+  // separate origin code for that: `MC::JPsi == 28.` --> this might not be in all samples/generators?
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // prompt muons
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Check if the type of muon is IsoMuon(6) and whether the origin
+  // of the muon is from a prompt source
+  static const std::set<int> promptOrigin({
+      MC::SingleMuon,  // Single muon (origin = 2) from muon twiki
+      MC::top,
+      MC::WBoson,
+      MC::ZBoson,
+      MC::Higgs,
+      MC::HiggsMSSM,
+      MC::SUSY,
+      MC::DiBoson,
+      MC::CCbarMeson, // PromptQuarkoniumDecay
+      MC::BBbarMeson,
+  });
+  if (type == MC::IsoMuon && isInSet(origin, promptOrigin))
+  {
+    classification = Truth::Type::PromptMuon;
+    return StatusCode::SUCCESS;
+  }
+
+
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  // Unknown & known Unknown
+  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  if (type == MC::UnknownMuon && origin == MC::NonDefined)
+  {
+    classification = Truth::Type::KnownUnknown;
+    return StatusCode::SUCCESS;
+  }
+
+  const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(muon);
+  int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : 0;
+  bool stable = (status == 1);
+
+  if (!stable) {
+    if (type == MC::Unknown && origin == MC::NonDefined)
+    { // Data
+      classification = Truth::Type::KnownUnknown;
+      return StatusCode::SUCCESS;
+    }
+    if (type == -99999 && origin == -99999)
+    { // MC - no status = 1 truth particle associated with the primary track
+      classification = Truth::Type::KnownUnknown;
+      return StatusCode::SUCCESS;
+    }
+  }
+
+  ATH_MSG_WARNING("Muon type unknown: type = " << type << ", origin = " << origin);
+
+  classification = Truth::Type::Unknown;
+  return StatusCode::SUCCESS;
+}
+
+
+bool TruthClassificationTool::isPromptElectron(const xAOD::Electron &electron) const
+{
+  namespace MC = MCTruthPartClassifier;
+
+  // Electron is IsoElectron - return true
+  int type = m_truthType(electron);
+  if (type == MC::IsoElectron)
+  {
+    return true;
+  }
+
+  int origin = m_truthOrigin(electron);
+  int pdgId = m_truthPdgId(electron);
+  int firstMotherType = m_firstMotherTruthType(electron);
+  int firstMotherOrigin = m_firstMotherTruthOrigin(electron);
+
+  // Adding these cases from ElectronEfficiencyHelpers
+  if (firstMotherType == MC::IsoElectron && std::abs(m_firstMotherPdgId(electron)) == 11)
+  {
+    return true;
+  }
+
+  // FSR photons from electrons
+  if (origin == MC::FSRPhot && type == MC::NonIsoPhoton && std::abs(pdgId) == 11)
+  {
+    return true;
+  }
+
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot
+    && std::abs(pdgId) == 11)
+  {
+    return true;
+  }
+
+  // If we reach here then it is not a prompt electron
+  return false;
+}
+
+
+
+bool TruthClassificationTool::isChargeFlipElectron(const xAOD::Electron &electron) const
+{
+  namespace MC = MCTruthPartClassifier;
+
+  int type = m_truthType(electron);
+  int origin = m_truthOrigin(electron);
+  int pdgId = m_truthPdgId(electron);
+  int firstMotherType = m_firstMotherTruthType(electron);
+  int firstMotherOrigin = m_firstMotherTruthOrigin(electron);
+  int firstMotherPdgId = m_firstMotherPdgId(electron);
+
+  // not consider FSR photons from electrons (the photon has no charge)
+  if (origin == MC::FSRPhot && type == MC::NonIsoPhoton && std::abs(pdgId) == 11)
+  {
+    return false;
+  }
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot
+    && std::abs(pdgId) == 11)
+  {
+    return false;
+  }
+
+  // bkg electrons with no additional info to help us classify them FSR -- not in the charge flip category
+  if (type == MC::BkgElectron && origin == MC::PhotonConv
+    && firstMotherType == MC::BkgElectron && firstMotherOrigin == MC::PhotonConv
+    && std::abs(pdgId) == 11)
+  {
+	  return false;
+  }
+
+  if (electron.charge() != 0) {
+    return (firstMotherPdgId * electron.charge()) > 0;
+  }
+
+  return (firstMotherPdgId * (-pdgId)) > 0;
+}
+
+
+bool TruthClassificationTool::hasBHadronOrigin(int origin) const
+{
+  namespace MC = MCTruthPartClassifier;
+  static const std::set<int> b_hadrons({
+    MC::BottomMeson,
+    MC::BBbarMeson,
+    MC::BottomBaryon,
+  });
+  return isInSet(origin, b_hadrons);
+}
+
+bool TruthClassificationTool::hasCHadronOrigin(int origin) const {
+  namespace MC = MCTruthPartClassifier;
+  static const std::set<int> c_hadrons({
+    MC::CharmedMeson,
+    MC::CCbarMeson,
+    MC::CharmedBaryon,
+  });
+  return isInSet(origin, c_hadrons);
+}
+
+bool TruthClassificationTool::hasLightHadronOrigin(int origin) const {
+  namespace MC = MCTruthPartClassifier;
+  static const std::set<int> light_source({
+    MC::PiZero,
+    MC::LightMeson,
+    MC::StrangeMeson,
+    MC::LightBaryon,
+    MC::StrangeBaryon,
+    MC::PionDecay,
+    MC::KaonDecay,
+  });
+  return isInSet(origin, light_source);
+}
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h
new file mode 100644
index 0000000000000000000000000000000000000000..c7f86cf43a9617afa21aae615439b0b2fdbe9743
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h
@@ -0,0 +1,10 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_
+#define TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_
+
+#include "TruthClassification/TruthClassificationTool.h"
+
+#endif // TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..64dbdee157985fff91a72da983e80c40a46f32a1
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h
@@ -0,0 +1,71 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_
+#define TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_
+
+#include <string>
+
+#include <AsgAnalysisInterfaces/ITruthClassificationTool.h>
+#include <AsgTools/AsgTool.h>
+#include <xAODEgamma/Electron.h>
+#include <xAODMuon/Muon.h>
+
+
+/// \brief a tool to classify particles based on their type and origin
+class TruthClassificationTool : virtual public ITruthClassificationTool, public asg::AsgTool
+{
+  ASG_TOOL_CLASS2(TruthClassificationTool, IClassificationTool, ITruthClassificationTool)
+
+public:
+  explicit TruthClassificationTool(const std::string &type);
+
+  /// \brief classify and return unsigned int
+  virtual StatusCode classify(const xAOD::IParticle &particle,
+                              unsigned int &classification) const override;
+
+  /// \brief classify and return Truth::Type
+  virtual StatusCode classify(const xAOD::IParticle &particle,
+                              Truth::Type &classification) const override;
+
+private:
+  /// \brief electron classification helper
+  StatusCode classify(const xAOD::Electron &electron,
+                      Truth::Type &classification) const;
+
+  /// \brief muon classification helper
+  StatusCode classify(const xAOD::Muon &muon,
+                      Truth::Type &classification) const;
+
+  /// \brief separately store charge-flip electrons
+  bool m_separateChargeFlipElectrons{};
+
+  // accessors
+  const SG::AuxElement::ConstAccessor<int> m_truthType{"truthType"};
+  const SG::AuxElement::ConstAccessor<int> m_truthOrigin{"truthOrigin"};
+  const SG::AuxElement::ConstAccessor<int> m_truthPdgId{"truthPdgId"};
+  const SG::AuxElement::ConstAccessor<int> m_firstMotherTruthType{"firstEgMotherTruthType"};
+  const SG::AuxElement::ConstAccessor<int> m_firstMotherTruthOrigin{"firstEgMotherTruthOrigin"};
+  const SG::AuxElement::ConstAccessor<int> m_firstMotherPdgId{"firstEgMotherPdgId"};
+  const SG::AuxElement::ConstAccessor<int> m_lastMotherTruthType{"lastEgMotherTruthType"};
+  const SG::AuxElement::ConstAccessor<int> m_lastMotherTruthOrigin{"lastEgMotherTruthOrigin"};
+  const SG::AuxElement::ConstAccessor<int> m_lastMotherPdgId{"lastEgMotherPdgId"};
+  const SG::AuxElement::ConstAccessor<int> m_fallbackTruthType{"TruthClassifierFallback_truthType"};
+  const SG::AuxElement::ConstAccessor<int> m_fallbackTruthOrigin{"TruthClassifierFallback_truthOrigin"};
+  const SG::AuxElement::ConstAccessor<float> m_fallbackDR{"TruthClassifierFallback_dR"};
+
+  /// \brief a helper to check if an electron is prompt
+  bool isPromptElectron(const xAOD::Electron &electron) const;
+  /// \brief a helper to check if an electron has incorrectly reconstructed charge
+  bool isChargeFlipElectron(const xAOD::Electron &electron) const;
+
+  /// \brief a helper to check if the origin is a b-hadron
+  bool hasBHadronOrigin(int origin) const;
+  /// \brief a helper to check if the origin is a c-hadron
+  bool hasCHadronOrigin(int origin) const;
+  /// \brief a helper to check if the origin is a light hadron
+  bool hasLightHadronOrigin(int origin) const;
+};
+
+#endif  // TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d7ab4e95c19f66e6e8a26bfa9830ebd6dc295dc0
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml
@@ -0,0 +1,3 @@
+<lcgdict>
+  <class name="TruthClassificationTool" />
+</lcgdict>
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..bc92f6d4147a0f585c31fdb7a2b4cf7a91c76ebf
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx
@@ -0,0 +1,7 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#include "TruthClassification/TruthClassificationTool.h"
+
+DECLARE_COMPONENT(TruthClassificationTool)
diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..79447d720ab325bb46d1da1a852e6a0eaa51db9c
--- /dev/null
+++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx
@@ -0,0 +1,346 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+// EDM include(s):
+#include <AsgMessaging/MessageCheck.h>
+#include <AsgTools/AnaToolHandle.h>
+
+// Project dependent include(s)
+#ifdef XAOD_STANDALONE
+#include <xAODRootAccess/Init.h>
+#include <xAODRootAccess/TEvent.h>
+#else
+#include <POOLRootAccess/TEvent.h>
+#endif
+
+// Tool interface(s):
+#include <AsgAnalysisInterfaces/IClassificationTool.h>
+#include <MuonAnalysisInterfaces/IMuonSelectionTool.h>
+
+// EDM include(s):
+#include <xAODEventInfo/EventInfo.h>
+#include <xAODEgamma/ElectronContainer.h>
+#include <xAODMuon/MuonContainer.h>
+#include <xAODTruth/TruthParticle.h>
+#include <xAODTruth/xAODTruthHelpers.h>
+
+// ROOT include(s):
+#include <TFile.h>
+
+// std
+#include <iomanip>
+#include <memory>
+
+// messaging
+ANA_MSG_HEADER(Test)
+ANA_MSG_SOURCE(Test, "TruthClassification")
+using namespace Test;
+
+int main(int argc, char* argv[])
+{
+  ANA_CHECK_SET_TYPE (int); // makes ANA_CHECK return ints if exiting function
+
+  // Initialise the application:
+#ifdef XAOD_STANDALONE
+  StatusCode::enableFailure();
+  ANA_CHECK(xAOD::Init());
+#else
+  IAppMgrUI *app = POOL::Init();
+#endif
+
+  // Use a default MC file for testing if none is provided
+  TString fileName = "$ASG_TEST_FILE_MC";
+  if (argc < 2) {
+    ANA_MSG_WARNING("No file name received, using ASG_TEST_FILE_MC");
+  } else {
+    fileName = argv[1]; // use the user provided file
+  }
+
+  // The map of class names
+  std::map<int, std::string> classNames {
+    {0, "Unknown"},
+    {1, "KnownUnknown"},
+    {2, "IsoElectron"},
+    {3, "ChargeFlipIsoElectron"},
+    {4, "PromptMuon"},
+    {5, "PromptPhotonConversion"},
+    {6, "ElectronFromMuon"},
+    {7, "TauDecay"},
+    {8, "BHadronDecay"},
+    {9, "CHadronDecay"},
+    {10, "LightFlavorDecay"}
+  };
+
+  // Initialise TEvent reading
+#ifdef XAOD_STANDALONE
+  std::unique_ptr<TFile> ptrFile(TFile::Open(fileName, "READ"));
+  xAOD::TEvent event(xAOD::TEvent::kClassAccess);
+  ANA_CHECK(event.readFrom(ptrFile.get()));
+#else
+  POOL::TEvent event(POOL::TEvent::kClassAccess);
+  ANA_CHECK(event.readFrom(fileName));
+#endif
+  ANA_MSG_INFO("Opened file: " << fileName);
+
+  // Decide how many events to run over:
+  uint64_t entries = event.getEntries();
+  if (argc > 2) {
+    const uint64_t e = atoll(argv[2]);
+    if (e < entries) {
+      entries = e;
+    }
+  }
+
+  bool do_electrons = true;
+  bool do_muons = true;
+
+  // Create the truth classification tool:
+  ANA_MSG_INFO("Creating TruthClassificationTool...");
+  asg::AnaToolHandle< CP::IClassificationTool > tool("TruthClassificationTool/TruthClassificationTool");
+  ANA_CHECK(tool.retrieve());
+
+  const SG::AuxElement::ConstAccessor<int> truthTypeAcc("truthType");
+  const SG::AuxElement::ConstAccessor<int> truthOriginAcc("truthOrigin");
+  const SG::AuxElement::ConstAccessor<int> truthPdgIdAcc("truthPdgId");
+  const SG::AuxElement::ConstAccessor<int> firstMotherTruthTypeAcc("firstEgMotherTruthType");
+  const SG::AuxElement::ConstAccessor<int> firstMotherTruthOriginAcc("firstEgMotherTruthOrigin");
+  const SG::AuxElement::ConstAccessor<int> firstMotherPdgIdAcc("firstEgMotherPdgId");
+  const SG::AuxElement::ConstAccessor<int> lastMotherTruthTypeAcc("lastEgMotherTruthType");
+  const SG::AuxElement::ConstAccessor<int> lastMotherTruthOriginAcc("lastEgMotherTruthOrigin");
+  const SG::AuxElement::ConstAccessor<int> lastMotherPdgIdAcc("lastEgMotherPdgId");
+  // Fallback variables
+  const SG::AuxElement::ConstAccessor<int> fallbackTruthTypeAcc("TruthClassifierFallback_truthType");
+  const SG::AuxElement::ConstAccessor<int> fallbackTruthOriginAcc("TruthClassifierFallback_truthOrigin");
+  const SG::AuxElement::ConstAccessor<float> fallbackDRAcc("TruthClassifierFallback_dR");
+  // Ambiguity flag
+  const SG::AuxElement::ConstAccessor<int> ambiguityAcc("DFCommonAddAmbiguity");
+  const SG::AuxElement::ConstAccessor<char> passDFLooseAcc("DFCommonElectronsLHLoose");
+  const SG::AuxElement::ConstAccessor<char> passDFLooseBLAcc("DFCommonElectronsLHLooseBL");
+  const SG::AuxElement::ConstAccessor<char> passDFTightAcc("DFCommonElectronsLHTight");
+
+  // Muon selection tool
+  asg::AnaToolHandle< CP::IMuonSelectionTool > muonSelectionTool("CP::MuonSelectionTool/MuonSelectionTool");
+  // Tight: 0, Med: 1, Loose: 2, VeryLoose: 3
+  ANA_CHECK(muonSelectionTool.setProperty("MuQuality", 1));
+  ANA_CHECK(muonSelectionTool.setProperty("MaxEta", 2.4));
+  ANA_CHECK(muonSelectionTool.retrieve());
+
+  std::map<int, int> el_counter;
+  std::map<int, int> mu_counter;
+  std::map<int, std::map<int, int>> unknown_el_type_origin_counter;
+  std::map<int, int> unknown_mu_origin_counter;
+
+  std::map<int, std::map<int, std::map<int, std::map<int, int>>>> ambiguity_map;
+
+  // Loop over the events:
+  for (uint64_t entry = 0; entry < entries; ++entry)
+  {
+    // Tell the object which entry to look at:
+    event.getEntry(entry);
+
+    // Electrons
+    const xAOD::ElectronContainer *electrons{};
+    ANA_CHECK(event.retrieve(electrons, "Electrons"));
+
+    if (do_electrons)
+    {
+      for (const xAOD::Electron *el : *electrons)
+      {
+        // Look at electrons with pT > 1 GeV, |eta| < 2.47, LHLooseBL
+        if (el->pt() < 4500 || std::abs(el->eta()) > 2.47)
+        {
+          continue;
+        }
+
+        if ((passDFLooseBLAcc.isAvailable(*el) && !passDFLooseBLAcc(*el))
+          || (passDFLooseAcc.isAvailable(*el) && !passDFLooseAcc(*el)))
+        {
+          continue;
+        }
+
+        unsigned int classification{};
+        ANA_CHECK(tool->classify(*el, classification));
+
+        el_counter[classification]++;
+
+        int ambiguity = ambiguityAcc.isAvailable(*el) ? ambiguityAcc(*el) : -99;
+        if (classification == 0)
+        {
+          /// not in the smart slimming list, thus only in few derivations
+          int type = truthTypeAcc(*el);
+          int origin = truthOriginAcc(*el);
+          int lastMotherType = lastMotherTruthTypeAcc.isAvailable(*el) ? lastMotherTruthTypeAcc(*el) : -1;
+          int lastMotherOrigin = lastMotherTruthOriginAcc.isAvailable(*el) ? lastMotherTruthOriginAcc(*el) : -1;
+          int lastMotherPdgId = lastMotherPdgIdAcc.isAvailable(*el) ? lastMotherPdgIdAcc(*el) : -1;
+          int fallbackType = (fallbackTruthTypeAcc.isAvailable(*el) && fallbackDRAcc(*el) < 0.05) ? fallbackTruthTypeAcc(*el) : -1;
+          int fallbackOrigin = (fallbackTruthOriginAcc.isAvailable(*el) && fallbackDRAcc(*el) < 0.05) ? fallbackTruthOriginAcc(*el) : -1;
+          float fallbackDR = fallbackDRAcc.isAvailable(*el) ? fallbackDRAcc(*el) : -1;
+
+          const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(*el);
+          int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : -1;
+          int pdgId = truthPdgIdAcc.isAvailable(*el)
+            ? truthPdgIdAcc(*el)
+            : (truthParticle != nullptr && truthParticle->isAvailable<int>("pdgId")) ? truthParticle->pdgId() : 0;
+
+          ANA_MSG_WARNING("Unknown electron passing loose selection with "
+            << "status = " << status
+            << ", type = " << type
+            << ", origin = " << origin
+            << ", PDG ID = " << pdgId
+            << ", first mother type = " << firstMotherTruthTypeAcc(*el)
+            << ", first mother origin = " << firstMotherTruthOriginAcc(*el)
+            << ", first mother PDG ID = " << firstMotherPdgIdAcc(*el)
+            << ", last mother type = " << lastMotherType
+            << ", last mother origin = " << lastMotherOrigin
+            << ", last mother PDG ID = " << lastMotherPdgId
+            << ", fallback type = " << fallbackType
+            << ", fallback origin = " << fallbackOrigin
+            << ", fallback DR= " << fallbackDR
+            << ", ambiguity = " << ambiguity
+            << ", pt = " << el->pt());
+
+          unknown_el_type_origin_counter[type][origin]++;
+
+          if (truthParticle == nullptr) {
+            ANA_MSG_WARNING("Unknown electron is NOT truth matched!");
+          }
+        }
+
+        /// How ambiguity works (from
+        /// https://gitlab.cern.ch/atlas/athena/merge_requests/24852/)
+		    /// https://indico.cern.ch/event/862748/contributions/3635039/attachments/1946503/3235131/conversionsIFF2019.11.18.pdf, page 13
+		    /// "It is meant to be used together with ambiguityType to tighten the conversion veto by requiring (ambiguityType == 0 && DFCommonAddAmbiguity<=0)"
+        /// Cuts to define the various types :
+        /// -1 : no other track,
+        /// 0 : other track exists but no good gamma reco,
+        /// 1 : gamma*,
+        /// 2 : material conversion
+        if (ambiguityAcc.isAvailable(*el))
+        {
+          if (ambiguity >= -1) {
+            ambiguity_map[classification][ambiguity][truthTypeAcc(*el)][truthOriginAcc(*el)]++;
+          }
+        }
+      }
+    }
+
+    // Muons
+    if (do_muons)
+    {
+      const xAOD::EventInfo *eventInfo{};
+      ANA_CHECK(event.retrieve(eventInfo, "EventInfo"));
+
+      const xAOD::MuonContainer *muons{};
+      ANA_CHECK(event.retrieve(muons, "Muons"));
+
+      for (const xAOD::Muon *mu : *muons)
+      {
+        if (mu->pt() < 3000 || std::abs(mu->eta()) > 2.5)
+        {
+          continue;
+        }
+
+        if (!muonSelectionTool->accept(*mu)) {
+          continue;
+        }
+
+	      unsigned int classification{};
+        ANA_CHECK(tool->classify(*mu, classification));
+
+        mu_counter[classification]++;
+
+        if (classification == 0)
+        {
+          int type = truthTypeAcc(*mu);
+          int origin = truthOriginAcc(*mu);
+          int fallbackType = (fallbackTruthTypeAcc.isAvailable(*mu) && fallbackDRAcc(*mu) < 0.05) ? fallbackTruthTypeAcc(*mu) : -1;
+          int fallbackOrigin = (fallbackTruthOriginAcc.isAvailable(*mu) && fallbackDRAcc(*mu) < 0.05) ? fallbackTruthOriginAcc(*mu) : -1;
+          float fallbackDR = (fallbackDRAcc.isAvailable(*mu)) ? fallbackDRAcc(*mu) : -1;
+
+          const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(*mu);
+          int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : -1;
+          int pdgId = truthPdgIdAcc.isAvailable(*mu)
+            ? truthPdgIdAcc(*mu)
+            : (truthParticle != nullptr && truthParticle->isAvailable<int>("pdgId")) ? truthParticle->pdgId() : 0;
+
+		      ANA_MSG_WARNING("Unknown muon passing Medium selection with "
+            << "status = " << status
+            << ", type = " << type
+            << ", origin = " << origin
+            << ", PDG ID = " << pdgId
+            << ", fallback type = " << fallbackType
+            << ", fallback origin = " << fallbackOrigin
+            << ", fallback DR= " << fallbackDR
+            << ", pt = " << mu->pt());
+          
+          unknown_mu_origin_counter[origin]++;
+
+          if (truthParticle == nullptr) {
+            ANA_MSG_WARNING("Unknown muon is NOT truth matched!");
+          }
+        }
+      }
+    }
+  }  // Event loop
+
+  // Use standard c++ stdout for nice log
+  std::cout << std::endl << std::endl;
+
+  std::cout << "Electron summary:" << std::endl;
+  for (const auto&[classification, count] : el_counter)
+  {
+    std::cout << "  " << std::setw(25) << (classNames.at(classification) + ": ") << std::right << std::setw(6) << count << std::endl;
+  }
+  std::cout << std::endl;
+
+  std::cout << "Muon summary:" << std::endl;
+  for (const auto&[classification, count] : mu_counter)
+  {
+    std::cout << "  " << std::setw(25) << (classNames.at(classification) + ": ") << std::right << std::setw(6) << count << std::endl;
+  }
+  std::cout << std::endl;
+
+  std::cout << "Unknown electron origin:" << std::endl;
+  for (const auto&[type, map] : unknown_el_type_origin_counter)
+  {
+    for (const auto&[origin, count] : map)
+    {
+      std::cout << "  " << std::setw(2) << type << ", " << std::setw(2) << origin << ": " << std::setw(6) << count << std::endl;
+    }
+  }
+  std::cout << std::endl;
+
+  std::cout << "Unknown muon origin:" << std::endl;
+  for (const auto&[origin, count] : unknown_mu_origin_counter)
+  {
+    std::cout << "  " << std::setw(2) << origin << ": " << std::setw(6) << count << std::endl;
+  }
+  std::cout << std::endl;
+
+  std::cout << "Electron ambiguity map:" << std::endl;
+  std::cout << "           Classification | Amb. | Type | Origin | Count" << std::endl;
+  std::cout << "-----------------------------------------------------------" << std::endl;
+  for (const auto&[classification, map1] : ambiguity_map) {
+    for (const auto&[ambiguity, map2] : map1) {
+      for (const auto&[type, map3] : map2) {
+        for (const auto&[origin, count] : map3) {
+          std::cout << "  " << std::setw(23) << classNames.at(classification)
+            << " | " << std::setw(4) << ambiguity
+            << " | " << std::setw(4) << type
+            << " | " << std::setw(6) << origin
+            << " | " << std::setw(6) << count
+            << std::endl;
+        }
+      }
+    }
+  }
+  std::cout << std::endl;
+
+  // trigger finalization of all services and tools created by the Gaudi Application
+#ifndef XAOD_STANDALONE
+  ANA_CHECK(app->finalize());
+#endif
+
+  return 0;
+}
diff --git a/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h b/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h
new file mode 100644
index 0000000000000000000000000000000000000000..d59900c858713b96c3b1caf7cb9e2ad6dcc0947c
--- /dev/null
+++ b/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h
@@ -0,0 +1,66 @@
+/*
+  Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+*/
+
+#ifndef ITRUTHCLASSIFICATIONTOOL_H_
+#define ITRUTHCLASSIFICATIONTOOL_H_
+
+#include <iostream>
+#include <type_traits>
+
+#include <AsgAnalysisInterfaces/IClassificationTool.h>
+#include <xAODBase/IParticle.h>
+
+
+namespace Truth
+{
+
+/// \brief truth classification type enum
+enum class Type {
+  Unknown,
+  KnownUnknown,
+  IsoElectron,
+  ChargeFlipIsoElectron,
+  PromptMuon,
+  PromptPhotonConversion,
+  ElectronFromMuon,
+  TauDecay,
+  BHadronDecay,
+  CHadronDecay,
+  LightFlavorDecay,
+};
+
+} // namespace Truth
+
+inline std::ostream &operator<<(std::ostream &os, const Truth::Type &obj)
+{
+  os << static_cast<std::underlying_type<Truth::Type>::type>(obj);
+  return os;
+}
+
+
+
+/// \brief a tool interface to classify particles into multiple categories
+/// based on their truth information
+/// 
+/// The tools either assignes them an unsigned integer using the base
+/// \ref CP::IClassificationTool interface or a \ref Truth::Type enum
+/// representing possible truth classification classes.
+class ITruthClassificationTool : virtual public CP::IClassificationTool
+{
+  ASG_TOOL_INTERFACE(ITruthClassificationTool)
+
+public:
+  virtual ~ITruthClassificationTool() = default;
+
+  /// \brief classify and return unsigned int
+  /// re-declaration needed due to the same name used
+  virtual StatusCode classify(const xAOD::IParticle &particle,
+                              unsigned int &classification) const = 0;
+
+  /// \brief classify and return Truth::Type
+  virtual StatusCode classify(const xAOD::IParticle &particle,
+                              Truth::Type &classification) const = 0;
+};
+
+#endif // ITRUTHCLASSIFICATIONTOOL_H_
diff --git a/Projects/AnalysisBase/package_filters.txt b/Projects/AnalysisBase/package_filters.txt
index 4e2490364b072feb664693e811b60e62428d70ee..d9631a2366a70fb67da56a99174899ae0ee073cf 100644
--- a/Projects/AnalysisBase/package_filters.txt
+++ b/Projects/AnalysisBase/package_filters.txt
@@ -67,6 +67,7 @@
 + PhysicsAnalysis/AnalysisCommon/ParticleJetTools
 + PhysicsAnalysis/AnalysisCommon/PileupReweighting
 + PhysicsAnalysis/AnalysisCommon/ReweightUtils
++ PhysicsAnalysis/AnalysisCommon/TruthClassification
 + PhysicsAnalysis/D3PDTools/.*
 - PhysicsAnalysis/ElectronPhotonID/ElectronPhotonTagTools
 + PhysicsAnalysis/ElectronPhotonID/.*