killbill-aplcache

analytics: first pass at sanity api The Analytics sanity

10/31/2012 10:42:33 PM

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/sanity/DefaultAnalyticsSanityApi.java b/analytics/src/main/java/com/ning/billing/analytics/api/sanity/DefaultAnalyticsSanityApi.java
new file mode 100644
index 0000000..11c82ff
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/sanity/DefaultAnalyticsSanityApi.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.api.sanity;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import com.ning.billing.analytics.dao.AnalyticsSanityDao;
+import com.ning.billing.util.callcontext.InternalCallContextFactory;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.TenantContext;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class DefaultAnalyticsSanityApi implements AnalyticsSanityApi {
+
+    private final AnalyticsSanityDao dao;
+    private final InternalCallContextFactory internalCallContextFactory;
+
+    @Inject
+    public DefaultAnalyticsSanityApi(final AnalyticsSanityDao dao,
+                                     final InternalCallContextFactory internalCallContextFactory) {
+        this.dao = dao;
+        this.internalCallContextFactory = internalCallContextFactory;
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithEntitlement(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBstMatchesSubscriptionEvents(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithInvoice(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBiiMatchesInvoiceItems(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithPayment(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final Collection<UUID> check1 = dao.checkBipMatchesInvoicePayments(internalTenantContext);
+        final Collection<UUID> check2 = dao.checkBinAmountPaidMatchesInvoicePayments(internalTenantContext);
+        final Collection<UUID> check3 = dao.checkBinAmountChargedMatchesInvoicePayments(internalTenantContext);
+
+        return ImmutableSet.<UUID>copyOf(Iterables.<UUID>concat(check1, check2, check3));
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsInSyncWithTag(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        return dao.checkBacTagsMatchesTags(internalTenantContext);
+    }
+
+    @Override
+    public Collection<UUID> checkAnalyticsConsistency(final TenantContext context) {
+        final InternalTenantContext internalTenantContext = internalCallContextFactory.createInternalTenantContext(context);
+        final Collection<UUID> check1 = dao.checkBinBiiBalanceConsistency(internalTenantContext);
+        final Collection<UUID> check2 = dao.checkBinBiiAmountCreditedConsistency(internalTenantContext);
+        final Collection<UUID> check3 = dao.checkBacBinBiiConsistency(internalTenantContext);
+
+        return ImmutableSet.<UUID>copyOf(Iterables.<UUID>concat(check1, check2, check3));
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanityDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanityDao.java
new file mode 100644
index 0000000..9a689ab
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanityDao.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public interface AnalyticsSanityDao {
+
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(InternalTenantContext context);
+
+    public Collection<UUID> checkBiiMatchesInvoiceItems(InternalTenantContext context);
+
+    public Collection<UUID> checkBipMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(InternalTenantContext context);
+
+    public Collection<UUID> checkBinBiiBalanceConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBacBinBiiConsistency(InternalTenantContext context);
+
+    public Collection<UUID> checkBacTagsMatchesTags(InternalTenantContext context);
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.java
new file mode 100644
index 0000000..fe9931f
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.callcontext.InternalTenantContextBinder;
+import com.ning.billing.util.dao.UuidMapper;
+
+@ExternalizedSqlViaStringTemplate3()
+@RegisterMapper(UuidMapper.class)
+public interface AnalyticsSanitySqlDao extends Transactional<AnalyticsSanitySqlDao>, Transmogrifier {
+
+    @SqlQuery
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBiiMatchesInvoiceItems(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBipMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinBiiBalanceConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBacBinBiiConsistency(@InternalTenantContextBinder final InternalTenantContext context);
+
+    @SqlQuery
+    public Collection<UUID> checkBacTagsMatchesTags(@InternalTenantContextBinder final InternalTenantContext context);
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsSanityDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsSanityDao.java
new file mode 100644
index 0000000..8e97d89
--- /dev/null
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsSanityDao.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.dao;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public class DefaultAnalyticsSanityDao implements AnalyticsSanityDao {
+
+    private final AnalyticsSanitySqlDao sqlDao;
+
+    @Inject
+    public DefaultAnalyticsSanityDao(final IDBI dbi) {
+        sqlDao = dbi.onDemand(AnalyticsSanitySqlDao.class);
+    }
+
+    @Override
+    public Collection<UUID> checkBstMatchesSubscriptionEvents(final InternalTenantContext context) {
+        return sqlDao.checkBstMatchesSubscriptionEvents(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBiiMatchesInvoiceItems(final InternalTenantContext context) {
+        return sqlDao.checkBiiMatchesInvoiceItems(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBipMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBipMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinAmountPaidMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBinAmountPaidMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinAmountChargedMatchesInvoicePayments(final InternalTenantContext context) {
+        return sqlDao.checkBinAmountChargedMatchesInvoicePayments(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinBiiBalanceConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBinBiiBalanceConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBinBiiAmountCreditedConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBinBiiAmountCreditedConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBacBinBiiConsistency(final InternalTenantContext context) {
+        return sqlDao.checkBacBinBiiConsistency(context);
+    }
+
+    @Override
+    public Collection<UUID> checkBacTagsMatchesTags(final InternalTenantContext context) {
+        return sqlDao.checkBacTagsMatchesTags(context);
+    }
+}
diff --git a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
index 7c9e1fd..ec2732e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/setup/AnalyticsModule.java
@@ -22,9 +22,12 @@ import com.ning.billing.analytics.BusinessSubscriptionTransitionDao;
 import com.ning.billing.analytics.BusinessTagDao;
 import com.ning.billing.analytics.api.AnalyticsService;
 import com.ning.billing.analytics.api.DefaultAnalyticsService;
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
+import com.ning.billing.analytics.api.sanity.DefaultAnalyticsSanityApi;
 import com.ning.billing.analytics.api.user.AnalyticsUserApi;
 import com.ning.billing.analytics.api.user.DefaultAnalyticsUserApi;
 import com.ning.billing.analytics.dao.AnalyticsDao;
+import com.ning.billing.analytics.dao.AnalyticsSanityDao;
 import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
 import com.ning.billing.analytics.dao.BusinessAccountTagSqlDao;
 import com.ning.billing.analytics.dao.BusinessInvoiceFieldSqlDao;
@@ -40,6 +43,7 @@ import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionFieldSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionTagSqlDao;
 import com.ning.billing.analytics.dao.DefaultAnalyticsDao;
+import com.ning.billing.analytics.dao.DefaultAnalyticsSanityDao;
 
 import com.google.inject.AbstractModule;
 
@@ -67,9 +71,11 @@ public class AnalyticsModule extends AbstractModule {
         bind(AnalyticsListener.class).asEagerSingleton();
 
         bind(AnalyticsDao.class).to(DefaultAnalyticsDao.class).asEagerSingleton();
+        bind(AnalyticsSanityDao.class).to(DefaultAnalyticsSanityDao.class).asEagerSingleton();
         bind(AnalyticsService.class).to(DefaultAnalyticsService.class).asEagerSingleton();
 
         bind(DefaultAnalyticsUserApi.class).asEagerSingleton();
+        bind(AnalyticsSanityApi.class).to(DefaultAnalyticsSanityApi.class).asEagerSingleton();
         bind(AnalyticsUserApi.class).to(DefaultAnalyticsUserApi.class).asEagerSingleton();
     }
 }
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.sql.stg
new file mode 100644
index 0000000..4040115
--- /dev/null
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/AnalyticsSanitySqlDao.sql.stg
@@ -0,0 +1,301 @@
+group AnalyticsSanitySqlDao;
+
+CHECK_TENANT(prefix) ::= "<prefix>tenant_record_id = :tenantRecordId"
+AND_CHECK_TENANT(prefix) ::= "AND <CHECK_TENANT(prefix)>"
+
+checkBstMatchesSubscriptionEvents() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , sum(per_event_check) account_check_left
+    , count(*) account_check_right
+    from (
+        select
+          account_id
+        , account_key_check and app_id and date_type and slug per_event_check
+        from (
+            select
+              q.account_key
+            , q.account_id
+            , b_account_key = account_key account_key_check
+            , b_app_id = app_id app_id
+            , case
+                when b_event like 'CANCEL_%' then b_req_dt = req_dt
+                when b_event like 'SYSTEM_CANCEL_%' then b_req_dt = eff_dt
+                else b_req_dt = req_dt  and b_eff_dt = eff_dt
+              end date_type
+            , coalesce(b_slug = slug, 1) slug
+            from (
+                select
+                  bst.total_ordering record_id
+                , bst.account_key b_account_key
+                , bst.external_key b_app_id
+                , bst.event b_event
+                , bst.next_slug b_slug
+                , from_unixtime(bst.requested_timestamp / 1000) b_req_dt
+                , from_unixtime(bst.next_start_date / 1000) b_eff_dt
+                , a.external_key account_key
+                , a.id account_id
+                , b.external_key app_id
+                , s.id
+                , e.event_type
+                , e.user_type
+                , e.phase_name slug
+                , e.effective_date eff_dt
+                , e.requested_date req_dt
+                , from_unixtime(coalesce(bst.next_start_date, bst.requested_timestamp) / 1000) b_dt
+                from subscription_events e
+                join subscriptions s on e.subscription_id = s.id
+                join bundles b on s.bundle_id = b.id
+                join accounts a on b.account_id = a.id
+                join bst on bst.total_ordering = e.record_id
+                where
+                    e.is_active = 1
+                and e.user_type != 'MIGRATE_BILLING'
+                <AND_CHECK_TENANT("e.")>
+                <AND_CHECK_TENANT("s.")>
+                <AND_CHECK_TENANT("b.")>
+                <AND_CHECK_TENANT("a.")>
+                <AND_CHECK_TENANT("bst.")>
+                order by e.record_id asc
+            ) q
+        ) p
+    ) r group by (account_id)
+) s
+where account_check_left != account_check_right
+;
+>>
+
+checkBiiMatchesInvoiceItems() ::= <<
+select distinct
+  account_id
+from (
+    select
+      id
+    , account_id
+    , start_date_check and end_date_check and amount_check and currency_check and linked_item_id_check and slug_check per_item_check
+    from (
+        select
+          ii.id
+        , ii.account_id
+        , ii.start_date = bii.start_date start_date_check
+        , coalesce(ii.end_date, bii.end_date) = bii.end_date end_date_check
+        , ii.amount = bii.amount amount_check
+        , ii.currency = bii.currency currency_check
+        , coalesce(ii.linked_item_id = bii.linked_item_id, 1) linked_item_id_check
+        , ii.phase_name = bii.slug slug_check
+        from invoice_items ii
+        join bii on ii.id = bii.item_id
+        where <CHECK_TENANT("ii.")>
+        <AND_CHECK_TENANT("bii.")>
+    ) p
+) q where !per_item_check
+;
+>>
+
+checkBipMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      payment_id
+    , account_id
+    , amount_check and currency_check and payment_type_check and linked_invoice_payment_id_check and invoice_id_check total_check
+    from (
+        select
+          bip.payment_id
+        , a.id account_id
+        , bip.amount = ip.amount amount_check
+        , bip.currency = ip.currency currency_check
+        , bip.invoice_payment_type = ip.type payment_type_check
+        , bip.linked_invoice_payment_id = ip.linked_invoice_payment_id linked_invoice_payment_id_check
+        , bip.invoice_id = ip.invoice_id invoice_id_check
+        from bip
+        join invoice_payments ip on bip.payment_id = ip.id
+        join accounts a on a.record_id = ip.account_record_id
+        where <CHECK_TENANT("bip.")>
+        <AND_CHECK_TENANT("ip.")>
+        <AND_CHECK_TENANT("a.")>
+    ) p
+) q where !total_check
+;
+>>
+
+checkBinAmountPaidMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bin.invoice_id
+    , bin.account_id
+    , sum(ip.amount) = amount_paid amount_paid_check
+    from bin
+    join invoice_payments ip on bin.invoice_id = ip.invoice_id
+    where <CHECK_TENANT("bin.")>
+    <AND_CHECK_TENANT("ip.")>
+    group by ip.invoice_id, bin.account_id
+) p where !amount_paid_check
+;
+>>
+
+checkBinAmountChargedMatchesInvoicePayments() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bin.invoice_id
+    , bin.account_id
+    , sum(ip.amount) = amount_charged amount_charged_check
+    from bin
+    join invoice_payments ip on bin.invoice_id = ip.invoice_id
+    where <CHECK_TENANT("bin.")>
+    <AND_CHECK_TENANT("ip.")>
+    group by ip.invoice_id, bin.account_id
+) p where !amount_charged_check
+;
+>>
+
+checkBinBiiBalanceConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      invoice_id
+    , account_id
+    , balance = amount_charged + total_adj_amount + total_cba - amount_paid balance_check
+    from (
+        select
+          bin.invoice_id
+        , bin.account_id
+        , bin.amount_paid
+        , bin.amount_charged
+        , bin.amount_credited
+        , bin.balance
+        , coalesce(total_adj_amount, 0) total_adj_amount
+        , coalesce(total_cba, 0) total_cba
+        from bin
+        left join (
+          select
+            bii.invoice_id
+          , sum(amount) total_adj_amount
+          from bii
+          join bin on bin.invoice_id = bii.invoice_id
+          where bii.item_type in ('CREDIT_ADJ', 'REFUND_ADJ', 'ITEM_ADJ')
+          <AND_CHECK_TENANT("bii.")>
+          <AND_CHECK_TENANT("bin.")>
+          group by (bii.invoice_id)
+        ) p on bin.invoice_id = p.invoice_id
+        left join (
+          select
+            q.invoice_id
+          , total_cba
+          from (
+              select
+                bii.invoice_id
+              , sum(amount) total_cba
+              from bii
+              join bin on bin.invoice_id = bii.invoice_id
+              where bii.item_type in ('CBA_ADJ')
+              <AND_CHECK_TENANT("bii.")>
+              <AND_CHECK_TENANT("bin.")>
+              group by (bii.invoice_id)
+          ) q
+        ) r on r.invoice_id = bin.invoice_id
+        where <CHECK_TENANT("bin.")>
+    ) s
+) t where !balance_check
+;
+>>
+
+checkBinBiiAmountCreditedConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      bii.invoice_id
+    , bin.account_id
+    , sum(amount) = bin.amount_credited credit_check
+    from bii
+    join bin on bin.invoice_id = bii.invoice_id
+    where bii.item_type in ('CREDIT_ADJ')
+    <AND_CHECK_TENANT("bii.")>
+    <AND_CHECK_TENANT("bin.")>
+    group by bii.invoice_id, bin.account_id
+) p where !credit_check
+;
+>>
+
+checkBacBinBiiConsistency() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , total_invoice_balance_check and total_account_balance_check bac_check
+    from (
+        select
+          account_id
+        , total_invoice_balance_on_account = total_invoice_balance total_invoice_balance_check
+        , total_account_balance = total_invoice_balance_on_account - account_cba total_account_balance_check
+        from (
+            select
+              bac.account_id
+            , bac.total_invoice_balance total_invoice_balance_on_account
+            , sum(bin.balance) total_invoice_balance
+            , bac.balance total_account_balance
+            , coalesce(account_cba, 0) account_cba
+            from bac
+            -- some might not have cba items
+            left join (
+                select
+                  bin.account_id
+                , sum(bii.amount) account_cba
+                from bac
+                join bin on bin.account_id = bac.account_id
+                join bii on bii.invoice_id = bin.invoice_id
+                where bii.item_type = 'CBA_ADJ'
+                <AND_CHECK_TENANT("bac.")>
+                <AND_CHECK_TENANT("bin.")>
+                <AND_CHECK_TENANT("bii.")>
+                group by (bin.account_id)
+            ) p on bac.account_id = p.account_id
+            left join bin on bin.account_id = bac.account_id
+            where <CHECK_TENANT("bac.")>
+            <AND_CHECK_TENANT("bin.")>
+            group by bac.account_id, account_cba
+        ) q
+    ) r
+) s where !bac_check
+;
+>>
+
+checkBacTagsMatchesTags() ::= <<
+select distinct
+  account_id
+from (
+    select
+      account_id
+    , b_tag_name = tag_name tag_name_check
+    from (
+        select
+          bt.account_id account_id
+        , bt.name b_tag_name
+        , case
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000001' then 'AUTO_PAY_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000002' then 'AUTO_INVOICING_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000003' then 'OVERDUE_ENFORCEMENT_OFF'
+          when t.tag_definition_id = '00000000-0000-0000-0000-000000000003' then 'WRITTEN_OFF'
+          else tdef.name
+        end tag_name
+        from bac_tags bt
+        join tags t on t.object_id = bt.account_id
+        left join tag_definitions tdef on t.tag_definition_id = tdef.id
+        where t.object_type  = 'account'
+        <AND_CHECK_TENANT("bt.")>
+        <AND_CHECK_TENANT("t.")>
+        <AND_CHECK_TENANT("tdef.")>
+    ) p
+) q where ! tag_name_check;
+>>
diff --git a/api/src/main/java/com/ning/billing/analytics/api/sanity/AnalyticsSanityApi.java b/api/src/main/java/com/ning/billing/analytics/api/sanity/AnalyticsSanityApi.java
new file mode 100644
index 0000000..adf59c4
--- /dev/null
+++ b/api/src/main/java/com/ning/billing/analytics/api/sanity/AnalyticsSanityApi.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.analytics.api.sanity;
+
+import java.util.Collection;
+import java.util.UUID;
+
+import com.ning.billing.util.callcontext.TenantContext;
+
+public interface AnalyticsSanityApi {
+
+    /**
+     * @return the list of account ids not in sync with entitlement
+     */
+    public Collection<UUID> checkAnalyticsInSyncWithEntitlement(TenantContext context);
+
+    /**
+     * @return the list of account ids not in sync with invoice
+     */
+    public Collection<UUID> checkAnalyticsInSyncWithInvoice(TenantContext context);
+
+    /**
+     * @return the list of account ids not in sync with payment
+     */
+    public Collection<UUID> checkAnalyticsInSyncWithPayment(TenantContext context);
+
+    /**
+     * @return the list of account ids not in sync with the tag service
+     */
+    public Collection<UUID> checkAnalyticsInSyncWithTag(TenantContext context);
+
+    /**
+     * @return the list of account ids not self-consistent in Analytics
+     */
+    public Collection<UUID> checkAnalyticsConsistency(TenantContext context);
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AnalyticsSanityJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AnalyticsSanityJson.java
new file mode 100644
index 0000000..f2e4136
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AnalyticsSanityJson.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.jaxrs.json;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+
+public class AnalyticsSanityJson extends JsonBase {
+
+    private final List<String> checkEntitlement;
+    private final List<String> checkInvoice;
+    private final List<String> checkPayment;
+    private final List<String> checkTag;
+    private final List<String> checkConsistency;
+
+    @JsonCreator
+    public AnalyticsSanityJson(@JsonProperty("checkEntitlement") final List<String> checkEntitlement,
+                               @JsonProperty("checkInvoice") final List<String> checkInvoice,
+                               @JsonProperty("checkPayment") final List<String> checkPayment,
+                               @JsonProperty("checkTag") final List<String> checkTag,
+                               @JsonProperty("checkConsistency") final List<String> checkConsistency) {
+        this.checkEntitlement = checkEntitlement;
+        this.checkInvoice = checkInvoice;
+        this.checkPayment = checkPayment;
+        this.checkTag = checkTag;
+        this.checkConsistency = checkConsistency;
+    }
+
+    public AnalyticsSanityJson(final Collection<UUID> checkEntitlement,
+                               final Collection<UUID> checkInvoice,
+                               final Collection<UUID> checkPayment,
+                               final Collection<UUID> checkTag,
+                               final Collection<UUID> checkConsistency) {
+        this.checkEntitlement = ImmutableList.<String>copyOf(Collections2.transform(checkEntitlement, new Function<UUID, String>() {
+            @Override
+            public String apply(final UUID input) {
+                return input.toString();
+            }
+        }));
+        this.checkInvoice = ImmutableList.<String>copyOf(Collections2.transform(checkInvoice, new Function<UUID, String>() {
+            @Override
+            public String apply(final UUID input) {
+                return input.toString();
+            }
+        }));
+        this.checkPayment = ImmutableList.<String>copyOf(Collections2.transform(checkPayment, new Function<UUID, String>() {
+            @Override
+            public String apply(final UUID input) {
+                return input.toString();
+            }
+        }));
+        this.checkTag = ImmutableList.<String>copyOf(Collections2.transform(checkTag, new Function<UUID, String>() {
+            @Override
+            public String apply(final UUID input) {
+                return input.toString();
+            }
+        }));
+        this.checkConsistency = ImmutableList.<String>copyOf(Collections2.transform(checkConsistency, new Function<UUID, String>() {
+            @Override
+            public String apply(final UUID input) {
+                return input.toString();
+            }
+        }));
+    }
+
+    public List<String> getCheckEntitlement() {
+        return checkEntitlement;
+    }
+
+    public List<String> getCheckInvoice() {
+        return checkInvoice;
+    }
+
+    public List<String> getCheckPayment() {
+        return checkPayment;
+    }
+
+    public List<String> getCheckTag() {
+        return checkTag;
+    }
+
+    public List<String> getCheckConsistency() {
+        return checkConsistency;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("AnalyticsSanityJson");
+        sb.append("{checkEntitlement=").append(checkEntitlement);
+        sb.append(", checkInvoice=").append(checkInvoice);
+        sb.append(", checkPayment=").append(checkPayment);
+        sb.append(", checkTag=").append(checkTag);
+        sb.append(", checkConsistency=").append(checkConsistency);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final AnalyticsSanityJson json = (AnalyticsSanityJson) o;
+
+        if (checkConsistency != null ? !checkConsistency.equals(json.checkConsistency) : json.checkConsistency != null) {
+            return false;
+        }
+        if (checkEntitlement != null ? !checkEntitlement.equals(json.checkEntitlement) : json.checkEntitlement != null) {
+            return false;
+        }
+        if (checkInvoice != null ? !checkInvoice.equals(json.checkInvoice) : json.checkInvoice != null) {
+            return false;
+        }
+        if (checkPayment != null ? !checkPayment.equals(json.checkPayment) : json.checkPayment != null) {
+            return false;
+        }
+        if (checkTag != null ? !checkTag.equals(json.checkTag) : json.checkTag != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = checkEntitlement != null ? checkEntitlement.hashCode() : 0;
+        result = 31 * result + (checkInvoice != null ? checkInvoice.hashCode() : 0);
+        result = 31 * result + (checkPayment != null ? checkPayment.hashCode() : 0);
+        result = 31 * result + (checkTag != null ? checkTag.hashCode() : 0);
+        result = 31 * result + (checkConsistency != null ? checkConsistency.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
index 5dfe918..a258a19 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
@@ -16,6 +16,7 @@
 
 package com.ning.billing.jaxrs.resources;
 
+import java.util.Collection;
 import java.util.UUID;
 
 import javax.inject.Inject;
@@ -35,7 +36,9 @@ import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
 import com.ning.billing.analytics.api.BusinessSnapshot;
 import com.ning.billing.analytics.api.TimeSeriesData;
+import com.ning.billing.analytics.api.sanity.AnalyticsSanityApi;
 import com.ning.billing.analytics.api.user.AnalyticsUserApi;
+import com.ning.billing.jaxrs.json.AnalyticsSanityJson;
 import com.ning.billing.jaxrs.json.BusinessSnapshotJson;
 import com.ning.billing.jaxrs.json.TimeSeriesDataJson;
 import com.ning.billing.jaxrs.util.Context;
@@ -56,10 +59,12 @@ public class AnalyticsResource extends JaxRsResourceBase {
 
     private final AccountUserApi accountUserApi;
     private final AnalyticsUserApi analyticsUserApi;
+    private final AnalyticsSanityApi analyticsSanityApi;
 
     @Inject
     public AnalyticsResource(final AccountUserApi accountUserApi,
                              final AnalyticsUserApi analyticsUserApi,
+                             final AnalyticsSanityApi analyticsSanityApi,
                              final JaxrsUriBuilder uriBuilder,
                              final TagUserApi tagUserApi,
                              final CustomFieldUserApi customFieldUserApi,
@@ -68,6 +73,26 @@ public class AnalyticsResource extends JaxRsResourceBase {
         super(uriBuilder, tagUserApi, customFieldUserApi, auditUserApi, context);
         this.accountUserApi = accountUserApi;
         this.analyticsUserApi = analyticsUserApi;
+        this.analyticsSanityApi = analyticsSanityApi;
+    }
+
+    @GET
+    @Path("/sanity")
+    @Produces(APPLICATION_JSON)
+    public Response checkSanity(@javax.ws.rs.core.Context final HttpServletRequest request) {
+        final TenantContext tenantContext = context.createContext(request);
+        final Collection<UUID> checkEntitlement = analyticsSanityApi.checkAnalyticsInSyncWithEntitlement(tenantContext);
+        final Collection<UUID> checkInvoice = analyticsSanityApi.checkAnalyticsInSyncWithInvoice(tenantContext);
+        final Collection<UUID> checkPayment = analyticsSanityApi.checkAnalyticsInSyncWithPayment(tenantContext);
+        final Collection<UUID> checkTag = analyticsSanityApi.checkAnalyticsInSyncWithTag(tenantContext);
+        final Collection<UUID> checkConsistency = analyticsSanityApi.checkAnalyticsConsistency(tenantContext);
+
+        final AnalyticsSanityJson json = new AnalyticsSanityJson(checkEntitlement,
+                                                                 checkInvoice,
+                                                                 checkPayment,
+                                                                 checkTag,
+                                                                 checkConsistency);
+        return Response.status(Status.OK).entity(json).build();
     }
 
     @GET