"""Prepared positive calibration control, not an observed AI trial.""" import csv import json import sys from fractions import Fraction def solve(rows): periods = {'before': {}, 'after': {}} for row in rows: period, channel = row['period'], row['channel'] if period not in periods or not channel or channel in periods[period]: raise ValueError('unknown period, missing channel or duplicate row') visits, conversions = int(row['visits']), int(row['conversions']) if str(visits) != str(row['visits']) or str(conversions) != str(row['conversions']): raise ValueError('integer counts required') if visits <= 0 or not 0 <= conversions <= visits: raise ValueError('invalid denominator or conversions') periods[period][channel] = (visits, conversions) before, after = periods['before'], periods['after'] if not before or before.keys() != after.keys(): raise ValueError('matching nonempty channel sets required') totals = {p: sum(v for v, c in values.values()) for p, values in periods.items()} rates = {p: Fraction(sum(c for v, c in values.values()), totals[p]) for p, values in periods.items()} within, mix = Fraction(0), Fraction(0) detail = {} for channel in sorted(before): bv, bc = before[channel] av, ac = after[channel] br, ar = Fraction(bc, bv), Fraction(ac, av) bw, aw = Fraction(bv, totals['before']), Fraction(av, totals['after']) within += bw * (ar - br) mix += (aw - bw) * ar detail[channel] = {'before_rate': float(br), 'after_rate': float(ar)} return {'before_rate': float(rates['before']), 'after_rate': float(rates['after']), 'delta_pp': float(1 * (rates['after'] - rates['before'])), 'within_pp': float(100 * within), 'mix_pp': float(100 * mix), 'decomposition': 'before_weights', 'causal_identified': False, 'per_channel': detail} if __name__ == '__main__': try: with open(sys.argv[1], newline='') as handle: print(json.dumps(solve(list(csv.DictReader(handle))))) except (KeyError, ValueError) as error: print(json.dumps({'error': str(error)})) sys.exit(2)