To protect your data, the CISO officer has suggested users to enable GitLab 2FA as soon as possible.

Commit d47043b6 authored by Lincoln Smith's avatar Lincoln Smith
Browse files

Improved handling of weird alignments in build_reqnode_tree().

parent 0a120889
Library for scraping [Program Orders](https://policies.anu.edu.au/ppl/document/ANUP_006803) from the ANU [Programs and Courses](https://programsandcourses.anu.edu.au/) website.
Provides functions for processing P&C html into an intermediate `dr_scarper.ReqNode` representation that attempts to preserve layout information, and a `dr_scraper.DegreeRuleScraper` class that can be used to generate a `dr_scraper.ProgramOrder` tree representaion of a set of requirements that should be amenable to further processing.
Provides functions for processing P&C html into an intermediate `dr_scraper.ReqNode` representation that attempts to preserve layout information, and a `dr_scraper.DegreeRuleScraper` class that can be used to generate a `dr_scraper.ProgramOrder` tree representaion of a set of requirements that should be amenable to further processing.
## Usage
Typical usage is to create a `DegreeRuleScraper` with a url:
```python
from dr_scraper import DegreeRuleScraper
scraper = DegreeRuleScraper('https://programsandcourses.anu.edu.au/program/VCOMP')
orders = scraper.build_program_order_struct()
```
The scraper class will attempt to interpret the path as a file path if processing it as a URL fails. You can force interpretation as a file path by setting `path_is_file`:
```python
scraper = DegreeRuleScraper('/some/file/here.html', path_is_file=True)
```
The other common parameter to change is `header_id`. Degree/program level plans use the default `header_id`, subplans like majors and minors have a different page structure and you will need to set `header_id` as below:
```python
scraper = DegreeRuleScraper('https://programsandcourses.anu.edu.au/specialisation/HCSD-SPEC', header_id='requirements')
```
This diff is collapsed.
<html>
<head>
</head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p>Either:</p>
<p style="margin-left: 40px;">30 units from completion of</p>
<p>Or:</p>
<p style="margin-left: 40px;">24 units from completion of</p>
<p style="margin-left: 80px;">Artificial Intelligence&nbsp;<br>Data Science<br>Human Centred Design and Software Development</p>
<p><br>&nbsp;</p>
<p style="margin-left: 40px;">Unless otherwise stated</p>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</div>
</body>
</html>
<html>
<head>
</head>
<body id="top">
<div class="subplan">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="requirements">Requirements</h2>
<p>The specialisation requires completion of 24 units</p>
<p>A minimum of 12 units</p>
<p style="margin-left: 40px;">A maximum of 12 units</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP6353">COMP6353</a> Systems Engineering for Software Engineers<br><a href="/2019/course/COMP6461">COMP6461</a> Computer Graphics</p>
<p style="margin-left: 40px;">A minimum of 12 units</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP8173">COMP8173</a> Software Engineering Processes<br><a href="/2019/course/COMP8190">COMP8190</a> Model-Driven Software Development<br>&nbsp;</p>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
......@@ -11,7 +11,6 @@
<p style="margin-left: 40px;">A minimum of 48 units</p>
<p>The 96 units must consist of:</p>
<p style="margin-left: 40px;">36 units from completion of:</p>
<p style="margin-left: -40px;">Negative margin!</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP6442">COMP6442</a> Software Construction&nbsp;<br><a href="/2019/course/COMP8260">COMP8260</a> Professional Practice 2<br><a href="/2019/course/COMP6445">COMP6445</a> Computing Research Methods<br><a href="/2019/course/COMP6331">COMP6331</a> Computer Networks<br><a href="/2019/course/COMP6420">COMP6420</a> Introduction to Data Management, Analysis and Security<br><a href="/2019/course/COMP6120">COMP6120</a> Software Engineering&nbsp;</p>
<p><br>&nbsp;</p>
<p>Either:</p>
......
<html>
<head>
</head>
<body id="top">
<div class="subplan">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="requirements">Requirements</h2>
<p>The specialisation requires completion of 24 units, which must consist of</p>
<p>A minimum of 12 units must come from 8000-level courses</p>
<p style="margin-left: 40px;">A maximum of 12 units from completion of courses from the following list:</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP6353">COMP6353</a> Systems Engineering for Software Engineers<br><a href="/2019/course/COMP6390">COMP6390</a> HCI and Usability Engineering<br><a href="/2019/course/COMP6461">COMP6461</a> Computer Graphics</p>
<p style="margin-left: 40px;">A minimum of 12 units from completion of courses from the following list:</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP8100">COMP8100</a> Requirements Elicitation and Analysis Techniques<br><a href="/2019/course/COMP8173">COMP8173</a> Software Engineering Processes<br><a href="/2019/course/COMP8190">COMP8190</a> Model-Driven Software Development<br><a href="/2019/course/COMP8420">COMP8420</a> Bio-Inspired Computing Applications &amp; Interfaces<br><a href="/2019/course/VCPG6001">VCPG6001</a> Unravelling Complexity<br><a href="/2019/course/VCPG6004">VCPG6004</a> Creating Impact<br><a href="/2019/course/VCPG8001">VCPG8001</a> Dealing with Wicked Problems<br>&nbsp;</p>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<html>
<head></head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p style="padding-left: 30px;">24 units from completion of one of the following Engineering minors:</p>
<p style="padding-left: 60px;"><a href="../minor/BMSY-MIN">Biomedical Systems</a></p>
<p style="padding-left: 60px;"><a href="../minor/ELCO-MIN">Electronic and Communication Systems</a></p>
<p style="padding-left: 60px;"><a href="../minor/MMSY-MIN">Mechanical and Material Systems</a></p>
<h2 id="minors">Minors</h2>
<div class="body__inner__columns">
<div class="w-narrow body__inner__column body__inner__column-first">
<ul>
<li>
<a href="/minor/BMSY-MIN">Biomedical Systems</a>
</li>
<li>
<a href="/minor/ELCO-MIN">Electronic and Communication Systems</a>
</li>
<li>
<a href="/minor/MMSY-MIN">Mechanical and Material Systems</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</body>
</html>
<html>
<head>
</head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p style="margin-left: 40px;">Students who do not achieve a GPA of 6 in the first 48 units of courses attempted will be transferred to the Master of Computing.</p>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</div>
</body>
</html>
<html><head>
</head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p style="margin-left: 40px;">This specialisation must be taken in conjunction with the Cyber Security major</p>
<h2 id="majors">Majors</h2>
<div class="body__inner__columns">
<div class="w-narrow body__inner__column body__inner__column-first">
<ul>
<li>
<a href="/2019/major/CSEC-MAJ">Cyber Security</a>
</li>
</ul>
</div>
<div class="w-narrow body__inner__column">
<ul>
</ul>
</div>
<div class="w-narrow body__inner__column body__inner__column-last">
<ul>
</ul>
</div>
</div>
</div>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</body>
</html>
<html>
<head></head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content" style="display: inline;">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p style="padding-left: 30px;">This specialisation must be taken in conjunction with a major from the following list</p>
<p style="padding-left: 60px;"><a href="../major/BMSY-MAJ">Biomedical Systems</a></p>
<p style="padding-left: 60px;"><a href="../major/ELCO-MAJ">Electronic and Communication Systems</a></p>
<p style="padding-left: 60px;"><a href="../major/MMSY-MAJ">Mechanical and Material Systems</a></p>
<h2 id="majors">Majors</h2>
<div class="body__inner__columns">
<div class="w-narrow body__inner__column body__inner__column-first">
<ul>
<li>
<a href="/major/BMSY-MAJ">Biomedical Systems</a>
</li>
<li>
<a href="/major/ELCO-MAJ">Electronic and Communication Systems</a>
</li>
<li>
<a href="/major/MMSY-MAJ">Mechanical and Material Systems</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</body>
</html>
<html class="js rgba opacity fontface wf-proximanova-n7-active wf-proximanova-i7-active wf-proximanova-n6-active wf-proximanova-i6-active wf-proximanova-n4-active wf-proximanova-i4-active wf-active" style=""><head>
</head>
<body id="top">
<div class="main">
<div class="main-inner">
<div class="body transition">
<div id="study" class="tab-content">
<div class="body__inner w-doublewide copy">
<h2 id="program-requirements">Program Requirements</h2>
<p>The Master of Computing (Advanced) requires the completion of 96 units, of which:</p>
<p style="margin-left: 40px;">A minimum of 48 units must come from completion of 8000-level courses</p>
<p>The 96 units must consist of:</p>
<p style="margin-left: 40px;">36 units from completion of the following compulsory courses:</p>
<p style="margin-left: 80px;"><a href="/2019/course/COMP6442">COMP6442</a> Software Construction&nbsp;<br><a href="/2019/course/COMP8260">COMP8260</a> Professional Practice 2<br><a href="/2019/course/COMP6445">COMP6445</a> Computing Research Methods<br><a href="/2019/course/COMP6331">COMP6331</a> Computer Networks<br><a href="/2019/course/COMP6420">COMP6420</a> Introduction to Data Management, Analysis and Security<br><a href="/2019/course/COMP6120">COMP6120</a> Software Engineering&nbsp;</p>
<p><br>&nbsp;</p>
<p>Either:</p>
<p style="margin-left: 40px;">30 units from completion of further courses from the subject area COMP Computer Science, including non-COMP courses included in specialisation lists.</p>
<p>Or:</p>
<p style="margin-left: 40px;">6 units from completion of further courses from the subject area COMP Computer Science</p>
<h2 id="specialisations">Specialisations</h2>
<div class="body__inner__columns">
<div class="w-narrow body__inner__column body__inner__column-first">
<ul>
<li>
<a href="/2019/specialisation/AINT-SPEC">Artificial Intelligence</a>
</li>
</ul>
</div>
<div class="w-narrow body__inner__column">
<ul>
<li>
<a href="/2019/specialisation/DTSC-SPEC">Data Science</a>
</li>
</ul>
</div>
<div class="w-narrow body__inner__column body__inner__column-last">
<ul>
<li>
<a href="/2019/specialisation/HCSD-SPEC">Human Centred Design and Software Development</a>
</li>
</ul>
</div>
</div>
<h2 id="studyoptions">Study Options</h2>
<div class="table-container">
<table class="table-program">
<tbody>
<tr>
<td class="table-program__year" rowspan="2">
<span class="table-program__year-label">Year 1</span>
<span class="table-program__units-label">48 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP6442">COMP6442</a></span>
<span class="table-program__course-level">Software Construction</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP6445">COMP6445</a></span>
<span class="table-program__course-level">Advanced Computing Research Methods</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP6420">COMP6420</a></span>
<span class="table-program__course-level">Introduction to Data Management, Analysis and Security</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP6331">COMP6331</a></span>
<span class="table-program__course-level">Computer Networks</span>
<span class="table-program__course-units">6 units</span>
</td>
</tr>
<tr>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP8260">COMP8260</a></span>
<span class="table-program__course-level">Professional Practice 2</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP6120">COMP6120</a></span>
<span class="table-program__course-level">Software Engineering</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Specialisation Course/ Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
</tr>
<tr>
<td class="table-program__year" rowspan="2">
<span class="table-program__year-label">Year 2</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP8800">COMP8800</a></span>
<span class="table-program__course-level">Computing Research Project</span>
<span class="table-program__course-units">12 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"></span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Specialisation Course/ Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
</tr>
<tr>
<td class="table-program__course ">
<span class="table-program__course-type"><a href="/2019/course/COMP8800">COMP8800</a></span>
<span class="table-program__course-level">Computing Research Project</span>
<span class="table-program__course-units">12 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type"></span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Specialisation Course/ Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
<td class="table-program__course ">
<span class="table-program__course-type">Specialisation Course/ Computing Elective</span>
<span class="table-program__course-units">6 units</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<a class="back-to-top" href="#top">Back to the top</a>
</div>
</div>
</div>
</body></html>
......@@ -43,20 +43,18 @@ class TestMiscMethods(unittest.TestCase):
self.assertTupleEqual(
req_tree.dump_tree(),
([''], -1, [
(['The Master of Computing:'], 0, [(['A minimum of 48 units'], 40, []), ]),
(['The 96 units must consist of:'], 0, [
(['36 units from completion of:'], 40, [
(['COMP6442', 'Software Construction', 'COMP8260',
'Professional Practice 2', 'COMP6445', 'Computing Research Methods',
'COMP6331', 'Computer Networks', 'COMP6420',
'Introduction to Data Management, Analysis and Security', 'COMP6120',
'Software Engineering'], 80, []),
]),
(['The Master of Computing:'], 0, []),
(['A minimum of 48 units'], 40, []),
(['36 units from completion of:'], 40, [
(['COMP6442', 'Software Construction', 'COMP8260',
'Professional Practice 2', 'COMP6445', 'Computing Research Methods',
'COMP6331', 'Computer Networks', 'COMP6420',
'Introduction to Data Management, Analysis and Security', 'COMP6120',
'Software Engineering'], 80, []),
]),
(['Either:'], 0, [(['30 units from completion of.'], 40, []), ]),
(['Or:'], 0, [(['6 units from completion of'], 40, []), ]),
]
)
])
)
def test_build_reqnode_tree_basic_tree2(self):
......@@ -73,31 +71,26 @@ class TestMiscMethods(unittest.TestCase):
self.assertTupleEqual(
req_tree.dump_tree(),
([''], -1, [
(['The Bachelor of Advanced Computing:'], 0, [
(['A maximum of 60 units'], 40, []),
(['A minimum of 24 units.'], 40, [])
(['The Bachelor of Advanced Computing:'], 0, []),
(['A maximum of 60 units'], 40, []),
(['A minimum of 24 units.'], 40, []),
(['54 units from completion of:'], 40, [
(['COMP1600', 'Foundations of Computing'], 80, []),
(['COMP2100', 'Software Design Methodologies'], 80, [])
]),
(['The 192 units must include:'], 0, [
(['54 units from completion of:'], 40, [
(['COMP1600', 'Foundations of Computing'], 80, []),
(['COMP2100', 'Software Design Methodologies'], 80, [])
]),
(['6 units from completion of:'], 40, [
(['COMP1100', 'Programming as Problem Solving'], 80, []),
(['COMP1130', 'Programming as Problem Solving (Advanced)'], 80, [])
]),
(['Either:'], 40, [
(['12 units from completion of'], 80, [])
]),
(['Or:'], 40, [
(['12 units from completion of:'], 80, [
(['ENGN3230', 'Engineering Innovation'], 120, []),
(['VCUG3001', 'Unravelling Complexity'], 120, []),
(['VCUG3002', 'Mobilising Research'], 120, [])
])
]),
(['48 units from completion of'], 40, [])
])
(['6 units from completion of:'], 40, [
(['COMP1100', 'Programming as Problem Solving'], 80, []),
(['COMP1130', 'Programming as Problem Solving (Advanced)'], 80, [])
]),
(['Either:'], 40, [(['12 units from completion of'], 80, [])]),
(['Or:'], 40, [
(['12 units from completion of:'], 80, [
(['ENGN3230', 'Engineering Innovation'], 120, []),
(['VCUG3001', 'Unravelling Complexity'], 120, []),
(['VCUG3002', 'Mobilising Research'], 120, [])
])
]),
(['48 units from completion of'], 40, [])
])
)
......@@ -133,6 +126,47 @@ class TestMiscMethods(unittest.TestCase):
])
)
def test_build_reqnode_tree_bad_indent(self):
"""
Process a page with mis-aligned requirements
"""
with open('tests/sample_html/bad_indent.html') as sample_file:
soup = BeautifulSoup(sample_file.read(), 'html5lib')
req_tree = build_reqnode_tree(get_requirements_block(soup))
self.assertTupleEqual(
req_tree.dump_tree(),
([''], -1, [
(['Either:'], 0, [(['30 units from completion of'], 40, [])]),
(['Or:'], 0, [
(['24 units from completion of'], 40, [
(['Artificial Intelligence',
'Data Science',
'Human Centred Design and Software Development'], 80, [])
])
]),
(['Unless otherwise stated'], 40, [])
])
)
with open('tests/sample_html/bad_indent2.html') as sample_file:
soup = BeautifulSoup(sample_file.read(), 'html5lib')
req_tree = build_reqnode_tree(get_requirements_block(soup), header_id='requirements')
self.assertTupleEqual(
req_tree.dump_tree(),
([''], -1, [
(['The specialisation requires completion of 24 units'], 0, []),
(['A minimum of 12 units'], 0, []),
(['A maximum of 12 units'], 40, [
(['COMP6353', 'Systems Engineering for Software Engineers', 'COMP6461',
'Computer Graphics'], 80, [])
]),
(['A minimum of 12 units'], 40, [
(['COMP8173', 'Software Engineering Processes', 'COMP8190',
'Model-Driven Software Development'], 80, [])
])
])
)
class TestDegreeRuleScraper(unittest.TestCase):
def test_init(self):
......@@ -143,10 +177,13 @@ class TestDegreeRuleScraper(unittest.TestCase):
self.assertEqual(scraper.year, str(date.today().year))
self.assertEqual(scraper.plan_type, 'program')
self.assertEqual(scraper.plan_code, 'VCOMP')
scraper = DegreeRuleScraper('https://programsandcourses.anu.edu.au/program/VCOMP')
# Parse a more comlicated URL
scraper = DegreeRuleScraper(
'https://programsandcourses.anu.edu.au/program/7706XMCOMP#program-requirements'
)
self.assertEqual(scraper.year, date.today().year)
self.assertEqual(scraper.plan_type, 'program')
self.assertEqual(scraper.plan_code, 'VCOMP')
self.assertEqual(scraper.plan_code, '7706XMCOMP')
def test_process_global_reqs(self):
"""
......@@ -155,8 +192,7 @@ class TestDegreeRuleScraper(unittest.TestCase):
"""
scraper = DegreeRuleScraper('tests/sample_html/global_reqs.html', path_is_file=True)
scraper.plan_code = 'AACOM'
req_tree = build_reqnode_tree(get_requirements_block(scraper.soup))
orders = list(scraper.process_requirement_node(req_tree.children))
orders = scraper.build_program_order_struct().children
self.assertEqual(len(orders), 4)
self.assertEqual(
'AACOM Bachelor of Advanced Computing (Honours): >= 192 units from courses '
......@@ -201,11 +237,11 @@ class TestDegreeRuleScraper(unittest.TestCase):
self.assertEqual(order.operator, dr_scraper.REQUIREMENT_OPERATORS['=='])
order = orders.children[1]
self.assertEqual(order.title, dr_scraper.ORDER_LABEL['PRINCIPAL_SINGLE_SET'])
self.assertEqual(order.unit_value,12 )
self.assertEqual(order.unit_value, 12)
self.assertEqual(order.operator, dr_scraper.REQUIREMENT_OPERATORS['MAX'])
order = orders.children[2]
self.assertEqual(order.title, dr_scraper.ORDER_LABEL['PRINCIPAL_SINGLE_SET'])
self.assertEqual(order.unit_value,12 )
self.assertEqual(order.unit_value, 12)
self.assertEqual(order.operator, dr_scraper.REQUIREMENT_OPERATORS['MIN'])
def test_process_principal_requirement_single_course(self):
......@@ -528,3 +564,103 @@ class TestDegreeRuleScraper(unittest.TestCase):
self.assertEqual(child_order.code, 'ELCO-MAJ')
child_order = orders.children[2]
self.assertEqual(child_order.code, 'MMSY-MAJ')
def test_process_principal_requirement_minor_options(self):
"""
Process minor alternatives subplan requirement.
"""
scraper = DegreeRuleScraper('tests/sample_html/minor_options.html', path_is_file=True)
scraper.plan_code = 'VCOMP'
scraper.path = 'https://programsandcourses.anu.edu.au/'
orders = scraper.build_program_order_struct()
self.assertEqual(len(orders.children), 1)
# Check the root node of the subplan orders
child_order = orders.children[0]
self.assertEqual(child_order.code, 'VCOMP')
self.assertEqual(child_order.title, dr_scraper.ORDER_LABEL['PRINCIPAL_SUBPLAN_CHOICE'])
self.assertEqual(child_order.unit_value, -1)
self.assertEqual(child_order.operator, dr_scraper.REQUIREMENT_OPERATORS['OR'])
self.assertIsNone(child_order.course_filter)
# Check a subplan order tree was added for each subplan option
self.assertEqual(len(child_order.children), 3)
orders = child_order
child_order = orders.children[0]
self.assertEqual(child_order.code, 'BMSY-MIN')
child_order = orders.children[1]
self.assertEqual(child_order.code, 'ELCO-MIN')
child_order = orders.children[2]
self.assertEqual(child_order.code, 'MMSY-MIN')
def test_process_principal_requirement_specialisation_coreq(self):
"""
Process undergrad specialisation that requires a major.
"""
scraper = DegreeRuleScraper('tests/sample_html/specialisation_coreq.html',
path_is_file=True)
scraper.plan_code = 'VCOMP' </