def testRangeFunction(self):
        # create the scoring function for checking if a value passes a range
        rangeFunction1 = ScoreFunction(
            calculator='redistricting.calculators.Range', name='RangeFn')
        rangeFunction1.save()

        # create the arguments
        ScoreArgument(function=rangeFunction1,
                      argument='value',
                      value='2',
                      type='literal').save()
        ScoreArgument(function=rangeFunction1,
                      argument='min',
                      value='1',
                      type='literal').save()
        ScoreArgument(function=rangeFunction1,
                      argument='max',
                      value='3',
                      type='literal').save()

        # test raw value
        score = rangeFunction1.score(self.district1)
        self.assertEqual(1, score['value'], '2 is between 1 and 3')

        # HTML
        score = rangeFunction1.score(self.district1, 'html')
        self.assertEqual("<span>1</span>", score,
                         'Range HTML was incorrect: ' + score)

        # JSON
        score = rangeFunction1.score(self.district1, 'json')
        self.assertEqual('{"result": 1}', score,
                         'Range JSON was incorrect: ' + score)
    def testSumFunction(self):
        """
        Test the sum scoring function
        """
        # create the scoring function for summing three parameters
        sumThreeFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumThreeFn')
        sumThreeFunction.save()

        # create the arguments
        ScoreArgument(function=sumThreeFunction,
                      argument='value1',
                      value='0',
                      type='literal').save()
        ScoreArgument(function=sumThreeFunction,
                      argument='value2',
                      value='1',
                      type='literal').save()
        ScoreArgument(function=sumThreeFunction,
                      argument='value3',
                      value='2',
                      type='literal').save()

        # test raw value
        score = sumThreeFunction.score(self.district1)
        self.assertEqual(3, score['value'],
                         'sumThree was incorrect: %d' % score['value'])

        # HTML -- also make sure mixed case format works
        score = sumThreeFunction.score(self.district1, 'HtmL')
        self.assertEqual('<span>3</span>', score,
                         'sumThree was incorrect: %s' % score)

        # JSON -- also make sure uppercase format works
        score = sumThreeFunction.score(self.district1, 'JSON')
        self.assertEqual('{"result": 3.0}', score,
                         'sumThree was incorrect: %s' % score)

        # create the scoring function for summing a literal and a subject
        sumMixedFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumMixedFn')
        sumMixedFunction.save()

        # create the arguments
        ScoreArgument(function=sumMixedFunction,
                      argument='value1',
                      value=self.subject1.name,
                      type='subject').save()
        ScoreArgument(function=sumMixedFunction,
                      argument='value2',
                      value='5.2',
                      type='literal').save()

        # test raw value
        score = sumMixedFunction.score(self.district1)
        self.assertEqual(Decimal('11.2'), score['value'],
                         'sumMixed was incorrect: %d' % score['value'])
    def testNestedSumPlanFunction(self):
        """
        Test the nested sum scoring function on a plan level
        """
        # create the scoring function for summing the districts in a plan
        sumPlanFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumPlanFn',
            is_planscore=True)
        sumPlanFunction.save()
        ScoreArgument(function=sumPlanFunction,
                      argument='value1',
                      value='1',
                      type='literal').save()

        # find the number of districts in the plan in an alternate fashion
        num_districts = len(
            self.plan.get_districts_at_version(self.plan.version,
                                               include_geom=False))

        # ensure the sumPlanFunction works correctly
        score = sumPlanFunction.score(self.plan)
        self.assertEqual(
            num_districts, score['value'],
            'sumPlanFunction was incorrect. (e:%d, a:%d)' % (
                num_districts,
                score['value'],
            ))

        # create the scoring function for summing the sum of the districts in a
        # plan
        sumSumPlanFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumSumPlanFn',
            is_planscore=True)
        sumSumPlanFunction.save()
        ScoreArgument(function=sumSumPlanFunction,
                      argument='value1',
                      value=sumPlanFunction.name,
                      type='score').save()

        # test nested sum
        score = sumSumPlanFunction.score(self.plan)
        self.assertEqual(
            num_districts**2, score['value'],
            'sumSumPlanFunction was incorrect: %d' % score['value'])

        # test a list of plans
        score = sumSumPlanFunction.score([self.plan, self.plan])
        self.assertEqual(
            num_districts**2, score[0]['value'],
            'sumSumPlanFunction was incorrect for first plan: %d' %
            score[0]['value'])
        self.assertEqual(
            num_districts**2, score[1]['value'],
            'sumSumPlanFunction was incorrect for second plan: %d' %
            score[1]['value'])
    def testThresholdFunction(self):
        # create the scoring function for checking if a value passes a
        # threshold
        thresholdFunction1 = ScoreFunction(
            calculator='redistricting.calculators.Threshold',
            name='ThresholdFn1')
        thresholdFunction1.save()

        # create the arguments
        ScoreArgument(function=thresholdFunction1,
                      argument='value',
                      value='1',
                      type='literal').save()
        ScoreArgument(function=thresholdFunction1,
                      argument='threshold',
                      value='2',
                      type='literal').save()

        # test raw value
        score = thresholdFunction1.score(self.district1)
        self.assertEqual(False, score['value'], '1 is not greater than 2')

        # create a new scoring function to test the inverse
        thresholdFunction2 = ScoreFunction(
            calculator='redistricting.calculators.Threshold',
            name='ThresholdFn2')
        thresholdFunction2.save()

        # create the arguments
        ScoreArgument(function=thresholdFunction2,
                      argument='value',
                      value='2',
                      type='literal').save()
        ScoreArgument(function=thresholdFunction2,
                      argument='threshold',
                      value='1',
                      type='literal').save()

        # test raw value
        score = thresholdFunction2.score(self.district1)
        self.assertEqual(1, score['value'], '2 is greater than 1')

        # HTML
        score = thresholdFunction2.score(self.district1, 'html')
        self.assertEqual("<span>1</span>", score,
                         'Threshold HTML was incorrect: ' + score)

        # JSON
        score = thresholdFunction2.score(self.district1, 'json')
        self.assertEqual('{"result": 1}', score,
                         'Threshold JSON was incorrect: ' + score)
    def testSumPlanFunction(self):
        """
        Test the sum scoring function on a plan level
        """
        # create the scoring function for summing the districts in a plan
        sumPlanFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumPlanFn',
            is_planscore=True)
        sumPlanFunction.save()

        # create the arguments
        ScoreArgument(function=sumPlanFunction,
                      argument='value1',
                      value='1',
                      type='literal').save()

        # test raw value
        num_districts = len(
            self.plan.get_districts_at_version(self.plan.version,
                                               include_geom=False))
        score = sumPlanFunction.score(self.plan)
        self.assertEqual(
            num_districts, score['value'],
            'sumPlanFunction was incorrect. (e:%d, a:%d)' %
            (num_districts, score['value']))

        # test a list of plans
        score = sumPlanFunction.score([self.plan, self.plan])
        self.assertEqual(
            num_districts, score[0]['value'],
            'sumPlanFunction was incorrect for first plan: %d' %
            score[0]['value'])
        self.assertEqual(
            num_districts, score[1]['value'],
            'sumPlanFunction was incorrect for second plan: %d' %
            score[1]['value'])
    def test_splitcounter_display(self):
        # Create a plan with two districts - one crosses both 5 and 8
        p1 = self.plan
        d1a_id = 1
        dist1ids = self.geounits[20:23] + self.geounits[29:32] + \
            self.geounits[38:41] + self.geounits[47:50] + \
            self.geounits[56:59]
        dist1ids = map(lambda x: str(x.id), dist1ids)
        p1.add_geounits(d1a_id, dist1ids, self.geolevel.id, p1.version)

        # the other is entirely within 5
        d2a_id = 5
        dist2ids = [self.geounits[32], self.geounits[41], self.geounits[50]]
        dist2ids = map(lambda x: str(x.id), dist2ids)
        p1.add_geounits(d2a_id, dist2ids, self.geolevel.id, p1.version)

        # Get a ScoreDisplay and components to render
        display = ScoreDisplay.objects.get(title='TestScoreDisplay')
        display.is_page = False
        display.save()

        panel = ScorePanel(title="Splits Report",
                           type="plan",
                           template="sp_template1.html",
                           cssclass="split_panel")
        function = ScoreFunction(
            calculator="redistricting.calculators.SplitCounter",
            name="splits_test",
            is_planscore=True)
        geolevel = self.plan.legislative_body.get_geolevels()[0]
        arg1 = ScoreArgument(argument="boundary_id",
                             value="geolevel.%d" % geolevel.id,
                             type="literal")

        components = [(panel, [(function, arg1)])]

        expected_result = (
            '%s:[u\'<div class="split_report"><div>Total '
            'districts which split a biggest level short label: 2</div>'
            '<div>Total number of splits: 7</div>'
            '<div class="table_container"><table class="report"><thead><tr>'
            '<th>Testplan</th><th>Biggest level short label</th></tr></thead>'
            '<tbody><tr><td>District 1</td><td>Unit 1-0</td></tr><tr>'
            '<td>District 1</td><td>Unit 1-1</td></tr><tr><td>District 1</td>'
            '<td>Unit 1-3</td></tr><tr><td>District 1</td><td>Unit 1-4</td>'
            '</tr><tr><td>District 1</td><td>Unit 1-6</td></tr><tr>'
            '<td>District 1</td><td>Unit 1-7</td></tr><tr><td>District 5</td>'
            '<td>Unit 1-4</td></tr></tbody></table></div></div>\']') % p1.name

        tplfile = settings.TEMPLATES[0]['DIRS'][0] + '/' + panel.template
        template = open(tplfile, 'w')
        template.write(
            '{% for planscore in planscores %}{{planscore.plan.name}}:' +
            '{{ planscore.score|safe }}{% endfor %}')
        template.close()

        # Check the result
        plan_result = display.render(p1, components=components)

        self.assertEqual(
            expected_result, plan_result,
            "Didn't get expected result when rendering " +
            "SplitCounter:\ne:%s\na:%s" % (expected_result, plan_result))

        os.remove(tplfile)
    def test_scoredisplay_with_overrides(self):
        # Get a ScoreDisplay
        display = ScoreDisplay.objects.get(title='TestScoreDisplay')
        display.is_page = False

        # Make up a ScorePanel - don't save it
        panel = ScorePanel(title="My Fresh Panel",
                           type="district",
                           template="sp_template2.html")
        # Create two functions, one with an arg and one without
        function = ScoreFunction(
            calculator="redistricting.calculators.SumValues",
            name="My Fresh Calc",
            is_planscore=False)

        arg1 = ScoreArgument(argument="value1", value="5", type="literal")
        arg2 = ScoreArgument(argument="value2",
                             value="TestSubject",
                             type="subject")

        tplfile = settings.TEMPLATES[0]['DIRS'][0] + '/' + panel.template
        template = open(tplfile, 'w')
        template.write('{% for dscore in districtscores %}' +
                       '{{dscore.district.long_label }}:' +
                       '{% for score in dscore.scores %}' +
                       '{{ score.score|safe }}{% endfor %}{% endfor %}')
        template.close()

        # Render the ScoreDisplay
        components = [(panel, [(function, arg1, arg2)])]
        district_result = display.render([self.district1],
                                         components=components)
        expected = 'District 1:<span>5</span>'
        self.assertEqual(
            expected, district_result,
            'Didn\'t get expected result when overriding district\'s display '
            + '(e:"%s", a:"%s")' % (expected, district_result))

        os.remove(tplfile)

        # Add some districts to our plan
        geolevelid = self.geolevel.id
        geounits = self.geounits

        dist1ids = geounits[0:3] + geounits[9:12]
        dist2ids = geounits[6:9] + geounits[15:18]
        dist1ids = map(lambda x: str(x.id), dist1ids)
        dist2ids = map(lambda x: str(x.id), dist2ids)

        self.plan.add_geounits(self.district1.district_id, dist1ids,
                               geolevelid, self.plan.version)
        self.plan.add_geounits(self.district2.district_id, dist2ids,
                               geolevelid, self.plan.version)

        # Set up the elements to work for a plan
        panel.type = 'plan'
        panel.template = 'sp_template1.html'
        function.is_planscore = True
        components = [(panel, [(function, arg1, arg2)])]

        tplfile = settings.TEMPLATES[0]['DIRS'][0] + '/' + panel.template
        template = open(tplfile, 'w')
        template.write(
            '{% for planscore in planscores %}{{planscore.plan.name}}:' +
            '{{ planscore.score|safe }}{% endfor %}')
        template.close()

        # Check the result
        plan_result = display.render(self.plan, components=components)
        expected = "testPlan:[u'<span>24</span>']"
        self.assertEqual(
            expected, plan_result,
            'Didn\'t get expected result when overriding plan\'s display' +
            ' (e: "%s", a:"%s")' % (expected, plan_result))

        os.remove(tplfile)
    def testPlanScoreNestedWithDistrictScore(self):
        """
        Test the case where a ScoreFunction of type 'plan' has an argument
        that is a ScoreFunction of type 'district', in which case, the argument
        ScoreFunction needs to be evaluated over all districts in the list of
        plans
        """
        # create the district scoring function for getting subject1
        districtSubjectFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='GetSubjectFn')
        districtSubjectFunction.save()

        ScoreArgument(function=districtSubjectFunction,
                      argument='value1',
                      value=self.subject1.name,
                      type='subject').save()

        # create the plan scoring function for summing values
        planSumFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='PlanSumFn',
            is_planscore=True)
        planSumFunction.save()
        ScoreArgument(function=planSumFunction,
                      value=districtSubjectFunction.name,
                      type='score').save()

        # subject values are 6, 9, and 0; so the total should be 15
        score = planSumFunction.score(self.plan)
        self.assertEqual(
            9, score['value'],
            'planSumFunction was incorrect: (e:9, a:%d)' % score['value'])

        # test a list of plans
        score = planSumFunction.score([self.plan, self.plan])
        self.assertEqual(
            9, score[0]['value'],
            'planSumFunction was incorrect for first plan: %d' %
            score[0]['value'])
        self.assertEqual(
            9, score[1]['value'],
            'planSumFunction was incorrect for second plan: %d' %
            score[1]['value'])

        # test with multiple arguments
        districtSubjectFunction2 = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='GetSubjectFn2')
        districtSubjectFunction2.save()
        ScoreArgument(function=districtSubjectFunction2,
                      argument='value1',
                      value=self.subject1.name,
                      type='subject').save()
        ScoreArgument(function=districtSubjectFunction2,
                      argument='value2',
                      value=self.subject1.name,
                      type='subject').save()

        planSumFunction2 = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='PlanSumFn2',
            is_planscore=True)
        planSumFunction2.save()
        ScoreArgument(function=planSumFunction2,
                      value=districtSubjectFunction2.name,
                      type='score').save()

        # should be twice as much
        score = planSumFunction2.score(self.plan)
        self.assertEqual(18, score['value'],
                         'planSumFunction was incorrect: %d' % score['value'])

        # test with adding another argument to the plan function, should
        # double again
        ScoreArgument(function=planSumFunction2,
                      value=districtSubjectFunction2.name,
                      type='score').save()
        score = planSumFunction2.score(self.plan)
        self.assertEqual(36, score['value'],
                         'planSumFunction was incorrect: %d' % score['value'])
    def testNestedSumFunction(self):
        """
        Test a sum scoring function that references a sum scoring function
        """
        # create the scoring function for summing two literals
        sumTwoLiteralsFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumTwoLiteralsFn')
        sumTwoLiteralsFunction.save()
        ScoreArgument(function=sumTwoLiteralsFunction,
                      argument='value1',
                      value='5',
                      type='literal').save()
        ScoreArgument(function=sumTwoLiteralsFunction,
                      argument='value2',
                      value='7',
                      type='literal').save()

        # create the scoring function for summing a literal and a score
        sumLiteralAndScoreFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumLiteralAndScoreFn')
        sumLiteralAndScoreFunction.save()

        # first argument is just a literal
        ScoreArgument(function=sumLiteralAndScoreFunction,
                      argument='value1',
                      value='2',
                      type='literal').save()

        # second argument is a score function
        ScoreArgument(function=sumLiteralAndScoreFunction,
                      argument='value2',
                      value=sumTwoLiteralsFunction.name,
                      type='score').save()

        # test nested sum
        score = sumLiteralAndScoreFunction.score(self.district1)
        self.assertEqual(
            14, score['value'],
            'sumLiteralAndScoreFunction was incorrect: %d' % score['value'])

        # sum two of these nested sums
        sumTwoNestedSumsFunction = ScoreFunction(
            calculator='redistricting.calculators.SumValues',
            name='SumTwoNestedSumsFn')
        sumTwoNestedSumsFunction.save()
        ScoreArgument(function=sumTwoNestedSumsFunction,
                      argument='value1',
                      value=sumLiteralAndScoreFunction.name,
                      type='score').save()
        ScoreArgument(function=sumTwoNestedSumsFunction,
                      argument='value2',
                      value=sumLiteralAndScoreFunction.name,
                      type='score').save()
        score = sumTwoNestedSumsFunction.score(self.district1)
        self.assertEqual(
            28, score['value'],
            'sumTwoNestedSumsFunction was incorrect: %d' % score['value'])

        # test a list of districts
        score = sumTwoNestedSumsFunction.score(
            [self.district1, self.district1])
        self.assertEqual(
            28, score[0]['value'],
            'sumTwoNestedSumsFunction was incorrect for first district: %d' %
            score[0]['value'])
        self.assertEqual(
            28, score[1]['value'],
            'sumTwoNestedSumsFunction was incorrect for second district: %d' %
            score[1]['value'])