diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 72869ece1142b2f3d30fa91edfe661db094f115d..4dd79e72e3ddc13d0b463252f11a3374ef8e3a72 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -128,7 +128,9 @@ set(SRCS
   qa/PadConfig.cxx
   qa/QaData.cxx
   ca/TrackingChain.cxx
-  ca/qa/CaQaBuilder.cxx
+  ca/qa/CaInputQa.cxx
+  ca/qa/CaOutputQa.cxx
+  ca/qa/CaQaBase.cxx
 )
 
 set(BUILD_INFO_CXX ${CMAKE_CURRENT_BINARY_DIR}/base/BuildInfo.cxx)
@@ -246,7 +248,9 @@ install(
     ca/TrackingChainConfig.h
     # NOTE: SZh 20.11.2023:
     #       The ca/qa directory depends on the online qa classes, so for now it has to be a part of the Algo library.
-    ca/qa/CaQaBuilder.h
+    ca/qa/CaInputQa.h
+    ca/qa/CaOutputQa.h
+    ca/qa/CaQaBase.h
   DESTINATION
     include/
 )
diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index b54a1dfc0ae9edaaee40de06157299343579a324..ea1acc6fcf6f64d993f19b6662c76667890909f1 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -29,15 +29,20 @@ using cbm::algo::ca::EDetectorID;
 using cbm::algo::ca::Framework;
 using cbm::algo::ca::HitTypes_t;
 using cbm::algo::ca::InitManager;
+using cbm::algo::ca::InputQa;
+using cbm::algo::ca::OutputQa;
 using cbm::algo::ca::Parameters;
-using cbm::algo::ca::QaBuilder;
 using cbm::algo::ca::Track;
 using cbm::algo::ca::constants::clrs::CL;   // clear text
 using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender) : fQaBuilder(QaBuilder(histoSender)) {}
+TrackingChain::TrackingChain(std::shared_ptr<HistogramSender> histoSender)
+  : fInputQa(InputQa(histoSender))
+  , fOutputQa(OutputQa(histoSender))
+{
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
@@ -60,10 +65,13 @@ void TrackingChain::Init()
   fCaFramework.Init(ca::Framework::TrackingMode::kMcbm);
   fCaFramework.ReceiveParameters(std::move(parameters));
 
-  // ------ Initialize QA builder
-  if (fQaBuilder.IsSenderDefined()) {
-    fQaBuilder.RegisterParameters(&fCaFramework.GetParameters());
-    fQaBuilder.Init();
+  // ------ Initialize QA modules
+  if (fOutputQa.IsSenderDefined()) {
+    fInputQa.RegisterParameters(&fCaFramework.GetParameters());
+    fInputQa.Init();
+
+    fOutputQa.RegisterParameters(&fCaFramework.GetParameters());
+    fOutputQa.Init();
   }
 }
 
@@ -149,11 +157,16 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
   output.monitorData = fCaMonitorData;
 
   // QA
-  if (fQaBuilder.IsSenderDefined()) {
-    fQaBuilder.RegisterInputData(&fCaFramework.GetInputData());
-    fQaBuilder.RegisterTracks(&output.tracks);
-    fQaBuilder.RegisterRecoHitIndices(&fCaFramework.fRecoHits);
-    fQaBuilder.Build();
+  if (fOutputQa.IsSenderDefined()) {
+    fInputQa.RegisterInputData(&fCaFramework.GetInputData());
+    fInputQa.RegisterTracks(&output.tracks);
+    fInputQa.RegisterRecoHitIndices(&fCaFramework.fRecoHits);
+    fInputQa.Exec();
+
+    fOutputQa.RegisterInputData(&fCaFramework.GetInputData());
+    fOutputQa.RegisterTracks(&output.tracks);
+    fOutputQa.RegisterRecoHitIndices(&fCaFramework.fRecoHits);
+    fOutputQa.Exec();
   }
 
 
@@ -170,6 +183,8 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
     out.open(std::string("./Debug_hits_") + ca::kDetName[DetID] + ".txt");
   }
 
+  int nSt = fCaFramework.GetParameters().GetNstationsActive();
+
   using Hit_t           = ca::HitTypes_t::at<DetID>;
   constexpr bool IsMvd  = (DetID == EDetectorID::Mvd);
   constexpr bool IsSts  = (DetID == EDetectorID::Sts);
@@ -201,6 +216,11 @@ void TrackingChain::ReadHits(PartitionedSpan<const ca::HitTypes_t::at<DetID>> hi
     int iStActive  = (iStLocal != -1) ? fCaFramework.GetParameters().GetStationIndexActive(iStLocal, DetID) : -1;
     size_t iOffset = hits.Offsets()[iPartition];
     if (iStActive < 0) {
+      continue;  // legit
+    }
+    if (iStActive >= nSt) {
+      L_(error) << "TrackingChain: found hit with wrong active station index above the upper limit: " << iStActive
+                << ", detector: " << ca::kDetName[DetID];
       continue;
     }
     double lastTime = -1e9;
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index 43cd2b4ec95c2d282dc3e845404eb6920021f6c8..ec6bf2644dec09827e7bf408e90ba3b803e6a521 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -11,7 +11,8 @@
 
 #include "CaDataManager.h"
 #include "CaFramework.h"
-#include "CaQaBuilder.h"
+#include "CaInputQa.h"
+#include "CaOutputQa.h"
 #include "CaTrack.h"
 #include "CaTrackingMonitor.h"
 #include "CaVector.h"
@@ -22,6 +23,7 @@
 #include "TrackingDefs.h"
 #include "sts/Hit.h"
 #include "tof/Hit.h"
+
 #include <memory>
 #include <vector>
 
@@ -100,7 +102,9 @@ namespace cbm::algo
     ca::TrackingMonitorData fCaMonitorData{};      ///< CA monitor data object
     ca::Framework fCaFramework{};                  ///< CA framework instance
     ca::DataManager fCaDataManager{};              ///< CA data manager
-    ca::QaBuilder fQaBuilder;                      ///< CA QA builder
+
+    ca::InputQa fInputQa;    ///< CA input QA builder
+    ca::OutputQa fOutputQa;  ///< CA output QA builder
 
     // ************************
     // **  Auxilary variables
diff --git a/algo/ca/qa/CaInputQa.cxx b/algo/ca/qa/CaInputQa.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ec3eba9e6c7f821c721d3ea26226bbbfcf87dba7
--- /dev/null
+++ b/algo/ca/qa/CaInputQa.cxx
@@ -0,0 +1,200 @@
+/* Copyright (C) 2023-2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaInputQa.cxx
+/// \date   01.03.2024
+/// \brief  An input QA module for CA tracking (source)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "CaInputQa.h"
+
+#include "../TrackingDefs.h"
+#include "CaConstants.h"
+#include "CaInputData.h"
+#include "CaParameters.h"
+
+#include <algorithm>
+#include <limits>
+
+#include <fmt/format.h>
+
+using cbm::algo::ca::Hit;
+using cbm::algo::ca::InputQa;
+using cbm::algo::ca::constants::math::Pi;
+
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void InputQa::Init()
+{
+  using cbm::algo::qa::CanvasConfig;
+  using cbm::algo::qa::Data;
+  using cbm::algo::qa::H1D;
+  using cbm::algo::qa::H2D;
+  using cbm::algo::qa::PadConfig;
+  using fmt::format;
+
+  if (!fpSender.get()) {
+    return;
+  }
+
+  if (!fpParameters) {
+    L_(error) << "ca::InputQa::Init(): parameters object is not initialized";
+    assert(false);
+  }
+
+  int nSt = fpParameters->GetNstationsActive();  // Number of active tracking stations
+
+  // ------ Init histograms
+
+  // Occupancy distributions
+  {
+    fvphHitOccupXY.resize(nSt);
+    fvphHitOccupZX.resize(nSt);
+    fvphHitOccupZY.resize(nSt);
+
+    // Station sizes in transverse plane
+    std::vector<double> vMinX(nSt);
+    std::vector<double> vMaxX(nSt);
+    std::vector<double> vMinY(nSt);
+    std::vector<double> vMaxY(nSt);
+
+    int nBinsXY = 200;
+    int nBinsZ  = 400;
+
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      const auto& station = fpParameters->GetStation(iSt);
+      vMaxX[iSt]          = station.GetXmax<double>();
+      vMinX[iSt]          = station.GetXmin<double>();
+      vMaxY[iSt]          = station.GetYmax<double>();
+      vMinY[iSt]          = station.GetYmin<double>();
+      double dy           = (vMaxY[iSt] - vMinY[iSt]) * kXYZMargin;
+      double dx           = (vMaxX[iSt] - vMinX[iSt]) * kXYZMargin;
+      vMaxX[iSt] += dx;
+      vMinX[iSt] -= dx;
+      vMaxY[iSt] += dy;
+      vMinY[iSt] -= dy;
+    }
+    // Station max
+    double xMinA = *std::min_element(vMinX.begin(), vMinX.end());
+    double xMaxA = *std::max_element(vMaxX.begin(), vMaxX.end());
+    double yMinA = *std::min_element(vMinY.begin(), vMinY.end());
+    double yMaxA = *std::max_element(vMaxY.begin(), vMaxY.end());
+    double zMinA = fpParameters->GetStation(0).GetZ<double>();
+    double zMaxA = fpParameters->GetStation(nSt - 1).GetZ<double>();
+    {
+      double dz = (zMaxA - zMinA) * kXYZMargin;
+      zMinA -= dz;
+      zMaxA += dz;
+    }
+    for (int iSt = 0; iSt < nSt; ++iSt) {
+      int iStGeo           = fpParameters->GetActiveToGeoMap()[iSt];
+      auto [detID, iStLoc] = fpParameters->GetGeoToLocalIdMap()[iStGeo];
+      for (auto hitSet : kHitSets) {
+        auto setNm = EHitSet::Input == hitSet ? "input" : "used";
+        auto setTl = EHitSet::Input == hitSet ? "Input" : "Used";
+        {
+          auto name = format("hit_{}_occup_xy_sta_{}", setNm, iSt);
+          auto titl = format("{} hit occupancy in XY plane for station {} ({}{});x [cm];y [cm]", setTl, iSt,
+                             kDetName[detID], iStLoc);
+          fvphHitOccupXY[iSt][hitSet] =
+            fQaData.MakeObj<H2D>(name, titl, nBinsXY, vMinX[iSt], vMaxX[iSt], nBinsXY, vMinY[iSt], vMaxY[iSt]);
+        }
+        {
+          auto name = format("hit_{}_occup_zx_sta_{}", setNm, iSt);
+          auto titl = format("{} hit occupancy in ZX plane for station {} ({}{});z [cm];x [cm]", setTl, iSt,
+                             kDetName[detID], iStLoc);
+          fvphHitOccupZX[iSt][hitSet] = fQaData.MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, xMinA, xMaxA);
+        }
+        {
+          auto name = format("hit_{}_occup_zy_sta_{}", setNm, iSt);
+          auto titl = format("{} hit occupancy in ZY plane for station {} ({}{});z [cm];y [cm]", setTl, iSt,
+                             kDetName[detID], iStLoc);
+          fvphHitOccupZY[iSt][hitSet] = fQaData.MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, yMinA, yMaxA);
+        }
+      }
+    }
+  }
+
+  // ----- Init canvases
+  {
+    // Hit occupancies
+    for (auto hitSet : kHitSets) {
+      auto setNm = EHitSet::Input == hitSet ? "input" : "used";
+      auto setTl = EHitSet::Input == hitSet ? "Input" : "Used";
+      {  // XY
+        auto name = format("ca_hit_{}_occupancy_xy", setNm);
+        auto titl = format("{} hit occupancy in different stations in XY plane", setNm);
+        auto canv = CanvasConfig(name, titl);
+        for (int iSt = 0; iSt < nSt; ++iSt) {
+          auto pad = PadConfig();
+          pad.RegisterHistogram(fvphHitOccupXY[iSt][hitSet], "colz");
+          canv.AddPadConfig(pad);
+        }
+        fQaData.AddCanvasConfig(canv);
+      }
+      {  // ZX and ZY
+        auto name = format("ca_hit_{}_occupancy_zx_zy", setNm);
+        auto titl = format("{} hit occupancy in different stations in ZX and ZY planes", setTl);
+        auto canv = CanvasConfig(name, titl);
+        {  // ZX
+          auto pad = PadConfig();
+          for (int iSt = 0; iSt < nSt; ++iSt) {
+            pad.RegisterHistogram(fvphHitOccupZX[iSt][hitSet], (iSt == 0 ? "colz" : "cols same"));
+          }
+          canv.AddPadConfig(pad);
+        }
+        {  // ZY
+          auto pad = PadConfig();
+          for (int iSt = 0; iSt < nSt; ++iSt) {
+            pad.RegisterHistogram(fvphHitOccupZY[iSt][hitSet], (iSt == 0 ? "colz" : "cols same"));
+          }
+          canv.AddPadConfig(pad);
+        }
+        fQaData.AddCanvasConfig(canv);
+      }
+    }
+  }
+  fQaData.Init(fpSender);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void InputQa::Exec()
+{
+  if (!fpSender.get()) {
+    return;
+  }
+
+  if (!CheckInit()) {
+    L_(fatal) << "ca::OutputQa: instance is not initialized";
+    assert(false);
+  }
+
+  // Fill input hit histograms
+  {
+    for (const auto& hit : fpInputData->GetHits()) {
+      FillHitDistributionsForHitSet(EHitSet::Input, hit);
+    }
+
+    for (int iH : (*fpvRecoHits)) {
+      FillHitDistributionsForHitSet(EHitSet::Used, fpInputData->GetHit(iH));
+    }
+  }
+
+  fQaData.Send(fpSender);
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void InputQa::FillHitDistributionsForHitSet(InputQa::EHitSet hitSet, const Hit& hit)
+{
+  int iSt  = hit.Station();
+  double x = hit.X();
+  double y = hit.Y();
+  double z = hit.Z();
+  fvphHitOccupXY[iSt][hitSet]->Fill(x, y);
+  fvphHitOccupZX[iSt][hitSet]->Fill(z, x);
+  fvphHitOccupZY[iSt][hitSet]->Fill(z, y);
+}
diff --git a/algo/ca/qa/CaInputQa.h b/algo/ca/qa/CaInputQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..baeedcb9761e688c582eee678074d3449a72a2b0
--- /dev/null
+++ b/algo/ca/qa/CaInputQa.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaInputQa.h
+/// \date   01.03.2024
+/// \brief  A QA module for CA tracking input data (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaEnumArray.h"
+#include "CaQaBase.h"
+
+namespace cbm::algo::ca
+{
+  /// \class cbm::algo::ca::qa::InputQa
+  /// \brief InputQa class for the CA tracking QA (header)
+  ///
+  class InputQa : public QaBase {
+    /// \brief Hit set entries
+    enum class EHitSet
+    {
+      Input,  ///< Input hits
+      Used,   ///< Hits used in tracks
+      kEND
+    };
+
+    /// \brief Definition of enum array over EHitSet entries
+    template<typename T>
+    using HitSetArray_t = EnumArray<EHitSet, T>;
+
+    /// \brief Array of EHitSet entries for iteration
+    static constexpr HitSetArray_t<EHitSet> kHitSets = {EHitSet::Input, EHitSet::Used};
+
+   public:
+    /// \brief Default destructor
+    /// \param pSender  Pointer to the histogram sender
+    InputQa(std::shared_ptr<HistogramSender> pSender) : QaBase(pSender, "CA/Input"){};
+
+    /// \brief Constructor from the configuration object
+    /// \param config  QA configuration object
+    InputQa() = default;
+
+    /// \brief Copy constructor
+    InputQa(const InputQa&) = delete;
+
+    /// \brief Move constructor
+    InputQa(InputQa&&) = delete;
+
+    /// \brief Destructor
+    ~InputQa() = default;
+
+    /// \brief Copy assignment operator
+    InputQa& operator=(const InputQa&) = delete;
+
+    /// \brief Move assignment operator
+    InputQa& operator=(InputQa&&) = delete;
+
+    /// \brief QA execution function
+    void Exec();
+
+    /// \brief Initializes the QA
+    void Init();
+
+   private:
+    /// \brief Fills hit distributions
+    /// \param  hitSet  Hit set enum entry
+    /// \param  hit     Reference to hit
+    void FillHitDistributionsForHitSet(EHitSet hitSet, const ca::Hit& hit);
+
+
+    static constexpr double kXYZMargin = 0.05;  ///< Margin for occupancy distributions in XY plane
+    static constexpr int knHitSets     = 2;     ///< Number of hit sets: input/used
+
+    // Hit distributions vs. station
+    using OccupHistContainer_t = std::vector<HitSetArray_t<qa::H2D*>>;
+    OccupHistContainer_t fvphHitOccupXY;  ///< hist: Hit occupancy in different stations in XY plane
+    OccupHistContainer_t fvphHitOccupZX;  ///< hist: Hit occupancy in different stations in ZX plane
+    OccupHistContainer_t fvphHitOccupZY;  ///< hist: Hit occupancy in different stations in ZY plane
+  };
+}  // namespace cbm::algo::ca
diff --git a/algo/ca/qa/CaQaBuilder.cxx b/algo/ca/qa/CaOutputQa.cxx
similarity index 85%
rename from algo/ca/qa/CaQaBuilder.cxx
rename to algo/ca/qa/CaOutputQa.cxx
index 87e224ef1801db69be7bfb5828e8feb54a1ea0d5..d9c8fd0cc349ecae784b9c0754cf68bad855ffa7 100644
--- a/algo/ca/qa/CaQaBuilder.cxx
+++ b/algo/ca/qa/CaOutputQa.cxx
@@ -2,12 +2,12 @@
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
-/// \file   CaQaQaBuilder.cxx
+/// \file   CaQaOutputQa.cxx
 /// \date   20.11.2023
 /// \brief  A QA module for CA tracking (implementation)
 /// \author S.Zharko <s.zharko@gsi.de>
 
-#include "CaQaBuilder.h"
+#include "CaOutputQa.h"
 
 #include "CaConstants.h"
 #include "CaInputData.h"
@@ -16,27 +16,26 @@
 
 #include <fmt/format.h>
 
-using cbm::algo::ca::QaBuilder;
+using cbm::algo::ca::OutputQa;
 using cbm::algo::ca::constants::math::Pi;
-using cbm::algo::qa::CanvasConfig;
-using cbm::algo::qa::Data;
-using cbm::algo::qa::H1D;
-using cbm::algo::qa::H2D;
-using cbm::algo::qa::PadConfig;
-
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void QaBuilder::Init()
+void OutputQa::Init()
 {
+  using cbm::algo::qa::CanvasConfig;
+  using cbm::algo::qa::Data;
+  using cbm::algo::qa::H1D;
+  using cbm::algo::qa::H2D;
+  using cbm::algo::qa::PadConfig;
   using fmt::format;
 
   if (!fpSender.get()) {
     return;
   }
 
-  // ---- Init histograms
-  // TODO: Provide definition from config
+
+  // ----- Histograms initialization
   constexpr int nParPads                              = 4;
   std::array<std::string, knTrkParPoints> vsPointName = {"first", "last"};
   for (int i = 0; i < knTrkParPoints; ++i) {
@@ -70,6 +69,7 @@ void QaBuilder::Init()
 
   // ---- Init canvases
   {
+
     // Track parameters at first/last track hit
 
     for (int i = 0; i < knTrkParPoints; ++i) {
@@ -124,27 +124,15 @@ void QaBuilder::Init()
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-void QaBuilder::Build()
+void OutputQa::Exec()
 {
   if (!fpSender.get()) {
     return;
   }
 
-  if (!fpParameters) {
-    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): parameters object is undefined";
-    return;
-  }
-  if (!fpInputData) {
-    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): input data object is undefined";
-    return;
-  }
-  if (!fpvTracks) {
-    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): tracks vector is undefined";
-    return;
-  }
-  if (!fpvRecoHits) {
-    LOG(error) << "cbm::algo::ca::QaBuilder::Build(): reco hit indices vector is undefined";
-    return;
+  if (!CheckInit()) {
+    L_(fatal) << "ca::OutputQa: instance is not initialized";
+    assert(false);
   }
 
   // Fill track histograms
diff --git a/algo/ca/qa/CaOutputQa.h b/algo/ca/qa/CaOutputQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..2da5c4fd047430922ee0119f4a9e0e17fa243cac
--- /dev/null
+++ b/algo/ca/qa/CaOutputQa.h
@@ -0,0 +1,61 @@
+/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaOutputQa.h
+/// \date   20.11.2023
+/// \brief  A QA module for CA tracking (header)
+/// \author S.Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaQaBase.h"
+
+namespace cbm::algo::ca
+{
+  /// \class cbm::algo::ca::qa::OutputQa
+  /// \brief OutputQa class for the CA tracking QA (header)
+  ///
+  class OutputQa : public QaBase {
+   public:
+    /// \brief Default destructor
+    /// \param pSender  Pointer to the histogram sender
+    OutputQa(std::shared_ptr<HistogramSender> pSender) : QaBase(pSender, "CA/Output"){};
+
+    /// \brief Constructor from the configuration object
+    /// \param config  QA configuration object
+    OutputQa() = default;
+
+    /// \brief Copy constructor
+    OutputQa(const OutputQa&) = delete;
+
+    /// \brief Move constructor
+    OutputQa(OutputQa&&) = delete;
+
+    /// \brief Destructor
+    ~OutputQa() = default;
+
+    /// \brief Copy assignment operator
+    OutputQa& operator=(const OutputQa&) = delete;
+
+    /// \brief Move assignment operator
+    OutputQa& operator=(OutputQa&&) = delete;
+
+    /// \brief QA execution function
+    void Exec();
+
+    /// \brief Initializes the QA
+    void Init();
+
+   private:
+    static constexpr int knTrkParPoints = 2;  ///< Number of track points to build par distributions
+
+    // Track distributions
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkTheta    = {{0}};    ///< hist: theta at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkPhi      = {{0}};    ///< hist: phi at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphTrkChi2Ndf  = {{0}};    ///< hist: chi2/NDF at first/last hit
+    std::array<qa::H1D*, knTrkParPoints> fvphHitSta      = {{0}};    ///< hist: station of first/last hit
+    std::array<qa::H2D*, knTrkParPoints> fvphTrkPhiTheta = {{0}};    ///< hist: theta vs. phi at first/last hit
+    qa::H1D* fphTrkNofHits                               = nullptr;  ///< hist: number of hits in track
+  };
+}  // namespace cbm::algo::ca
diff --git a/algo/ca/qa/CaQaBase.cxx b/algo/ca/qa/CaQaBase.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..da2810670e09ca26f360443956bf6f9181264138
--- /dev/null
+++ b/algo/ca/qa/CaQaBase.cxx
@@ -0,0 +1,36 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CaQaBase.h
+/// \date   01.03.2024
+/// \brief  Base class for tracking QA (source)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "CaQaBase.h"
+
+using cbm::algo::ca::QaBase;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool QaBase::CheckInit() const
+{
+  bool res = true;
+  if (!fpParameters) {
+    L_(error) << "cbm::algo::ca::OutputQa::Build(): parameters object is undefined";
+    res = false;
+  }
+  if (!fpInputData) {
+    L_(error) << "cbm::algo::ca::OutputQa::Build(): input data object is undefined";
+    res = false;
+  }
+  if (!fpvTracks) {
+    L_(error) << "cbm::algo::ca::OutputQa::Build(): track vector is undefined";
+    res = false;
+  }
+  if (!fpvRecoHits) {
+    L_(error) << "cbm::algo::ca::OutputQa::Build(): used hit index vector is undefined";
+    res = false;
+  }
+  return res;
+}
diff --git a/algo/ca/qa/CaQaBase.h b/algo/ca/qa/CaQaBase.h
new file mode 100644
index 0000000000000000000000000000000000000000..3a85a06f6a3523f56ba9b9dd1a97ac66bc3c1a74
--- /dev/null
+++ b/algo/ca/qa/CaQaBase.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaBase.h
+/// \date   29.02.2024
+/// \brief  Base class for tracking QA (header)
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CaHit.h"  // for HitIndex_t
+#include "CaTimesliceHeader.h"
+#include "CaVector.h"
+#include "QaData.h"  // QA data
+
+namespace cbm::algo
+{
+  namespace qa
+  {
+    class H1D;
+    class H2D;
+  }  // namespace qa
+  namespace ca
+  {
+    template<typename DataT>
+    class Parameters;
+    class InputData;
+    class Track;
+  }  // namespace ca
+}  // namespace cbm::algo
+
+namespace cbm::algo::ca
+{
+  /// \class cbm::algo::ca::qa::QaBase
+  /// \brief Base class for CA tracking QA
+  ///
+  /// The class contains set of pointers to data structures, which are needed to build the histograms.
+  class QaBase {
+   public:
+    /// \brief Default destructor
+    /// \param pSender  Pointer to the histogram sender
+    /// \param dirname  Directory name in the output file
+    QaBase(std::shared_ptr<HistogramSender> pSender, const std::string& dirname) : fQaData(dirname), fpSender(pSender)
+    {
+    }
+
+    /// \brief Constructor from the configuration object
+    /// \param config  QA configuration object
+    QaBase() = default;
+
+    /// \brief Copy constructor
+    QaBase(const QaBase&) = delete;
+
+    /// \brief Move constructor
+    QaBase(QaBase&&) = delete;
+
+    /// \brief Destructor
+    ~QaBase() = default;
+
+    /// \brief Copy assignment operator
+    QaBase& operator=(const QaBase&) = delete;
+
+    /// \brief Move assignment operator
+    QaBase& operator=(QaBase&&) = delete;
+
+    /// \brief Check initialization
+    /// \return true   All variables are initialized
+    /// \return false  Some of are not initialized
+    bool CheckInit() const;
+
+    /// \brief Checks, if the histogram sender is defined
+    bool IsSenderDefined() const { return static_cast<bool>(fpSender.get()); }
+
+    /// \brief Registers tracking input data object
+    /// \note  Call per TS
+    void RegisterInputData(const InputData* pInputData) { fpInputData = pInputData; }
+
+    /// \brief Registers track vector
+    /// \note  Call per TS
+    void RegisterTracks(const Vector<Track>* pvTracks) { fpvTracks = pvTracks; }
+
+    /// \brief Registers reco hits indices vector
+    /// \note  Call per TS
+    void RegisterRecoHitIndices(const Vector<HitIndex_t>* pvRecoHits) { fpvRecoHits = pvRecoHits; }
+
+    /// \brief Registers tracking parameters object
+    /// \note  Call per run
+    void RegisterParameters(const Parameters<fvec>* pParameters) { fpParameters = pParameters; }
+
+   protected:
+    qa::Data fQaData;  ///< QA data
+
+    std::shared_ptr<HistogramSender> fpSender = nullptr;  ///< Histogram sender
+    const Parameters<fvec>* fpParameters      = nullptr;  ///< Pointer to tracking parameters
+    const InputData* fpInputData              = nullptr;  ///< Pointer to input data
+    const Vector<Track>* fpvTracks            = nullptr;  ///< Pointer to tracks vector
+    const Vector<HitIndex_t>* fpvRecoHits     = nullptr;  ///< Pointer to reco hit indices
+  };
+}  // namespace cbm::algo::ca
diff --git a/algo/ca/qa/CaQaBuilder.h b/algo/ca/qa/CaQaBuilder.h
deleted file mode 100644
index 5fa1f2321ec7a1e4a3809f1d2f022d879deed173..0000000000000000000000000000000000000000
--- a/algo/ca/qa/CaQaBuilder.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
-   SPDX-License-Identifier: GPL-3.0-only
-   Authors: Sergei Zharko [committer] */
-
-/// \file   CaQaQaBuilder.h
-/// \date   20.11.2023
-/// \brief  A QA module for CA tracking (header)
-/// \author S.Zharko <s.zharko@gsi.de>
-
-#pragma once
-
-#include "CaHit.h"  // for HitIndex_t
-#include "CaTimesliceHeader.h"
-#include "CaVector.h"
-#include "QaData.h"  // QA data
-
-namespace cbm::algo
-{
-  namespace qa
-  {
-    class H1D;
-    class H2D;
-  }  // namespace qa
-}  // namespace cbm::algo
-
-namespace cbm::algo::ca
-{
-  template<typename DataT>
-  class Parameters;
-  class InputData;
-  class Track;
-
-  /// \class cbm::algo::ca::qa::QaBuilder
-  /// \brief QaBuilder class for the CA tracking QA (header)
-  ///
-  class QaBuilder {
-   public:
-    using TrackV_t    = ca::Vector<ca::Track>;
-    using HitIndexV_t = ca::Vector<std::vector<std::pair<uint32_t, uint32_t>>>;
-
-    /// \brief Default destructor
-    /// \param pSender  Pointer to the histogram sender
-    QaBuilder(std::shared_ptr<HistogramSender> pSender) : fpSender(pSender){};
-
-    /// \brief Constructor from the configuration object
-    /// \param config  QA configuration object
-    QaBuilder() = default;
-
-    /// \brief Copy constructor
-    QaBuilder(const QaBuilder&) = delete;
-
-    /// \brief Move constructor
-    QaBuilder(QaBuilder&&) = delete;
-
-    /// \brief Destructor
-    ~QaBuilder() = default;
-
-    /// \brief Copy assignment operator
-    QaBuilder& operator=(const QaBuilder&) = delete;
-
-    /// \brief Move assignment operator
-    QaBuilder& operator=(QaBuilder&&) = delete;
-
-    /// \brief QA execution function
-    void Build();
-
-    /// \brief Initializes the QA
-    void Init();
-
-    /// \brief Checks, if the histogram sender is defined
-    bool IsSenderDefined() const { return static_cast<bool>(fpSender.get()); }
-
-    /// \brief Registers tracking input data
-    /// \note  Call per TS
-    void RegisterInputData(const InputData* pInputData) { fpInputData = pInputData; }
-
-    /// \brief Registers track vector
-    /// \note  Call per TS
-    void RegisterTracks(const Vector<Track>* pvTracks) { fpvTracks = pvTracks; }
-
-    /// \brief Registers reco hits indices
-    /// \note  Call per TS
-    void RegisterRecoHitIndices(const Vector<HitIndex_t>* pvRecoHits) { fpvRecoHits = pvRecoHits; }
-
-    /// \brief Registers tracking parameters object
-    /// \note  Call per run
-    void RegisterParameters(const Parameters<fvec>* pParameters) { fpParameters = pParameters; }
-
-   private:
-    qa::Data fQaData{"CaQa"};  ///< QA data
-
-    std::shared_ptr<HistogramSender> fpSender = nullptr;  ///< Histogram sender
-    const Parameters<fvec>* fpParameters      = nullptr;  ///< Pointer to tracking parameters
-    const InputData* fpInputData              = nullptr;  ///< Pointer to input data
-    const Vector<Track>* fpvTracks            = nullptr;  ///< Pointer to tracks vector
-    const Vector<HitIndex_t>* fpvRecoHits     = nullptr;  ///< Pointer to reco hit indices
-
-    // ----- List of the histograms ------------------------------------------------------------------------------------
-    static constexpr int knTrkParPoints = 2;  ///< Number of track points to build par distributions
-
-    std::array<qa::H1D*, knTrkParPoints> fvphTrkTheta    = {{0}};    ///< hist: theta at first/last hit
-    std::array<qa::H1D*, knTrkParPoints> fvphTrkPhi      = {{0}};    ///< hist: phi at first/last hit
-    std::array<qa::H1D*, knTrkParPoints> fvphTrkChi2Ndf  = {{0}};    ///< hist: chi2/NDF at first/last hit
-    std::array<qa::H1D*, knTrkParPoints> fvphHitSta      = {{0}};    ///< hist: station of first/last hit
-    std::array<qa::H2D*, knTrkParPoints> fvphTrkPhiTheta = {{0}};    ///< hist: theta vs. phi at first/last hit
-    qa::H1D* fphTrkNofHits                               = nullptr;  ///< hist: number of hits in track
-  };
-}  // namespace cbm::algo::ca::qa
diff --git a/services/histserv/tester/Application.cxx b/services/histserv/tester/Application.cxx
index f6a675dc7261ad4600ae289241c857ef15d1c25a..99e9c3deeaf311d40817dcca719ffa7d88ad8b4d 100644
--- a/services/histserv/tester/Application.cxx
+++ b/services/histserv/tester/Application.cxx
@@ -45,7 +45,7 @@ namespace cbm::services::histserv_tester
     /// FIXME: Initialize communication channels of SOMETHING_To_Replace_FairMQ
     /// FIXME: Link channel to method in order to process received messages
     // fZmqSocket.set(zmq::sockopt::rcvhwm, int(hwm));  // FIXME: need for HWM? (NOTE: SZh 29.02.2024: if needed, move it
-    // do it in the HistogramSender)
+    // in the HistogramSender)
   }
   // -------------------------------------------------------------------------------------------------------------------