Skip to content

Commit c6ea4f9

Browse files
committed
fix: merge conflicts
2 parents 2624899 + 86ba2c3 commit c6ea4f9

File tree

6 files changed

+234
-26
lines changed

6 files changed

+234
-26
lines changed

.coveragerc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
omit = app.py, test/utils/*

school_center.py

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ def haversine_distance(lat1, lon1, lat2, lon2):
4040
return distance
4141

4242

43-
def centers_within_distance(school: Dict[str, str], centers: Dict[str, str], distance_threshold: float) -> List[Dict[str, any]]:
43+
def centers_within_distance(school: Dict[str, str], centers: Dict[str, str], distance_threshold: float, relax_threshold: bool) -> List[Dict[str, any]]:
4444
"""
4545
Return List of centers that are within given distance from school.
46-
If there are no centers within given distance return one that is closest
46+
relax_threshold: If there are no centers within given distance return one that is closest
4747
Returned params :
4848
{'cscode', 'name', 'address', 'capacity', 'lat', 'long', 'distance_km'}
4949
@@ -60,33 +60,35 @@ def center_to_dict(c, distance):
6060
def sort_key(c):
6161
# intent: sort by preference score DESC then by distance_km ASC
6262
# leaky abstraction - sorted requires a single numeric value for each element
63-
return c['distance_km'] * random.uniform(1, 5) - get_pref(school['scode'], c['cscode'])*100
64-
63+
return c['distance_km'] * random.uniform(1, 5) - get_pref(school['scode'], c['cscode']) * 100
64+
6565
school_lat = school.get('lat')
6666
school_long = school.get('long')
6767
if len(school_lat) == 0 or len(school_long) == 0:
6868
return []
6969

70-
within_distance = []
71-
nearest_distance = None
72-
nearest_center = None
70+
qualifying_centers = []
71+
# nearest_distance = None
72+
# nearest_center = None
7373
for c in centers:
74+
if school['scode'] == c['cscode'] \
75+
or is_allocated(c['cscode'], s['scode']) \
76+
or get_pref(school['scode'], c['cscode']) <= PREF_CUTOFF:
77+
continue
7478
distance = haversine_distance(float(school_lat), float(
7579
school_long), float(c.get('lat')), float(c.get('long')))
76-
if school['scode'] == c['cscode']:
77-
continue
78-
if nearest_center is None or distance < nearest_distance:
79-
nearest_center = c
80-
nearest_distance = distance
81-
82-
if distance <= distance_threshold and get_pref(school['scode'], c['cscode']) > PREF_CUTOFF:
83-
within_distance.append(center_to_dict(c, distance))
80+
# if nearest_center is None or distance < nearest_distance:
81+
# nearest_center = c
82+
# nearest_distance = distance
83+
qualifying_centers.append(center_to_dict(c, distance))
8484

85+
within_distance = [ c for c in qualifying_centers if c['distance_km'] <= distance_threshold ]
8586
if len(within_distance) > 0:
86-
return sorted(within_distance, key=sort_key)
87-
else: # if there are no centers within given threshold, return one that is closest
88-
return [center_to_dict(nearest_center, nearest_distance)]
89-
87+
return sorted(within_distance, key=sort_key)
88+
elif relax_threshold: # if there are no centers within given threshold, return one that is closest
89+
return sorted(qualifying_centers, key=sort_key)
90+
else:
91+
return []
9092

9193
def read_tsv(file_path: str) -> List[Dict[str, str]]:
9294
"""
@@ -271,7 +273,7 @@ def get_output_filename():
271273

272274
for s in schools:
273275
centers_for_school = centers_within_distance(
274-
s, centers, PREF_DISTANCE_THRESHOLD)
276+
s, centers, PREF_DISTANCE_THRESHOLD, False)
275277
to_allot = int(s['count'])
276278
per_center = calc_per_center(to_allot)
277279

@@ -289,8 +291,6 @@ def get_output_filename():
289291
c['address'],
290292
c['capacity'],
291293
c['distance_km']])
292-
if is_allocated(c['cscode'], s['scode']):
293-
continue
294294
next_allot = min(to_allot, per_center, max(
295295
centers_remaining_cap[c['cscode']], MIN_STUDENT_IN_CENTER))
296296
if to_allot > 0 and next_allot > 0 and centers_remaining_cap[c['cscode']] >= next_allot:
@@ -302,10 +302,8 @@ def get_output_filename():
302302

303303
if to_allot > 0: # try again with relaxed constraints and more capacity at centers
304304
expanded_centers = centers_within_distance(
305-
s, centers, ABS_DISTANCE_THRESHOLD)
305+
s, centers, ABS_DISTANCE_THRESHOLD, True)
306306
for c in expanded_centers:
307-
if is_allocated(c['cscode'], s['scode']):
308-
continue
309307
stretched_capacity = math.floor(
310308
int(c['capacity']) * STRETCH_CAPACITY_FACTOR + centers_remaining_cap[c['cscode']])
311309
next_allot = min(to_allot, max(
@@ -330,7 +328,7 @@ def get_output_filename():
330328

331329
if to_allot > 0:
332330
remaining += to_allot
333-
logger.warn(
331+
logger.warning(
334332
f"{to_allot}/{s['count']} left for {s['scode']} {s['name-address']} centers: {len(centers_for_school)}")
335333

336334
logger.info("Remaining capacity at each center (remaining_capacity cscode):")

test/__init__.py

Whitespace-only changes.

test/test_results.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import unittest
2+
import sys
3+
import os
4+
import subprocess
5+
import warnings
6+
7+
# Add the parent directory to the Python path
8+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9+
10+
from test.utils.custom_tsv_parser import ParseTSVFile
11+
12+
PREF_CUTOFF = -4
13+
14+
15+
def get_scode_cscode_id(data):
16+
# Create an id with scode and center with sorted values of scode and center
17+
school_centers = []
18+
for row in data:
19+
scode = int(row["scode"])
20+
center = int(row["cscode"])
21+
sccode_center_id = sorted((scode, center))
22+
sccode_center_id = "_".join(map(str, sccode_center_id))
23+
school_centers.append(sccode_center_id)
24+
return school_centers
25+
26+
27+
class TestSchoolCenter(unittest.TestCase):
28+
"""_Tests to validate the outcome of the output are matching
29+
as per the requirements
30+
31+
Needs the ouptut from the results/school-center.tsv and
32+
results/school-center-distance.tsv files to be present
33+
34+
"""
35+
36+
def setUp(self):
37+
self.school_center_file = "results/school-center.tsv"
38+
self.school_center_distance_file = "results/school-center-distance.tsv"
39+
self.school_center_pref_file = "sample_data/prefs.tsv"
40+
schools_tsv = "sample_data/schools_grade12_2081.tsv"
41+
centers_tsv = "sample_data/centers_grade12_2081.tsv"
42+
cmd = f"python school_center.py {schools_tsv} {centers_tsv} {self.school_center_pref_file}"
43+
subprocess.run(cmd, shell=True)
44+
45+
def tearDown(self):
46+
os.remove(self.school_center_file)
47+
os.remove(self.school_center_distance_file)
48+
49+
def test_results_exists(self):
50+
"""_Test if the application in running which output the results in the
51+
results filder_
52+
53+
Returns:
54+
Pass: If the file exists in the results folder
55+
Fail: If the file doesnot exists in the results folder
56+
"""
57+
self.assertTrue(os.path.exists(self.school_center_file))
58+
self.assertTrue(os.path.exists(self.school_center_distance_file))
59+
self.assertTrue(os.path.exists(self.school_center_pref_file))
60+
61+
def test_scode_student_count_not_more_than_200(self):
62+
"""_Test if the student count is not more than 200_
63+
Test case ID 001:- एक विद्यालयको परिक्षार्थी संख्या हेरी सकभर १००, २०० भन्दा बढी
64+
परीक्षार्थी एकै केन्द्रमा नपर्ने गरी बाँढ्न पर्ने
65+
66+
Returns:
67+
Pass: If the student count is not more than 100
68+
Fail: If the student count is more than 100
69+
"""
70+
ptf = ParseTSVFile(self.school_center_file)
71+
data = ptf.get_rows()
72+
for row in data:
73+
student_count = row["allocation"]
74+
if int(student_count) > 200:
75+
warnings.warn(f"student count is more than 200 for the school {row}")
76+
77+
78+
def test_scode_cscode_not_same(self):
79+
"""_Test if the output of scode is not equal to cscode_
80+
Test case ID :- आफ्नै विद्यालयमा केन्द्र पार्न नहुने
81+
82+
Returns:
83+
Pass: If the scode is not same as cscode
84+
Fail: If the scode is same as cscode
85+
"""
86+
scf = ParseTSVFile(self.school_center_file)
87+
data = scf.get_rows()
88+
failures = []
89+
for row in data:
90+
scode = row["scode"]
91+
cscode = row["cscode"]
92+
if scode == cscode:
93+
failures.append(f"scode and cscode are same for row {row} {scode}")
94+
assert len(failures) == 0, f'{len(failures)} rows failed. {chr(10).join(failures)}'
95+
96+
def test_no_mutual_centers(self):
97+
"""_Test if the scode's center is not same as cscode's
98+
centre and vice versa_
99+
Test case ID :- दुई विद्यालयका परीक्षार्थीको केन्द्र एक अर्कामा पर्न नहुने, अर्थात् कुनै विद्यालयका परीक्षार्थीको केन्द्र परेको विद्यालयका परीक्षार्थीहरूको केन्द्र अघिल्लो विद्यालयमा पार्न नहुने ।
100+
101+
Returns:
102+
Pass: If the scode's center is not same as cscode's center
103+
Fail: If the scode's center is same as cscode's center
104+
"""
105+
scf = ParseTSVFile(self.school_center_file)
106+
data = scf.get_rows()
107+
scodes_centers = []
108+
109+
scodes_centers = get_scode_cscode_id(data)
110+
111+
# Check if there are any duplicates id if duplicate then there is a collision between school and center
112+
duplicates = [
113+
item for item in set(scodes_centers) if scodes_centers.count(item) > 1
114+
]
115+
116+
self.assertFalse(
117+
duplicates,
118+
f"Duplicate values found in scode_center_code: {', '.join(duplicates)}",
119+
)
120+
121+
@unittest.skip ("needs review")
122+
def test_undesired_cscode_scode_pair(self):
123+
"""_Test if the schools and the centers are not matched based on the
124+
cost preferences defined in the prefs.tsv file_
125+
Test case ID :-
126+
1 एकै स्वामित्व / व्यवस्थापनको भनी पहिचान भएका केन्द्रमा पार्न नहुने
127+
2 विगतमा कुनै विद्यालयको कुनै केन्द्रमा पार्दा समस्या देखिएकोमा केन्द्र दोहोऱ्याउन नहुने
128+
129+
Returns:
130+
Pass: If the schools with undesired scodes are are not paired with its cscodes
131+
Fail: If the schools with same management are each other's center
132+
"""
133+
134+
scf = ParseTSVFile(self.school_center_file)
135+
cpf = ParseTSVFile(self.school_center_pref_file)
136+
data_scf = scf.get_rows()
137+
data_cpf = cpf.get_rows()
138+
for cpf_data in data_cpf:
139+
if int(cpf_data["pref"]) < PREF_CUTOFF:
140+
data_cpf.remove(cpf_data)
141+
142+
failures = []
143+
144+
scodes_centers = get_scode_cscode_id(data_scf)
145+
146+
undesired_csodes_centers = get_scode_cscode_id(data_cpf)
147+
for undesired_cscodes_center in undesired_csodes_centers:
148+
if undesired_cscodes_center in scodes_centers:
149+
failures.append(
150+
f"Schools with undesired centers {undesired_cscodes_center}"
151+
)
152+
153+
assert len(failures) == 0, f'{len(failures)} rows failed. {chr(10).join(failures)}'
154+
155+
156+
157+
if __name__ == "__main__":
158+
unittest.main()

test/utils/__init__.py

Whitespace-only changes.

test/utils/custom_tsv_parser.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import csv
2+
3+
4+
class ParseTSVFile:
5+
def __init__(self, file_path):
6+
self.file_path = file_path
7+
8+
def read_file(self):
9+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
10+
reader = csv.DictReader(file, delimiter="\t")
11+
return reader
12+
13+
def get_columns(self):
14+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
15+
reader = csv.DictReader(file, delimiter="\t")
16+
return reader.fieldnames
17+
18+
def get_row_count(self):
19+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
20+
reader = csv.DictReader(file, delimiter="\t")
21+
return len(list(reader))
22+
23+
def get_column_count(self):
24+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
25+
reader = csv.DictReader(file, delimiter="\t")
26+
return len(reader.fieldnames)
27+
28+
def get_rows(self):
29+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
30+
reader = csv.DictReader(file, delimiter="\t")
31+
data = []
32+
for row in reader:
33+
data.append(row)
34+
return data
35+
36+
def get_column_data(self, column_name):
37+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
38+
reader = csv.DictReader(file, delimiter="\t")
39+
data = []
40+
for row in reader:
41+
data.append(row[column_name])
42+
return data
43+
44+
def get_row_data(self, row_number):
45+
with open(self.file_path, "r", newline="", encoding="utf-8") as file:
46+
reader = csv.DictReader(file, delimiter="\t")
47+
data = []
48+
for row in reader:
49+
data.append(row)
50+
return data[row_number]

0 commit comments

Comments
 (0)