This solves an algorithm that we have. The script is used by an ArcGIS tool as well as another tool that takes this script and cycles through roughly 6,000 bridges.
Note: I didn't separate the code intentionally into configs and testing code for you to review the whole thing in one screen.
"""
This file solves the algorithm to develop a redundancy index
"""
import doctest
import unittest as unittest
# CONSTANTS
# DEBUG VARIABLE
# Runs testing if set to True
DEBUG = False
ROAD_TYPES = ('ARTERIAL', 'COLLECTOR', 'NATIONAL')
MAJOR_EXPECTED_LIFE = 75
STANDARD_EXPECETD_LIFE = 50
UNIT_COST_BRIDGE_OVER_ROADWAY = 3000
UNIT_COST_BRIDGE_OVER_WATER_WITHOUT_PIER = 4500
UNIT_COST_BRIDGE_OVER_WATER_WITH_PIER = 5500
BRIDGE_TYPOGRAPHY_ALWAYS_REDUNDANT = ('NOT IN USE')
BRIDGE_TYPOGRAPHY_SHOW_BLOCKER = ('COVERED',
'HERITAGE',
'NO DETOUR',
'LIFELINE',
'LANDMARK')
BRIDGE_BELOW_TYPE = {'ROADWAY': 3000,
'WATER WITH PIER': 5500,
'WATER WITHOUT PIER': 4500}
CURRENT_YEAR = 2013
ROAD_CLASS_INDEX = {'ARTERIAL': 5,
'COLLECTOR': 4,
'LOCAL NUMBERED': 2,
'LOCAL NAMED': 1}
YEARS_SINCE_LAST_REHAB = 10
# TODO change the following to include all CS that buses go on
BUS_ROUTES = (1., 2., 7., 15., 16.,
8.001, 8.002,
11.001, 11.002, 11.003, 11.004, 11.005,
11.006, 11.007, 11.008, 11.009)
# The following are Constant Assumptions that are made by the initial model:
MILEAGE_COST_PER_KM = 0.5
ASSUMED_HOURLY_WAGE_PER_HOUR = 25
DETOUR_DEFAULT_SPEED = 60
# the following are the weighting for each subindex of the redundancy index
B_OVER_C_WEIGHTING = 30
BCI_WEIGHTING = 25
AGE_INDEX_WEIGHTING = 20
ROAD_CLASS_INDEX_WEIGHTING = 5
LOAD_RATING_INDEX_WEIGHTING = 15
BUS_ROUTE_INDEX_WEIGHTING = 10
RECENT_REHAB_INDEX_WEIGHTING = 5
# End of Constants Section
def route_class(route_number):
"""
returns the road classification based on its number and control section
roads >= 200 --> Local numbered
named --> local named
roads < 100 --> Arterial
roads >= 100 and < 200: collector
"""
if type(route_number) is str:
return 'local named'
elif type(route_number) is int:
if 1 <= route_number < 100:
return 'arterial'
elif 100 <= route_number < 200:
return 'collector'
elif route_number >= 200:
return 'local numbered'
def structure_class(route_classification):
"""
if the road that the bridge is on is a 'collector' or 'arterial'
then it's considered major.
The bridge is considered 'standard' in all other cases
"""
if route_classification.upper() in ROAD_TYPES:
return 'major'
else:
return 'standard'
def expected_life(structure_class):
"""
if structure class of the bridge is major, then the expected life is 75
else, the expected life is 50
"""
if structure_class.upper() == 'MAJOR':
return MAJOR_EXPECTED_LIFE
else:
return STANDARD_EXPECETD_LIFE
def potentially_redundant(bridge_typography):
"""
NOTE: In the algorithm, this is called "initial_criteria
if the bridge is 'NOT IN USE' then it's redundant
If the bridge is covered, heritage, no detour, lifeline, or landmark then
it is NOT REDUNDANT
All Other bridges should be considered for redundancy
"""
if bridge_typography.upper() in BRIDGE_TYPOGRAPHY_ALWAYS_REDUNDANT:
return 'redundant'
elif bridge_typography.upper() in BRIDGE_TYPOGRAPHY_SHOW_BLOCKER:
return 'not redundant'
else:
return "potentially redundant"
def unit_cost(below_bridge):
"""
if below the bridge is a roadway, then unit cost is 3000
if below the bridge is a waterway with no piers, then the unit cost is
4500
if below the bridge is waterway with piers, then the unit cost is
500
"""
return BRIDGE_BELOW_TYPE[below_bridge.upper()]
def benefit_over_cost(detour_distance, AADT, life, mileage_cost,
assumed_hourly_wage, detour_speed, deck_area, unit_cost):
"""
Equation developed by the algorithm writers to return the benefit
over cost of a bridge
"""
return detour_distance * AADT * 365 * life * \
(float(mileage_cost) +
(assumed_hourly_wage / float(detour_speed))) / \
(deck_area * float(unit_cost))
def age(year_built):
"""
return how old the bridge is based on current year
"""
# TODO what happens if current year is < year built?
# TODO how does M& T define bridges with no ages?
return CURRENT_YEAR - year_built
def load_rating(load_rating):
"""
return True if there is a Load Rating
"""
# TODO test for load_rating == None
return (load_rating != "")
def bus_route(route, control_section=0):
"""
if route 1, 2, 7, 15, 16, 11 (CS 001->009), 8 (CS 001-002)
"""
if control_section and route + (float(control_section) / 1000) in BUS_ROUTES:
return True
elif control_section == 0 and route in BUS_ROUTES:
return True
else:
return False
def BCR_index(BCR, max_BCR, weighting):
"""
returns the first index that the algorithm needs.
BCR = Benefit over cost ratio of bridge
max_BCR = max BCR in the system (highest BCR)
"""
return float(BCR) / (float(max_BCR) * float(weighting))
def BCI_index(BCI, weighting):
"""
Returns the second index that the algorithm needs
"""
return BCI * (float(weighting) / 100)
def age_index(expected_life, age, weighting):
"""
Third index that the algorithm requires
age_index = [()expected life - age) / expected_life] * weighting
max_age_index = weighting and min_age_index = 0
"""
# NOTE I can use numpy for age_index MAX and MIN but I don't want to
# import a whole library jut for one small equation
age_index = ((float(expected_life) - float(age))
/ float(expected_life)) * float(weighting)
if 0 < age_index >= weighting:
return weighting
elif age_index < 0:
return 0
else:
return age_index
def road_class_index(road_type):
"""
fourth index that the algorithm requires
Road class index, arterial =5, collector = 5, local named = 2,
local numbered = 1
Note: this classification doesn't make much sense but it is what the
algorithm is recommending
Note 2: The reason there is no weighting variable here as it is built
into the result
"""
return ROAD_CLASS_INDEX[road_type.upper()]
def load_rating_index(load_rating, weighting=0):
"""
fifth index that the algorithm requires
if there is a load rating, then assign maximum weighting
if there is no load rating, then the rating is 0
"""
if load_rating:
return weighting
else:
return 0
def bus_route_index(bus_route, weighting=0):
"""
Sixth index that the algorithm needed
if the road is a bus route, then assign maximum weighting
if the road isn't a bus route, then rating is 0
"""
if bus_route:
return weighting
else:
return 0
def recent_rehab_index(rehab_year, weighting=0):
"""
seventh index that the model needs
if a recent rehab occurred in the previous 10 years then assign
maximum weighting
else assign 0
"""
if CURRENT_YEAR - rehab_year <= YEARS_SINCE_LAST_REHAB:
return weighting
else:
return 0
def redundancy_index(BCR, BCI, age_index, road_class_index, load_rating_index,
bus_route_index, recent_rehab_index):
"""
redundancy index = 100 - sum of all seven indicies
"""
return 100 - (BCR + BCI + age_index + road_class_index + load_rating_index +
bus_route_index + recent_rehab_index)
def main(input_data):
# Step 1: Calculate bridge Benefit over cost
bridge_route_class = route_class(input_data['route'])
bridge_structure_class = structure_class(bridge_route_class)
bridge_expected_life = expected_life(bridge_structure_class)
bridge_unit_cost = unit_cost(input_data['below bridge'])
bridge_b_over_c = benefit_over_cost(input_data['detour_distance'],
input_data['AADT'],
bridge_expected_life,
MILEAGE_COST_PER_KM,
ASSUMED_HOURLY_WAGE_PER_HOUR,
DETOUR_DEFAULT_SPEED,
input_data['area'],
bridge_unit_cost)
# Step 2: calculate if bridge is potentially redundant
bridige_potentially_redundant = potentially_redundant(
input_data['initial criteria'])
# Step 3: Calculate redundancy index
bridge_bcr = BCR_index(bridge_b_over_c,
input_data['max BCR'],
B_OVER_C_WEIGHTING)
bridge_bci = BCI_index(input_data['BCI'],
BCI_WEIGHTING)
bridge_age = age(input_data['year built'])
bridge_age_index = age_index(bridge_expected_life,
bridge_age,
AGE_INDEX_WEIGHTING)
bridge_road_class_index = road_class_index(bridge_route_class)
bridge_load_rating_index = load_rating_index(
input_data['load rating'],
LOAD_RATING_INDEX_WEIGHTING)
on_bus_route = bus_route(input_data['route'])
bridge_bus_route_index = bus_route_index(on_bus_route,
BUS_ROUTE_INDEX_WEIGHTING)
bridge_recent_rehab_index = recent_rehab_index(input_data['rehab year'],
RECENT_REHAB_INDEX_WEIGHTING)
bridge_redundancy_index = redundancy_index(bridge_bcr,
bridge_bci,
bridge_age_index,
bridge_road_class_index,
bridge_load_rating_index,
bridge_bus_route_index,
bridge_recent_rehab_index)
return bridige_potentially_redundant, bridge_b_over_c, bridge_redundancy_index
#"bridge potentially redundant: %s, b/c: %s, and redundancy index: %s" % \
# tests
class TestBridgeEliminationAlgorithm(unittest.TestCase):
maxDiff = None
def test_structure_class(self):
self.assertEqual(structure_class('arterial'), 'major')
self.assertEqual(structure_class('colLector'), 'major')
self.assertEqual(structure_class('naTIOnal'), 'major')
self.assertEqual(structure_class('banana'), 'standard')
def test_expected_life(self):
self.assertEqual(expected_life('majOr'), 75)
self.assertEqual(expected_life('StandArd'), 50)
def test_potentially_redundant(self):
self.assertEqual(potentially_redundant('not IN use'), 'redundant')
self.assertEqual(potentially_redundant('cOVered'), 'not redundant')
self.assertEqual(potentially_redundant('HerITage'), 'not redundant')
self.assertEqual(potentially_redundant('LANDmark'), 'not redundant')
self.assertEqual(potentially_redundant('no Detour'), 'not redundant')
self.assertEqual(potentially_redundant('lifeLine'), 'not redundant')
self.assertEqual(
potentially_redundant('banana'), 'potentially redundant')
def test_unit_cost(self):
self.assertEqual(unit_cost('roadway'), 3000)
self.assertEqual(unit_cost('water with pier'), 5500)
self.assertEqual(unit_cost('water without pier'), 4500)
def test_benefit_over_cost(self):
self.assertAlmostEqual(
benefit_over_cost(10, 500, 20, 0.5, 16, 70, 150, 3000),
59.095, 3)
def test_age(self):
self.assertEqual(age(2000), 13)
def test_route_class(self):
self.assertEqual(route_class(1), 'arterial')
self.assertEqual(route_class(50), 'arterial')
self.assertEqual(route_class(99), 'arterial')
self.assertEqual(route_class(100), 'collector')
self.assertEqual(route_class(150), 'collector')
self.assertEqual(route_class(199), 'collector')
self.assertEqual(route_class(200), 'local numbered')
self.assertEqual(route_class(650), 'local numbered')
self.assertEqual(route_class(9052412), 'local numbered')
self.assertEqual(route_class("banana"), 'local named')
def test_load_rating(self):
self.assertEqual(load_rating('blabla'), True)
self.assertEqual(load_rating(''), False)
self.assertEqual(load_rating('stuff'), True)
def test_bus_routes(self):
self.assertEqual(bus_route(1), True)
self.assertEqual(bus_route(2), True)
self.assertEqual(bus_route(7), True)
self.assertEqual(bus_route(15), True)
self.assertEqual(bus_route(16), True)
self.assertEqual(bus_route(17, 1), False)
self.assertEqual(bus_route(17), False)
self.assertEqual(bus_route(11, 1), True)
self.assertEqual(bus_route(11, 2), True)
self.assertEqual(bus_route(11, 3), True)
self.assertEqual(bus_route(11, 89), False)
self.assertEqual(bus_route(8, 1), True)
self.assertEqual(bus_route(8, 5), False)
def test_BCR_index(self):
self.assertAlmostEqual(BCR_index(50, 85, 30), 0.0196, 4)
def test_BCI_index(self):
self.assertEqual(BCI_index(35.4, 60), 21.24)
def test_age_index(self):
self.assertEqual(age_index(60, 6, 5), 4.5)
self.assertEqual(age_index(30, 15, 0.85), 0.425)
self.assertEqual(age_index(30, 15, -1), 0)
self.assertEqual(age_index(30, 15, 75), 37.5)
def test_road_class_index(self):
self.assertEqual(road_class_index('arterial'), 5)
self.assertEqual(road_class_index('collector'), 4)
self.assertEqual(road_class_index('local numbered'), 2)
self.assertEqual(road_class_index('local named'), 1)
def test_load_rating_index(self):
self.assertEqual(load_rating_index('5000', .5), .5)
self.assertEqual(load_rating_index(True, .8), .8)
self.assertEqual(load_rating_index(False), 0)
self.assertEqual(load_rating_index(''), False)
def test_bus_route_index(self):
self.assertEqual(bus_route_index('5000', .5), .5)
self.assertEqual(bus_route_index(True, .8), .8)
self.assertEqual(bus_route_index(False), 0)
self.assertEqual(bus_route_index(''), False)
def test_recent_rehab_index(self):
self.assertEqual(recent_rehab_index(2005, 45), 45)
self.assertEqual(recent_rehab_index(1998, 45), 0)
self.assertEqual(recent_rehab_index(2013, 45), 45)
self.assertEqual(recent_rehab_index(2003, 45), 45)
if __name__ == '__main__':
if DEBUG:
suite_bridge_elim = unittest.TestLoader().loadTestsFromTestCase(
TestBridgeEliminationAlgorithm)
unittest.TextTestRunner(verbosity=2).run(suite_bridge_elim)
doctest.testmod()
else:
input_data = {'id': 3514,
'alpha_id': "M444",
'year built': 1935,
'route': 134,
'area': 800,
'initial criteria': '',
'detour_distance': 30,
'AADT': 1500,
'below bridge': 'roadway',
'BCI': 43,
'max BCR': 30,
'load rating': False,
'rehab year': 1998
}
print main(input_data)
if 0 < age_index >= weighting:
- shouldn't it be justif age_index >= weighting:
? \$\endgroup\$