diff --git a/LumiBlock/LumiBlockComps/CMakeLists.txt b/LumiBlock/LumiBlockComps/CMakeLists.txt
index 7d7d26c9d8f1ae558437752105bae103624db2eb..3abc461ccbbcae91766cd09a8568aab396acfcda 100644
--- a/LumiBlock/LumiBlockComps/CMakeLists.txt
+++ b/LumiBlock/LumiBlockComps/CMakeLists.txt
@@ -33,6 +33,7 @@ else()
       GaudiKernel
       LumiBlock/LumiCalc
       PRIVATE
+      Control/CxxUtils
       DataQuality/GoodRunsLists
       Database/AthenaPOOL/AthenaPoolUtilities
       Database/AthenaPOOL/DBDataModel
@@ -87,3 +88,7 @@ atlas_add_test( LBDurationCondAlg_test
    SOURCES test/LBDurationCondAlg_test.cxx
    LINK_LIBRARIES GaudiKernel LumiBlockCompsLib )
 
+atlas_add_test( LuminosityCondAlg_test
+   SOURCES test/LuminosityCondAlg_test.cxx
+   LINK_LIBRARIES GaudiKernel LumiBlockCompsLib )
+
diff --git a/LumiBlock/LumiBlockComps/python/LuminosityCondAlgDefault.py b/LumiBlock/LumiBlockComps/python/LuminosityCondAlgDefault.py
new file mode 100644
index 0000000000000000000000000000000000000000..835b4794fcbf4cc8269b120369b15516e064d5ca
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/python/LuminosityCondAlgDefault.py
@@ -0,0 +1,189 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+#
+# File: LumiBlockComps/python/LuminosityCondAlgDefault.py
+# Created: May 2019, sss, from existing LuminosityToolDefault.
+# Purpose: Configure LuminosityCondAlg.
+#
+
+# xxx lumicondalg to handle no-input case.
+
+from AthenaCommon.Logging import logging
+from AthenaCommon.AlgSequence import AthSequencer
+
+
+def configureLuminosityCondAlgMC (name):
+    return { 'LuminosityFolderInputKey' : '',
+             'OnlineLumiCalibrationInputKey' : '',
+             'BunchLumisInputKey' : '',
+             'BunchGroupInputKey' : '',
+             'FillParamsInputKey' : '' }
+             
+
+
+# Configuration for offline default tool used in Run2
+def configureLuminosityCondAlgRun2 (name):
+    mlog = logging.getLogger(name)
+
+    kwargs = {}
+
+    # Set up DB configuration
+    from IOVDbSvc.CondDB import conddb
+    from RecExConfig.RecFlags import rec
+
+    # Check if this is express stream or bulk
+    if rec.doExpressProcessing():
+        lumiFolder  = "/TRIGGER/LUMI/OnlPrefLumi"
+        if not conddb.folderRequested( lumiFolder ):
+            conddb.addFolder('TRIGGER_ONL', lumiFolder,
+                             className = 'CondAttrListCollection')
+
+    else:
+        lumiFolder = "/TRIGGER/OFLLUMI/OflPrefLumi"
+        if not conddb.folderRequested( lumiFolder ):
+            conddb.addFolder('TRIGGER_OFL', lumiFolder,
+                             className = 'CondAttrListCollection')
+
+    mlog.info("configureLuminosityCondAlgRun2 requested %s", lumiFolder)
+    kwargs['LuminosityFolderInputKey'] = lumiFolder
+
+    mlog.info("Created Run2 %s using folder %s" % (name, lumiFolder))
+
+    # Need the calibration tool just to get the proper MuToLumi value
+    from CoolLumiUtilities.OnlineLumiCalibrationCondAlgDefault \
+        import OnlineLumiCalibrationCondAlgDefault
+    calibAlg = OnlineLumiCalibrationCondAlgDefault()
+    kwargs['OnlineLumiCalibrationInputKey'] = calibAlg.LumiCalibOutputKey
+    
+    # Other folder names should be blank.
+    kwargs['BunchLumisInputKey'] = ''
+    kwargs['BunchGroupInputKey'] = ''
+    kwargs['FillParamsInputKey'] = ''
+
+    return kwargs
+
+
+# Configuration for offline default tool used in Run1
+def configureLuminosityCondAlgRun1 (name):
+    mlog = logging.getLogger(name)
+
+    kwargs = {}
+       
+    # Now configure DB based on the environment
+    from IOVDbSvc.CondDB import conddb
+    from RecExConfig.RecFlags import rec
+
+    # Check if this is express stream or bulk
+    if rec.doExpressProcessing():
+        lumiFolder  = "/TRIGGER/LUMI/LBLESTONL"
+        if not conddb.folderRequested( lumiFolder ):
+            conddb.addFolder('TRIGGER_ONL', lumiFolder,
+                             className = 'CondAttrListCollection')
+            mlog.info("configureLuminosityCondAlgRun1 requested %s", lumiFolder)
+
+    else:
+        lumiFolder = "/TRIGGER/OFLLUMI/LBLESTOFL"
+        if not conddb.folderRequested( lumiFolder ):
+            conddb.addFolder('TRIGGER_OFL', lumiFolder,
+                             className = 'CondAttrListCollection')
+            mlog.info("configureLuminosityCondAlgRun1 requested %s", lumiFolder)
+
+    kwargs['LuminosityFolderInputKey'] = lumiFolder
+
+    # Configure input conditions data.
+    from CoolLumiUtilities.FillParamsCondAlgDefault \
+        import FillParamsCondAlgDefault
+    fillParamsAlg = FillParamsCondAlgDefault()
+    kwargs['FillParamsInputKey'] = fillParamsAlg.FillParamsOutputKey
+
+    from CoolLumiUtilities.BunchLumisCondAlgDefault \
+        import BunchLumisCondAlgDefault
+    bunchLumisAlg = BunchLumisCondAlgDefault()
+    kwargs['BunchLumisInputKey'] = bunchLumisAlg.BunchLumisOutputKey
+
+    from CoolLumiUtilities.BunchGroupCondAlgDefault \
+        import BunchGroupCondAlgDefault
+    bunchGroupAlg = BunchGroupCondAlgDefault()
+    kwargs['BunchGroupInputKey'] = bunchGroupAlg.BunchGroupOutputKey
+
+    from CoolLumiUtilities.OnlineLumiCalibrationCondAlgDefault \
+        import OnlineLumiCalibrationCondAlgDefault
+    calibAlg = OnlineLumiCalibrationCondAlgDefault()
+    kwargs['OnlineLumiCalibrationInputKey'] = calibAlg.LumiCalibOutputKey
+
+    return kwargs
+
+
+def LuminosityCondAlgDefault (name = 'LuminosityCondAlg', kwargs = None):
+    mlog = logging.getLogger(name)
+    condSeq = AthSequencer ('AthCondSeq')
+
+    if hasattr (condSeq, name):
+        return getattr (condSeq, name)
+
+    from IOVDbSvc.CondDB import conddb
+    if kwargs != None:
+        pass
+
+    elif conddb.isMC:
+         mlog.info("LuminosityCondAlgDefault called for MC!")
+         kwargs = configureLuminosityCondAlgMC (name)
+
+    elif conddb.dbdata == "COMP200":
+        kwargs = configureLuminosityCondAlgRun1 (name)
+
+    elif conddb.dbdata == "CONDBR2":
+        kwargs = configureLuminosityCondAlgRun2 (name)
+
+    else:
+        mlog.warning("LuminosityToolDefault can't resolve conddb.dbdata = %s, assume Run2!" % conddb.dbdata)
+        kwargs = configureLuminosityCondAlgRun2 (name)
+    
+    from LumiBlockComps.LumiBlockCompsConf import \
+        LuminosityCondAlg
+
+    alg = LuminosityCondAlg (name,
+                             LuminosityOutputKey = 'LuminosityCondData',
+                             **kwargs)
+    condSeq += alg
+
+    return alg
+
+
+def LuminosityCondAlgOnline (name = 'LuminosityCondAlg'):
+    mlog = logging.getLogger(name)
+
+    kwargs = {}
+
+    # Keep values for invalid data
+    kwargs['SkipInvalid'] = False
+        
+    from IOVDbSvc.CondDB import conddb
+    if conddb.dbdata == "COMP200": # Run1
+        folder  = "/TRIGGER/LUMI/LBLESTONL"
+        conddb.addFolder('TRIGGER_ONL', folder,
+                         className = 'CondAttrListCollection')
+      
+    else: #  Run 2
+        if conddb.dbdata != "CONDBR2":
+            mlog.warning("LuminosityToolOnline can't resolve conddb.dbdata = %s, assume Run2!" % conddb.dbdata)
+            mlog.info("Using Run 2 configuration")
+
+        folder  = "/TRIGGER/LUMI/HLTPrefLumi"
+        conddb.addFolder('TRIGGER_ONL', folder,
+                         className = 'CondAttrListCollection')
+
+    kwargs['LuminosityFolderInputKey'] = folder
+    mlog.info("Created online %s using folder %s" % (name, folder))
+
+    # Need the calibration tool just to get the proper MuToLumi value
+    from CoolLumiUtilities.OnlineLumiCalibrationCondAlgDefault \
+        import OnlineLumiCalibrationCondAlgDefault
+    calibAlg = OnlineLumiCalibrationCondAlgDefault()
+    kwargs['OnlineLumiCalibrationInputKey'] = calibAlg.LumiCalibOutputKey
+    
+    # Other folder names should be blank.
+    kwargs['BunchLumisInputKey'] = ''
+    kwargs['BunchGroupInputKey'] = ''
+    kwargs['FillParamsInputKey'] = ''
+
+    return kwargs
diff --git a/LumiBlock/LumiBlockComps/share/LBDurationCondAlg_test.ref b/LumiBlock/LumiBlockComps/share/LBDurationCondAlg_test.ref
index 8e8489e6c24d7c6b5114b05eb1f3a24fd7792f97..d5a7fe27dee28d73c397ec3267e073983ccaa32e 100644
--- a/LumiBlock/LumiBlockComps/share/LBDurationCondAlg_test.ref
+++ b/LumiBlock/LumiBlockComps/share/LBDurationCondAlg_test.ref
@@ -1,4 +1,4 @@
-LumiBlockComps/LBDurationCondAlg
+LumiBlockComps/LBDurationCondAlg_test
 
 
 Initializing Gaudi ApplicationMgr using job opts ../share/LBDurationCondAlg_test.txt
@@ -10,10 +10,10 @@ JobOptionsSvc        INFO Job options successfully read in from ../share/LBDurat
 ApplicationMgr    SUCCESS 
 ====================================================================================================================================
                                                    Welcome to ApplicationMgr (GaudiCoreSvc v27r1p99)
-                                          running on karma on Fri May 17 13:28:07 2019
+                                          running on karma on Mon May 20 10:52:18 2019
 ====================================================================================================================================
 ApplicationMgr       INFO Application Manager Configured successfully
-ClassIDSvc           INFO  getRegistryEntries: read 2851 CLIDRegistry entries for module ALL
+ClassIDSvc           INFO  getRegistryEntries: read 2861 CLIDRegistry entries for module ALL
 EventLoopMgr      WARNING Unable to locate service "EventSelector" 
 EventLoopMgr      WARNING No events will be processed from external input.
 HistogramPersis...WARNING Histograms saving not required.
diff --git a/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.ref b/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..7508f297f3fbb0407431dd3fe670103d80bc58f5
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.ref
@@ -0,0 +1,36 @@
+LumiBlockComps/LuminosityCondAlg_test
+
+
+Initializing Gaudi ApplicationMgr using job opts ../share/LuminosityCondAlg_test.txt
+JobOptionsSvc        INFO # =======> /home/sss/nobackup/atlas/build/../tests/../share/LuminosityCondAlg_test.txt
+JobOptionsSvc        INFO # (1,1): ApplicationMgr.ExtSvc = ["StoreGateSvc/ConditionStore"]
+JobOptionsSvc        INFO # (3,1): LuminosityCondAlg.LuminosityFolderInputKey = "testLumi"
+JobOptionsSvc        INFO # (4,1): LuminosityCondAlg.OnlineLumiCalibrationInputKey = "testCalib"
+JobOptionsSvc        INFO # (5,1): LuminosityCondAlg.LuminosityOutputKey = "LuminosityCondData"
+JobOptionsSvc        INFO # (7,1): LuminosityCondAlgRun1.LuminosityFolderInputKey = "testLumiRun1"
+JobOptionsSvc        INFO # (8,1): LuminosityCondAlgRun1.OnlineLumiCalibrationInputKey = "testCalibRun1"
+JobOptionsSvc        INFO # (9,1): LuminosityCondAlgRun1.BunchLumisInputKey = "testBunchLumisRun1"
+JobOptionsSvc        INFO # (10,1): LuminosityCondAlgRun1.BunchGroupInputKey = "testBunchGroupRun1"
+JobOptionsSvc        INFO # (11,1): LuminosityCondAlgRun1.FillParamsInputKey = "testFillParamsRun1"
+JobOptionsSvc        INFO # (12,1): LuminosityCondAlgRun1.LuminosityOutputKey = "LuminosityCondDataRun1"
+JobOptionsSvc        INFO # (14,1): LuminosityCondAlgMC.LuminosityFolderInputKey = ""
+JobOptionsSvc        INFO # (15,1): LuminosityCondAlgMC.OnlineLumiCalibrationInputKey = ""
+JobOptionsSvc        INFO # (16,1): LuminosityCondAlgMC.LuminosityOutputKey = "LuminosityCondDataMC"
+JobOptionsSvc        INFO Job options successfully read in from ../share/LuminosityCondAlg_test.txt
+ApplicationMgr    SUCCESS 
+====================================================================================================================================
+                                                   Welcome to ApplicationMgr (GaudiCoreSvc v27r1p99)
+                                          running on karma on Wed May 22 16:49:43 2019
+====================================================================================================================================
+ApplicationMgr       INFO Application Manager Configured successfully
+ClassIDSvc           INFO  getRegistryEntries: read 2871 CLIDRegistry entries for module ALL
+EventLoopMgr      WARNING Unable to locate service "EventSelector" 
+EventLoopMgr      WARNING No events will be processed from external input.
+HistogramPersis...WARNING Histograms saving not required.
+ApplicationMgr       INFO Application Manager Initialized successfully
+ApplicationMgr Ready
+test1
+ClassIDSvc           INFO  getRegistryEntries: read 372 CLIDRegistry entries for module ALL
+test2
+ClassIDSvc           INFO  getRegistryEntries: read 1932 CLIDRegistry entries for module ALL
+test3
diff --git a/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.txt b/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fc33742ab8ec148e5375f9a53b68e5e8de0f2a95
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/share/LuminosityCondAlg_test.txt
@@ -0,0 +1,16 @@
+ApplicationMgr.ExtSvc = {"StoreGateSvc/ConditionStore"};
+
+LuminosityCondAlg.LuminosityFolderInputKey = "testLumi";
+LuminosityCondAlg.OnlineLumiCalibrationInputKey = "testCalib";
+LuminosityCondAlg.LuminosityOutputKey = "LuminosityCondData";
+
+LuminosityCondAlgRun1.LuminosityFolderInputKey = "testLumiRun1";
+LuminosityCondAlgRun1.OnlineLumiCalibrationInputKey = "testCalibRun1";
+LuminosityCondAlgRun1.BunchLumisInputKey = "testBunchLumisRun1";
+LuminosityCondAlgRun1.BunchGroupInputKey = "testBunchGroupRun1";
+LuminosityCondAlgRun1.FillParamsInputKey = "testFillParamsRun1";
+LuminosityCondAlgRun1.LuminosityOutputKey = "LuminosityCondDataRun1";
+
+LuminosityCondAlgMC.LuminosityFolderInputKey = "";
+LuminosityCondAlgMC.OnlineLumiCalibrationInputKey = "";
+LuminosityCondAlgMC.LuminosityOutputKey = "LuminosityCondDataMC";
diff --git a/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.cxx b/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..7a1868e648126fabc288f7a050d94b9d2071cdb1
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.cxx
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file LumiBlockComps/src/LuminosityCondAlg.cxx
+ * @author scott snyder <snyder@bnl.gov>, from existing LuminosityTool.
+ * @date May, 2019
+ * @brief Conditions algorithm for luminosity data.
+ */
+
+
+#include "LuminosityCondAlg.h"
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "CoolKernel/IObject.h"
+#include "CxxUtils/get_unaligned.h"
+#include <sstream>
+
+
+/**
+ * @brief Gaudi initialize method.
+ */
+StatusCode
+LuminosityCondAlg::initialize()
+{
+  ATH_CHECK( m_luminosityOutputKey.initialize() );
+
+  // May be empty if configured for MC.
+  ATH_CHECK( m_luminosityFolderInputKey.initialize(SG::AllowEmpty) );
+  ATH_CHECK( m_onlineLumiCalibrationInputKey.initialize(SG::AllowEmpty) );
+
+  // Only used for run1.
+  ATH_CHECK( m_bunchLumisInputKey.initialize(SG::AllowEmpty) );
+  ATH_CHECK( m_bunchGroupInputKey.initialize(SG::AllowEmpty) );
+  ATH_CHECK( m_fillParamsInputKey.initialize(SG::AllowEmpty) );
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Algorithm execute method.
+ * @param ctx Event Context.
+ */
+StatusCode
+LuminosityCondAlg::execute (const EventContext& ctx) const
+{
+  auto lumi = std::make_unique<LuminosityCondData>();
+  EventIDRange range;
+
+  if (m_luminosityFolderInputKey.empty()) {
+    // MC case.
+    const EventIDBase::number_type UNDEFNUM = EventIDBase::UNDEFNUM;
+    const EventIDBase::event_number_t UNDEFEVT = EventIDBase::UNDEFEVT;
+    EventIDRange fullrange (EventIDBase (0, UNDEFEVT, UNDEFNUM, 0, 0),
+                            EventIDBase (UNDEFNUM-1, UNDEFEVT, UNDEFNUM, 0, 0));
+    range = fullrange;
+  }
+  else {
+    SG::ReadCondHandle<CondAttrListCollection> luminosityFolder
+      (m_luminosityFolderInputKey, ctx);
+    ATH_CHECK( luminosityFolder.range (range) );
+
+    unsigned int preferredChannel;
+    unsigned int calibChannel;
+    const coral::Blob* bunchInstLumiBlob = nullptr;
+    ATH_CHECK( updateAvgLumi (**luminosityFolder,
+                              *lumi,
+                              preferredChannel,
+                              calibChannel,
+                              bunchInstLumiBlob) );
+
+    ATH_CHECK( updatePerBunchLumi (ctx,
+                                   bunchInstLumiBlob,
+                                   preferredChannel,
+                                   calibChannel,
+                                   range,
+                                   *lumi) );
+  }
+
+  SG::WriteCondHandle<LuminosityCondData> luminosityCondData
+    (m_luminosityOutputKey, ctx);
+  ATH_CHECK( luminosityCondData.record (range, std::move (lumi)) );
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Unpack luminosity data from the attribute list.
+ * @param lumiData Input luminosity data.
+ * @param lumi Output luminosity data being filled.
+ * @param preferredChannel[out] Preferred luminosity channel to use.
+ * @param calibChannel[out] Calibration luminosity channel to use.
+ * @param bunchInstLumiBlob[out] Packed per-bunch luminosity data.
+ *                               Set to null for Run 1.
+ *
+ * Unpacks luminosity data from the attribute list.
+ * Fills in the average luminosity fields in @c lumi,
+ * and determines the luminosity channels to use.
+ * For Run 2 and later, returns the packed luminosity data.
+ */
+StatusCode
+LuminosityCondAlg::updateAvgLumi (const CondAttrListCollection& lumiData,
+                                  LuminosityCondData& lumi,
+                                  unsigned int& preferredChannel,
+                                  unsigned int& calibChannel,
+                                  const coral::Blob*& bunchInstLumiBlob) const
+                                  
+{
+  preferredChannel = 0;
+  calibChannel = 0;
+  bunchInstLumiBlob = nullptr;
+
+  const coral::AttributeList& attrList = lumiData.attributeList (m_lumiChannel);
+  if (attrList["Valid"].isNull()) {
+    ATH_MSG_ERROR ("Can't find luminosity information for channel " << m_lumiChannel);
+    return StatusCode::FAILURE;
+  }
+
+  if (msgLvl (MSG::DEBUG)) {
+    std::ostringstream attrStr1;
+    attrList.toOutputStream( attrStr1 );
+    ATH_MSG_DEBUG( "ChanNum " << m_lumiChannel << " Attribute list "
+                   << attrStr1.str() );
+  }
+
+  // Check data availability
+  if (attrList["LBAvInstLumi"].isNull() || attrList["LBAvEvtsPerBX"].isNull()) {
+    ATH_MSG_ERROR( " NULL Luminosity information in database " );
+    return StatusCode::FAILURE;
+  }
+
+  // Check validity (don't bother continuing if invalid)
+  uint32_t valid = attrList["Valid"].data<cool::UInt32>();
+  lumi.setLbAverageValid (valid);
+  if (valid & 0x01) {
+    if (m_skipInvalid) {
+      ATH_MSG_WARNING( " Invalid LB Average luminosity ... set lumi to 0" );
+      return StatusCode::SUCCESS;
+    } else {
+      ATH_MSG_WARNING( " Invalid LB Average luminosity ... continuing because skipInvalid == FALSE" );
+    }
+  }
+
+  // Get preferred channel (needed for per-BCID calculation)
+  if (m_lumiChannel == 0u) {
+
+    // Check if we have a payload for this (Run2 only)      
+    bool hasAlgorithmID = false;
+    for (coral::AttributeList::const_iterator attr = attrList.begin();
+	 attr != attrList.end(); ++attr) {
+      if (attr->specification().name() == "AlgorithmID") {
+	hasAlgorithmID = true;
+	break;
+      }
+    }
+
+    if (hasAlgorithmID) {
+      // In Run2, channel 0 should be good.  Leave as is
+      preferredChannel = m_lumiChannel;
+      calibChannel = attrList["AlgorithmID"].data<cool::UInt32>();
+
+    } else {
+      // In Run1, we need to recalculate from the actual channel number
+      preferredChannel = (valid >> 22);
+      calibChannel = preferredChannel;
+    }
+
+  } else {
+    preferredChannel = m_lumiChannel;
+    calibChannel = m_lumiChannel;
+  }
+
+  float LBAvInstLumi = attrList["LBAvInstLumi"].data<cool::Float>();   // Lumi
+  float LBAvEvtsPerBX = attrList["LBAvEvtsPerBX"].data<cool::Float>(); // Mu
+
+  // Check (and protect for NaN
+  if ( std::isnan (LBAvInstLumi) ) {
+    ATH_MSG_WARNING( " Luminosity is not a number.. " << LBAvInstLumi << "  ... set it to 0 " );
+    LBAvInstLumi=0.;
+  }
+
+  if ( std::isnan (LBAvEvtsPerBX) ) {
+    ATH_MSG_WARNING( " Luminosity is not a number.. " << LBAvEvtsPerBX << "  ... set it to 0 " );
+    LBAvEvtsPerBX=0.;
+  }
+
+  lumi.setLbAverageLuminosity (LBAvInstLumi);
+  lumi.setLbAverageInteractionsPerCrossing (LBAvEvtsPerBX);
+
+  // Check validity of per-BCID luminosity (will issue warning in recalcPerBCIDLumi
+  int perBcidValid = (valid/10) % 10;
+  if ((perBcidValid > 0) && m_skipInvalid) {
+    return StatusCode::SUCCESS;
+  }
+
+  // Also save per-BCID blob if it exists
+  for (coral::AttributeList::const_iterator attr = attrList.begin();
+       attr != attrList.end(); ++attr)
+  {
+    if (attr->specification().name() == "BunchInstLumi") {
+      if (!attrList["BunchInstLumi"].isNull())
+	bunchInstLumiBlob = &attrList["BunchInstLumi"].data<coral::Blob>();
+      break;
+    }
+  }
+
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Fill in per-bunch luminosity data.
+ * @param ctx Event context.
+ * @param bunchInstLumiBlob Packed per-bunch luminosity data.
+ *                          Null for Run 1.
+ * @param preferredChannel Preferred luminosity channel to use.
+ * @param calibChannel Calibration luminosity channel to use.
+ * @param range Validity range of the conditions data being filled.
+ *              Updated if needed.
+ * @param lumi Output luminosity data being filled.
+ */
+StatusCode
+LuminosityCondAlg::updatePerBunchLumi (const EventContext& ctx,
+                                       const coral::Blob* bunchInstLumiBlob,
+                                       unsigned int preferredChannel,
+                                       unsigned int calibChannel,
+                                       EventIDRange& range,
+                                       LuminosityCondData& lumi) const
+{
+  if (lumi.lbAverageLuminosity() <= 0.) {
+    ATH_MSG_WARNING( "LBAvInstLumi is zero or negative in recalculatePerBCIDLumi():"
+                     << lumi.lbAverageLuminosity());
+    return StatusCode::SUCCESS;
+  }
+
+  ATH_CHECK( updateMuToLumi (ctx, calibChannel, range, lumi) );
+
+  // Check here if we want to do this the Run1 way (hard) or the Run2 way (easy)
+
+  if (bunchInstLumiBlob != nullptr) { // Run2 way, easy
+    ATH_CHECK( updatePerBunchLumiRun2 (*bunchInstLumiBlob,
+                                       preferredChannel,
+                                       lumi) );
+  }
+  else { // Run1 way, hard!
+    ATH_CHECK( updatePerBunchLumiRun1 (ctx,
+                                       preferredChannel,
+                                       range,
+                                       lumi) );
+  }
+
+  ATH_MSG_DEBUG( "finished recalculatePerBCIDLumi() for alg: "
+                 << preferredChannel );
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Fill in mu-to-lumi calibration.
+ * @param ctx Event context.
+ * @param calibChannel Calibration luminosity channel to use.
+ * @param range Validity range of the conditions data being filled.
+ *              Updated if needed.
+ * @param lumi Output luminosity data being filled.
+ */
+StatusCode
+LuminosityCondAlg::updateMuToLumi (const EventContext& ctx,
+                                   unsigned int calibChannel,
+                                   EventIDRange& range,
+                                   LuminosityCondData& lumi) const
+{
+  SG::ReadCondHandle<OnlineLumiCalibrationCondData> onlineLumiCalibration
+    ( m_onlineLumiCalibrationInputKey, ctx );
+  EventIDRange range2;
+  ATH_CHECK( onlineLumiCalibration.range (range2) );
+  range = EventIDRange::intersect (range, range2);
+
+  // This is the only correct way to do this!
+  // The division below gives average mu (over all bunches) to total lumi
+  float muToLumi = onlineLumiCalibration->getMuToLumi (calibChannel);
+
+  // Check if this is reasonable
+  if (muToLumi < 0.) {
+    ATH_MSG_WARNING(" Found muToLumi = " << muToLumi << " for channel " << calibChannel << ". Try backup channel..." );
+    muToLumi = onlineLumiCalibration->getMuToLumi(m_calibBackupChannel);
+    ATH_MSG_WARNING(" Found muToLumi = " << muToLumi << " for backup channel " << m_calibBackupChannel);
+  }
+
+  // Check validity
+  bool isValid = true;
+  int perBcidValid = (lumi.lbAverageValid()/10) % 10;
+  if ((lumi.lbAverageValid() & 0x03) || (perBcidValid > 0)) {  // Skip if either per-BCID or LBAv is invalid
+    isValid = false;
+    if (m_skipInvalid) {
+      ATH_MSG_WARNING( " Invalid per-BCID luminosity found: "
+                       << lumi.lbAverageValid() << "!" );
+      return StatusCode::SUCCESS;
+    } else {
+      ATH_MSG_WARNING( " Invalid per-BCID luminosity found: "
+                       << lumi.lbAverageValid()
+                       << " continuing because skipInvalid == FALSE" );
+    }
+  }
+
+  // Now check muToLumi and report depending upon whether lumi is valid or not
+  if (muToLumi < 0.) {
+    if (isValid) {
+      ATH_MSG_ERROR(" Found invalid muToLumi = " << muToLumi << " for backup channel " << m_calibBackupChannel << "!");
+    } else {
+      ATH_MSG_WARNING(" Found invalid muToLumi = " << muToLumi << " for backup channel " << m_calibBackupChannel << "!");
+    }
+
+    // Don't keep negative values
+    muToLumi = 0.;
+  }
+
+  ATH_MSG_DEBUG(" Found muToLumi = " << muToLumi << " for channel "
+                << calibChannel );
+
+  lumi.setMuToLumi (muToLumi);
+
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Fill in per-bunch luminosity data, run 2 and later.
+ * @param bunchInstLumiBlob Packed per-bunch luminosity data.
+ * @param preferredChannel Preferred luminosity channel to use.
+ * @param lumi Output luminosity data being filled.
+ */
+StatusCode
+LuminosityCondAlg::updatePerBunchLumiRun2 (const coral::Blob& bunchInstLumiBlob,
+                                           unsigned int preferredChannel,
+                                           LuminosityCondData& lumi) const
+{
+  ATH_MSG_DEBUG( "starting Run2 recalculatePerBCIDLumi() for alg: " << preferredChannel );
+
+  // Check that the length isn't zero
+  if (bunchInstLumiBlob.size() == 0) {
+    ATH_MSG_ERROR("BunchInstLumi blob found with zero length!");
+    return StatusCode::FAILURE;
+  }
+
+  // Hardcode the Run2 BLOB decoding (should use CoolLumiUtilities...)
+  const uint8_t* ATH_RESTRICT pchar =
+    static_cast<const uint8_t*>(bunchInstLumiBlob.startingAddress()); // First byte holds storage size and mode
+  unsigned int bss = ((*pchar) % 100) / 10;  // Byte storage size
+  unsigned int smod = ((*pchar) % 10);       // Storage mode
+  ++pchar;  // Points to next char after header
+
+  ATH_MSG_DEBUG( "BunchInstLumi blob found with storage mode " << smod
+                 << " and byte storage size " << bss );
+
+  // Make sure we have what we think we have
+  if (bss != 4 || smod != 1) {
+    ATH_MSG_ERROR( "BunchInstLumi blob found with storage mode " << smod << " and byte storage size " << bss << " - Unknown!");
+    return StatusCode::FAILURE;
+  }
+
+  unsigned int nbcids = LuminosityCondData::TOTAL_LHC_BCIDS;
+  unsigned int bloblength = bss * nbcids + 1;
+
+  if (static_cast<cool::UInt32>(bunchInstLumiBlob.size()) != bloblength) {
+    ATH_MSG_ERROR( "BunchRawInstLumi blob found with length"
+                   << bunchInstLumiBlob.size() << "in storage mode" << smod
+                   <<  ", expecting " << bloblength << "!" );
+    return StatusCode::FAILURE;
+  }
+
+  // Length is correct, read raw data according to packing scheme
+  // This is absolute luminosity, so just unpack values into our array
+    
+  ATH_MSG_DEBUG( "Unpacking lumi value from blob");
+  std::vector<float> instLumi (nbcids);
+  for (unsigned int i=0; i<nbcids; i++) {
+    // Can't use assignment directly because source may be misaligned.
+    instLumi[i] = CxxUtils::get_unaligned_float (pchar);
+  }
+
+  if (msgLvl (MSG::DEBUG)) {
+    for (unsigned int i=0; i<nbcids; i++) {
+      ATH_MSG_DEBUG( "Bcid: " << i << " Lumi: " << instLumi[i] );
+    }
+  }
+
+  lumi.setLbLuminosityPerBCIDVector (std::move (instLumi));
+
+  return StatusCode::SUCCESS;
+}
+
+
+/**
+ * @brief Fill in per-bunch luminosity data, run 1.
+ * @param preferredChannel Preferred luminosity channel to use.
+ * @param range Validity range of the conditions data being filled.
+ *              Updated if needed.
+ * @param lumi Output luminosity data being filled.
+ */
+StatusCode
+LuminosityCondAlg::updatePerBunchLumiRun1 (const EventContext& ctx,
+                                           unsigned int preferredChannel,
+                                           EventIDRange& range,
+                                           LuminosityCondData& lumi) const
+{
+  ATH_MSG_DEBUG( "starting Run1 recalculatePerBCIDLumi() for alg: " << preferredChannel );
+    
+  if (preferredChannel == 0) {
+    return StatusCode::SUCCESS;
+  }
+
+  // Nothing to do if we don't have the ingredients
+  if (m_onlineLumiCalibrationInputKey.empty()) {
+    ATH_MSG_ERROR( "OnlineLumiCalibrationInputKey.empty() is TRUE, skipping..." );
+    return StatusCode::FAILURE;
+  }
+  if (m_bunchLumisInputKey.empty()) {
+    ATH_MSG_DEBUG( "BunchLumisInputKey.empty() is TRUE, skipping..." );
+    return StatusCode::FAILURE;
+  }
+  if (m_bunchGroupInputKey.empty()) {
+    ATH_MSG_DEBUG( "BunchGroupTool.empty() is TRUE, skipping..." );
+    return StatusCode::FAILURE;
+  }
+  if (m_fillParamsInputKey.empty()) {
+    ATH_MSG_ERROR( "FillParamsInputKey.empty() is TRUE, skipping..." );
+    return StatusCode::FAILURE;
+  }
+
+  SG::ReadCondHandle<OnlineLumiCalibrationCondData> onlineLumiCalibration
+    (m_onlineLumiCalibrationInputKey, ctx);
+  SG::ReadCondHandle<BunchLumisCondData> bunchLumis (m_bunchLumisInputKey, ctx);
+  SG::ReadCondHandle<BunchGroupCondData> bunchGroup (m_bunchGroupInputKey, ctx);
+  SG::ReadCondHandle<FillParamsCondData> fillParams (m_fillParamsInputKey, ctx);
+
+  EventIDRange range2;
+  ATH_CHECK( onlineLumiCalibration.range (range2) );
+  range = EventIDRange::intersect (range, range2);
+  ATH_CHECK( bunchLumis.range (range2) );
+  range = EventIDRange::intersect (range, range2);
+  ATH_CHECK( bunchGroup.range (range2) );
+  range = EventIDRange::intersect (range, range2);
+  ATH_CHECK( fillParams.range (range2) );
+  range = EventIDRange::intersect (range, range2);
+
+  const std::vector<unsigned int>& luminousBunches = fillParams->luminousBunches();
+  ATH_MSG_DEBUG( "N LuminousBunches:" << luminousBunches.size() );
+
+  // Get the raw data for the preferred channel
+  const std::vector<float>& rawLumiVec = bunchLumis->rawLuminosity(preferredChannel);
+
+  //
+  // Calibration step
+  //
+    
+  //  Here we want to go through and calibrate raw values in the luminous bunches only.
+  // This is what the OL adds up, and since these are online calibrations, we want to rescale the total
+  // to agree to whatever offline tag we are using.
+  std::vector<float> calLumiVec (LuminosityCondData::TOTAL_LHC_BCIDS, 0.);
+    
+  // Update muToLumi while we are at it (also check that calibration exists)
+  float muToLumi = onlineLumiCalibration->getMuToLumi (preferredChannel);
+  if (muToLumi <= 0.) {
+    ATH_MSG_ERROR( " dont have calibration information for preferred channel "
+                   << preferredChannel << "!" );
+    return StatusCode::FAILURE;
+  }
+  lumi.setMuToLumi (muToLumi);
+
+  double lumiSum = 0.;
+  for (unsigned int bcid : luminousBunches) {
+    // Don't waste time on zero lumi 
+    if (rawLumiVec[bcid] <= 0.) {
+      ATH_MSG_DEBUG( "Calibrate BCID " << bcid << " with raw "
+                     << rawLumiVec[bcid] << " -> skipping" );
+      continue;
+    }
+
+    // Calibrate
+    if (!onlineLumiCalibration->calibrateLumi(preferredChannel,
+                                              rawLumiVec[bcid],
+                                              calLumiVec[bcid]))
+    {
+      ATH_MSG_DEBUG( "Calibrate BCID " << bcid << " with raw " << rawLumiVec[bcid] << " -> calibration failed!" );
+      ATH_MSG_WARNING( "Per-BCID calibration failed for bcid " << bcid << " with raw lumi = " << rawLumiVec[bcid] );
+      continue;
+    }
+      
+    lumiSum += calLumiVec[bcid];
+      
+    ATH_MSG_DEBUG( "Calibrate BCID " << bcid << " with raw " << rawLumiVec[bcid] << " -> " << calLumiVec[bcid] );
+  }
+
+  // Work out scale factor between offline and online estimate
+  float offlineOnlineRatio = 1.;
+  if (lumiSum > 0.) offlineOnlineRatio = lumi.lbAverageLuminosity() / lumiSum;
+    
+  ATH_MSG_DEBUG( " Offline/Online scale factor: " << lumi.lbAverageLuminosity()
+                 << " / " << lumiSum << " = " << offlineOnlineRatio );
+
+  // Make sure we have values for all BCIDs in the physics bunch group
+  for (unsigned int bcid : bunchGroup->bunchGroup (1)) {
+    // Don't repeat if value already exists
+    if (calLumiVec[bcid] > 0.) continue;
+    if (rawLumiVec[bcid] <= 0.) continue;
+      
+    // Calibrate
+    if (!onlineLumiCalibration->calibrateLumi(preferredChannel,
+                                              rawLumiVec[bcid],
+                                              calLumiVec[bcid]))
+    {
+      ATH_MSG_DEBUG( " -> Calibration failed!" );
+      ATH_MSG_WARNING( "Per-BCID calibration failed for bcid " << bcid
+                       << " with raw lumi = " << rawLumiVec[bcid] );
+      continue;
+    }
+  }
+
+  // Almost done, now we apply the scale factor to all BCIDs
+  for (float& lumi : calLumiVec) {
+    lumi *= offlineOnlineRatio;
+  }
+
+  lumi.setLbLuminosityPerBCIDVector (std::move (calLumiVec));
+
+  return StatusCode::SUCCESS;
+}
diff --git a/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.h b/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7c1b5fd7cfc765518f14dde3169062411ed44f3
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/src/LuminosityCondAlg.h
@@ -0,0 +1,168 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file LumiBlockComps/src/LuminosityCondAlg.h
+ * @author scott snyder <snyder@bnl.gov>, from existing LuminosityTool.
+ * @date May, 2019
+ * @brief Conditions algorithm for luminosity data.
+ */
+
+
+#ifndef LUMIBLOCKCOMPS_LUMINOSITYCONDALG_H
+#define LUMIBLOCKCOMPS_LUMINOSITYCONDALG_H
+
+
+#include "LumiBlockData/LuminosityCondData.h"
+#include "CoolLumiUtilities/OnlineLumiCalibrationCondData.h"
+#include "CoolLumiUtilities/FillParamsCondData.h"
+#include "CoolLumiUtilities/BunchLumisCondData.h"
+#include "CoolLumiUtilities/BunchGroupCondData.h"
+#include "AthenaBaseComps/AthReentrantAlgorithm.h"
+#include "AthenaPoolUtilities/CondAttrListCollection.h"
+#include "StoreGate/ReadCondHandleKey.h"
+#include "StoreGate/WriteCondHandleKey.h"
+#include "CoralBase/Blob.h"
+
+
+/**
+ * @brief Conditions algorithm for luminosity data.
+ */
+class LuminosityCondAlg
+  : public AthReentrantAlgorithm
+{
+public:
+  /// Forward base class ctor.
+  using AthReentrantAlgorithm::AthReentrantAlgorithm;
+
+
+  /// Gaudi initialize method.
+  virtual StatusCode initialize() override;
+
+
+  /// Algorithm execute method.
+  virtual StatusCode execute (const EventContext& ctx) const override;
+
+
+private:
+  /**
+   * @brief Unpack luminosity data from the attribute list.
+   * @param lumiData Input luminosity data.
+   * @param lumi Output luminosity data being filled.
+   * @param preferredChannel[out] Preferred luminosity channel to use.
+   * @param calibChannel[out] Calibration luminosity channel to use.
+   * @param bunchInstLumiBlob[out] Packed per-bunch luminosity data.
+   *                               Set to null for Run 1.
+   *
+   * Unpacks luminosity data from the attribute list.
+   * Fills in the average luminosity fields in @c lumi,
+   * and determines the luminosity channels to use.
+   * For Run 2 and later, returns the packed luminosity data.
+   */
+  StatusCode
+  updateAvgLumi (const CondAttrListCollection& lumiData,
+                 LuminosityCondData& lumi,
+                 unsigned int& preferredChannel,
+                 unsigned int& calibChannel,
+                 const coral::Blob*& bunchInstLumiBlob) const;
+
+
+  /**
+   * @brief Fill in per-bunch luminosity data.
+   * @param ctx Event context.
+   * @param bunchInstLumiBlob Packed per-bunch luminosity data.
+   *                          Null for Run 1.
+   * @param preferredChannel Preferred luminosity channel to use.
+   * @param calibChannel Calibration luminosity channel to use.
+   * @param range Validity range of the conditions data being filled.
+   *              Updated if needed.
+   * @param lumi Output luminosity data being filled.
+   */
+  StatusCode
+  updatePerBunchLumi (const EventContext& ctx,
+                      const coral::Blob* bunchInstLumiBlob,
+                      unsigned int preferredChannel,
+                      unsigned int calibChannel,
+                      EventIDRange& range,
+                      LuminosityCondData& lumi) const;
+
+
+  /**
+   * @brief Fill in mu-to-lumi calibration.
+   * @param ctx Event context.
+   * @param calibChannel Calibration luminosity channel to use.
+   * @param range Validity range of the conditions data being filled.
+   *              Updated if needed.
+   * @param lumi Output luminosity data being filled.
+   */
+  StatusCode
+  updateMuToLumi (const EventContext& ctx,
+                  unsigned int calibChannel,
+                  EventIDRange& range,
+                  LuminosityCondData& lumi) const;
+
+
+  /**
+   * @brief Fill in per-bunch luminosity data, run 2 and later.
+   * @param bunchInstLumiBlob Packed per-bunch luminosity data.
+   * @param preferredChannel Preferred luminosity channel to use.
+   * @param lumi Output luminosity data being filled.
+   */
+  StatusCode
+  updatePerBunchLumiRun2 (const coral::Blob& bunchInstLumiBlob,
+                          unsigned int preferredChannel,
+                          LuminosityCondData& lumi) const;
+
+
+  /**
+   * @brief Fill in per-bunch luminosity data, run 1.
+   * @param preferredChannel Preferred luminosity channel to use.
+   * @param range Validity range of the conditions data being filled.
+   *              Updated if needed.
+   * @param lumi Output luminosity data being filled.
+   */
+  StatusCode
+  updatePerBunchLumiRun1 (const EventContext& ctx,
+                          unsigned int preferredChannel,
+                          EventIDRange& range,
+                          LuminosityCondData& lumi) const;
+
+
+  Gaudi::Property<unsigned long> m_lumiChannel
+    { this, "LumiChannelNumber", 0, "Luminosity channel to read.  0 means to determine from the data." };
+
+  Gaudi::Property<unsigned long> m_calibBackupChannel
+    { this, "CalibBackupChannel", 112, "Backup channel in case calibChannel doesn't exist in online calibration folder" };
+
+  Gaudi::Property<bool> m_skipInvalid
+    { this, "SkipInvalid", true, "Flag to control whether invalid data is skipped:  True (default), returning a zero luminosity; false, retruning available luminosity values anyway" };
+
+  SG::ReadCondHandleKey<CondAttrListCollection> m_luminosityFolderInputKey
+  { this, "LuminosityFolderInputKey", "/TRIGGER/OFLLUMI/LBLESTOFL",
+    "Input luminosityCOOL folder." };
+
+  SG::ReadCondHandleKey<OnlineLumiCalibrationCondData> m_onlineLumiCalibrationInputKey
+  { this, "OnlineLumiCalibrationInputKey", "OnlineLumiCalibrationCondData",
+    "Input luminosity calibration." };
+
+  SG::ReadCondHandleKey<BunchLumisCondData> m_bunchLumisInputKey
+  { this, "BunchLumisInputKey", "",
+    "Input raw luminosities.  Only used for Run 1." };
+
+  SG::ReadCondHandleKey<BunchGroupCondData> m_bunchGroupInputKey
+  { this, "BunchGroupInputKey", "",
+    "Input filled bunch data.  Only used for Run 1." };
+
+  SG::ReadCondHandleKey<FillParamsCondData> m_fillParamsInputKey
+  { this, "FillParamsInputKey", "",
+    "Input luminous bunch data.  Only used for Run 1." };
+
+  /// Output conditions object.
+  SG::WriteCondHandleKey<LuminosityCondData> m_luminosityOutputKey
+  { this, "LuminosityOutputKey", "LuminosityCondData",
+    "Output luminosity data." };
+};
+
+
+#endif // not LUMIBLOCKCOMPS_LUMINOSITYCONDALG_H
diff --git a/LumiBlock/LumiBlockComps/src/components/LumiBlockComps_entries.cxx b/LumiBlock/LumiBlockComps/src/components/LumiBlockComps_entries.cxx
index 58b7359a3d9cf3d0d05a86af64434dc246202bd8..11cbdee34df1ab2af2b9842fa2304d73b3582924 100644
--- a/LumiBlock/LumiBlockComps/src/components/LumiBlockComps_entries.cxx
+++ b/LumiBlock/LumiBlockComps/src/components/LumiBlockComps_entries.cxx
@@ -10,6 +10,7 @@
 #include "LumiBlockComps/LumiCalcSvc.h"
 #include "LumiBlockComps/LumiBlockTester.h"
 #include "../LBDurationCondAlg.h"
+#include "../LuminosityCondAlg.h"
 #endif
 
 DECLARE_COMPONENT( CreateLumiBlockCollectionFromFile )
@@ -21,6 +22,7 @@ DECLARE_COMPONENT( LuminosityTool )
 DECLARE_COMPONENT( TrigLivefractionTool )
 DECLARE_COMPONENT( LumiCalcSvc )
 DECLARE_COMPONENT( LBDurationCondAlg )
+DECLARE_COMPONENT( LuminosityCondAlg )
 #endif
 DECLARE_COMPONENT( LumiBlockMetaDataTool )
 
diff --git a/LumiBlock/LumiBlockComps/test/LuminosityCondAlg_test.cxx b/LumiBlock/LumiBlockComps/test/LuminosityCondAlg_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..aaf9579c7d268b4ec3207982178078c00ca40ac2
--- /dev/null
+++ b/LumiBlock/LumiBlockComps/test/LuminosityCondAlg_test.cxx
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file LumiBlockComps/test/LuminosityAlg_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date May, 2019
+ * @brief Unit test for LuminosityCondAlg.
+ */
+
+
+#undef NDEBUG
+#include "../src/LuminosityCondAlg.h"
+#include "LumiBlockData/LuminosityCondData.h"
+#include "CoolLumiUtilities/BunchLumisCondData.h"
+#include "CoolLumiUtilities/BunchGroupCondData.h"
+#include "CoolLumiUtilities/FillParamsCondData.h"
+#include "CoolLumiUtilities/OnlineLumiCalibrationCondData.h"
+#include "AthenaKernel/ExtendedEventContext.h"
+#include "PersistentDataModel/AthenaAttributeList.h"
+#include "TestTools/initGaudi.h"
+#include "TestTools/FLOATassert.h"
+#include "CoolKernel/IObject.h"
+#include <iostream>
+#include <cassert>
+
+
+const unsigned int TOTAL_LHC_BCIDS = 3564;
+
+// BCID, lumi pairs
+const std::pair<unsigned int, float> lumiData[] =
+  {
+   { 10, 10.5 },
+   { 20, 20.5 },
+   { 30, 12.5 },
+   { 40, 13.5 },
+   { 55, 15.5 },
+};
+
+
+// Run1 results
+const std::pair<unsigned int, float> lumiCalibData[] =
+  {
+   { 10, 0.171479 },
+   { 20, 0.622183 },
+   { 30, 0.239085 },
+   { 40, 0.277113 },
+   { 55, 0.361620 },
+};
+
+
+class TestRCUSvc
+  : public Athena::IRCUSvc
+{
+public:
+  virtual StatusCode remove (Athena::IRCUObject* /*obj*/) override
+  {
+    return StatusCode::SUCCESS;
+  }
+  virtual size_t getNumSlots() const override { return 1; }
+  virtual void add (Athena::IRCUObject* /*obj*/) override
+  { }
+
+  virtual unsigned long addRef() override { std::abort(); }
+  virtual unsigned long release() override { std::abort(); }
+  virtual StatusCode queryInterface(const InterfaceID &/*ti*/, void** /*pp*/) override { std::abort(); }
+};
+
+
+EventIDBase timestamp (int t)
+{
+  return EventIDBase (EventIDBase::UNDEFNUM,  // run
+                      EventIDBase::UNDEFEVT,  // event
+                      t);
+}
+
+
+void push_float (float x, std::vector<uint8_t>& data)
+{
+  union {
+    float f;
+    uint32_t i;
+  } cnv;
+  cnv.f = x;
+  data.push_back (cnv.i & 0xff);
+  data.push_back ((cnv.i>>8) & 0xff);
+  data.push_back ((cnv.i>>16) & 0xff);
+  data.push_back ((cnv.i>>24) & 0xff);
+}
+
+
+coral::Blob makeBlob (float offs)
+{
+  unsigned int bss = 4;
+  unsigned int smod = 1;
+
+  std::vector<uint8_t> data;
+  data.push_back (bss*10 + smod);
+
+  unsigned int nbcid = std::end(lumiData) - std::begin(lumiData);
+
+  unsigned int ilumi = 0;
+  for (unsigned int bcid = 0; bcid < TOTAL_LHC_BCIDS; bcid++) {
+    float lumi = 0;
+    if (ilumi < nbcid && lumiData[ilumi].first == bcid) {
+      lumi = lumiData[ilumi].second+offs;
+      ++ilumi;
+    }
+    push_float (lumi, data);
+  }
+
+  coral::Blob blob (data.size());
+  memcpy (blob.startingAddress(), data.data(), data.size());
+  return blob;
+}
+
+
+std::unique_ptr<CondAttrListCollection> make_run2_attrlist()
+{
+  auto attrs = std::make_unique<CondAttrListCollection> (false);
+  coral::AttributeList al;
+
+  al.extend ("LBAvInstLumi", "float");
+  al.extend ("LBAvEvtsPerBX", "float");
+  al.extend ("Valid", "unsigned int");
+  al.extend ("AlgorithmID", "unsigned int");
+  al.extend ("BunchInstLumi", "blob");
+  al["LBAvInstLumi"].setValue (1.5f);
+  al["LBAvEvtsPerBX"].setValue (10.5f);
+  al["Valid"].setValue (0u);
+  al["AlgorithmID"].setValue (42u);
+  al["BunchInstLumi"].setValue (makeBlob (0));
+  attrs->add (0, al);
+  return attrs;
+}
+
+
+std::unique_ptr<CondAttrListCollection> make_run1_attrlist()
+{
+  auto attrs = std::make_unique<CondAttrListCollection> (false);
+  coral::AttributeList al;
+
+  al.extend ("LBAvInstLumi", "float");
+  al.extend ("LBAvEvtsPerBX", "float");
+  al.extend ("Valid", "unsigned int");
+  al["LBAvInstLumi"].setValue (1.5f);
+  al["LBAvEvtsPerBX"].setValue (10.5f);
+  unsigned int valid = (42) << 22;
+  // Round up to next 100.
+  valid = ((valid+99)/100) * 100;
+  al["Valid"].setValue (valid);
+  attrs->add (0, al);
+  return attrs;
+}
+
+
+std::unique_ptr<OnlineLumiCalibrationCondData> make_onlineLumiCalib()
+{
+  auto calib = std::make_unique<OnlineLumiCalibrationCondData>();
+
+  float muToLumi = 2.5;
+  float p0 = 1;
+  float p1 = 1;
+  float p2 = 1;
+
+  coral::AttributeList attr;
+  attr.extend ("NumOfParameters", "unsigned int");
+  attr.extend ("Function", "string");
+  attr.extend ("MuToLumi", "float");
+  attr.extend ("Parameters", "blob");
+
+  attr["MuToLumi"].setValue (muToLumi);
+  attr["Function"].setValue (std::string ("Polynomial"));
+
+  coral::Blob blob (9 * sizeof(float));
+  float* p = static_cast<float*> (blob.startingAddress());
+  p[0] = 1;
+  p[1] = 0;
+  p[2] = 100;
+  p[3] = p0;
+  p[4] = p1;
+  p[5] = p2;
+  p[6] = 0;
+  p[7] = 0;
+  p[8] = 0;
+  attr["Parameters"].setValue (blob);
+  attr["NumOfParameters"].setValue (9u);
+
+  OnlineLumiCalibrator lc;
+  assert (lc.setCalibration (attr));
+  calib->set (42, std::move (lc));
+  
+  return calib;
+}
+
+
+std::unique_ptr<BunchLumisCondData> make_bunchLumis()
+{
+  auto bl = std::make_unique<BunchLumisCondData>();
+  std::vector<float> rawLumi (LuminosityCondData::TOTAL_LHC_BCIDS);
+  for (const auto& p : lumiData) {
+    rawLumi[p.first] = p.second;
+  }
+  bl->addChannel (42, std::move (rawLumi));
+  return bl;
+}
+
+
+std::unique_ptr<BunchGroupCondData> make_bunchGroup()
+{
+  auto bg = std::make_unique<BunchGroupCondData>();
+  for (const auto& p : lumiData) {
+    bg->addBCID (p.first, 2);
+  }
+  return bg;
+}
+
+
+std::unique_ptr<FillParamsCondData> make_fillParams()
+{
+  auto params = std::make_unique<FillParamsCondData>();
+  std::vector<uint16_t> bunches;
+  for (const auto& p : lumiData) {
+    if (p.first == 10) continue;
+    bunches.push_back (p.first);
+  }
+  params->setLuminousBunches (bunches.data(), bunches.data()+bunches.size());
+  return params;
+}
+
+
+// run2
+void test1 (ISvcLocator* svcloc)
+{
+  std::cout << "test1\n";
+
+  EventContext ctx;
+  ctx.setExtension (Atlas::ExtendedEventContext());
+  EventIDBase eid (0, 0, 0, 0);
+  ctx.setEventID (eid);
+
+  LuminosityCondAlg alg ("LuminosityCondAlg", svcloc);
+  alg.addRef();
+  assert( alg.sysInitialize().isSuccess() );
+
+  TestRCUSvc rcu;
+  
+  DataObjID id1 ("testLumi");
+  auto cc1 = std::make_unique<CondCont<CondAttrListCollection> > (rcu, id1);
+  const EventIDRange range1 (timestamp (0), timestamp (100));
+  assert( cc1->insert (range1, make_run2_attrlist(), ctx).isSuccess() );
+
+  DataObjID id2 ("testCalib");
+  auto cc2 = std::make_unique<CondCont<OnlineLumiCalibrationCondData> > (rcu, id2);
+  const EventIDRange range2 (timestamp (0), timestamp (90));
+  assert( cc2->insert (range2, make_onlineLumiCalib(), ctx).isSuccess() );
+
+  ServiceHandle<StoreGateSvc> conditionStore ("ConditionStore", "test");
+  assert( conditionStore->record (std::move (cc1), "testLumi") );
+  assert( conditionStore->record (std::move (cc2), "testCalib") );
+
+  assert( alg.execute (ctx).isSuccess() );
+
+  CondCont<LuminosityCondData>* ccout = nullptr;
+  assert( conditionStore->retrieve (ccout, "LuminosityCondData").isSuccess() );
+  const LuminosityCondData* data = 0;
+  const EventIDRange* rangeout = nullptr;
+  assert (ccout->find (eid, data, &rangeout));
+  assert (rangeout->start().time_stamp() == timestamp(0).time_stamp());
+  assert (rangeout->stop().time_stamp() == timestamp(90).time_stamp());
+
+  assert( data->lbAverageLuminosity() == 1.5 );
+  assert( data->lbAverageInteractionsPerCrossing() == 10.5 );
+  assert( data->lbAverageValid() == 0 );
+  assert( data->muToLumi() == 2.5 );
+
+  std::vector<float> vec = data->lbLuminosityPerBCIDVector();
+  assert (vec.size() == LuminosityCondData::TOTAL_LHC_BCIDS);
+  for (const auto& p : lumiData) {
+    assert (vec[p.first] == p.second);
+    vec[p.first] = 0;
+  }
+
+  for (float f : vec) {
+    assert (f == 0);
+  }
+}
+
+
+// run1
+void test2 (ISvcLocator* svcloc)
+{
+  std::cout << "test2\n";
+
+  EventContext ctx;
+  ctx.setExtension (Atlas::ExtendedEventContext());
+  EventIDBase eid (0, 0, 0, 0);
+  ctx.setEventID (eid);
+
+  LuminosityCondAlg alg ("LuminosityCondAlgRun1", svcloc);
+  alg.addRef();
+  assert( alg.sysInitialize().isSuccess() );
+
+  TestRCUSvc rcu;
+  
+  DataObjID id1 ("testLumiRun1");
+  auto cc1 = std::make_unique<CondCont<CondAttrListCollection> > (rcu, id1);
+  const EventIDRange range1 (timestamp (0), timestamp (100));
+  assert( cc1->insert (range1, make_run1_attrlist(), ctx).isSuccess() );
+
+  DataObjID id2 ("testCalibRun1");
+  auto cc2 = std::make_unique<CondCont<OnlineLumiCalibrationCondData> > (rcu, id2);
+  const EventIDRange range2 (timestamp (0), timestamp (90));
+  assert( cc2->insert (range2, make_onlineLumiCalib(), ctx).isSuccess() );
+
+  DataObjID id3 ("testBunchLumisRun1");
+  auto cc3 = std::make_unique<CondCont<BunchLumisCondData> > (rcu, id3);
+  const EventIDRange range3 (timestamp (0), timestamp (85));
+  assert( cc3->insert (range3, make_bunchLumis(), ctx).isSuccess() );
+
+  DataObjID id4 ("testBunchGroupRun1");
+  auto cc4 = std::make_unique<CondCont<BunchGroupCondData> > (rcu, id4);
+  const EventIDRange range4 (timestamp (0), timestamp (95));
+  assert( cc4->insert (range4, make_bunchGroup(), ctx).isSuccess() );
+
+  DataObjID id5 ("testFillParamsRun1");
+  auto cc5 = std::make_unique<CondCont<FillParamsCondData> > (rcu, id5);
+  const EventIDRange range5 (timestamp (0), timestamp (80));
+  assert( cc5->insert (range5, make_fillParams(), ctx).isSuccess() );
+
+  ServiceHandle<StoreGateSvc> conditionStore ("ConditionStore", "test");
+  assert( conditionStore->record (std::move (cc1), "testLumiRun1") );
+  assert( conditionStore->record (std::move (cc2), "testCalibRun1") );
+  assert( conditionStore->record (std::move (cc3), "testBunchLumisRun1") );
+  assert( conditionStore->record (std::move (cc4), "testBunchGroupRun1") );
+  assert( conditionStore->record (std::move (cc5), "testFillParamsRun1") );
+
+  assert( alg.execute (ctx).isSuccess() );
+
+  CondCont<LuminosityCondData>* ccout = nullptr;
+  assert( conditionStore->retrieve (ccout, "LuminosityCondDataRun1").isSuccess() );
+  const LuminosityCondData* data = 0;
+  const EventIDRange* rangeout = nullptr;
+  assert (ccout->find (eid, data, &rangeout));
+  assert (rangeout->start().time_stamp() == timestamp(0).time_stamp());
+  assert (rangeout->stop().time_stamp() == timestamp(80).time_stamp());
+
+  assert( data->lbAverageLuminosity() == 1.5 );
+  assert( data->lbAverageInteractionsPerCrossing() == 10.5 );
+  assert( (data->lbAverageValid() >> 22) == 42 );
+  assert( (data->lbAverageValid() % 100) == 0 );
+  assert( data->muToLumi() == 2.5 );
+
+  std::vector<float> vec = data->lbLuminosityPerBCIDVector();
+  assert (vec.size() == LuminosityCondData::TOTAL_LHC_BCIDS);
+  for (const auto& p : lumiCalibData) {
+    assert( Athena_test::isEqual (vec[p.first], p.second, 1e-5) );
+    vec[p.first] = 0;
+  }
+
+  for (float f : vec) {
+    assert (f == 0);
+  }
+}
+
+
+// MC
+void test3 (ISvcLocator* svcloc)
+{
+  std::cout << "test3\n";
+
+  EventContext ctx;
+  ctx.setExtension (Atlas::ExtendedEventContext());
+  EventIDBase eid (0, 0, 0, 0, 0, 0);
+  ctx.setEventID (eid);
+
+  LuminosityCondAlg alg ("LuminosityCondAlgMC", svcloc);
+  alg.addRef();
+  assert( alg.sysInitialize().isSuccess() );
+
+  assert( alg.execute (ctx).isSuccess() );
+
+  ServiceHandle<StoreGateSvc> conditionStore ("ConditionStore", "test");
+  CondCont<LuminosityCondData>* ccout = nullptr;
+  assert( conditionStore->retrieve (ccout, "LuminosityCondDataMC").isSuccess() );
+  const LuminosityCondData* data = 0;
+  const EventIDRange* rangeout = nullptr;
+  assert (ccout->find (eid, data, &rangeout));
+  assert (rangeout->start().time_stamp() == timestamp(0).time_stamp());
+
+  assert( data->lbAverageLuminosity() == 0 );
+  assert( data->lbAverageInteractionsPerCrossing() == 0 );
+  assert( data->lbAverageValid() == 0xffffffff );
+  assert( data->muToLumi() == 0 );
+
+  std::vector<float> vec = data->lbLuminosityPerBCIDVector();
+  assert (vec.size() == LuminosityCondData::TOTAL_LHC_BCIDS);
+  for (float f : vec) {
+    assert (f == 0);
+  }
+}
+
+
+int main()
+{
+  std::cout << "LumiBlockComps/LuminosityCondAlg_test\n";
+
+  ISvcLocator* svcloc = nullptr;
+  if (!Athena_test::initGaudi("LumiBlockComps/LuminosityCondAlg_test.txt", svcloc)) {
+    return 1;
+  }
+
+  test1 (svcloc);
+  test2 (svcloc);
+  test3 (svcloc);
+  return 0;
+}