diff --git a/Pipfile.lock b/Pipfile.lock
index 5c2633d..4ee6d55 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -41,10 +41,10 @@
},
"azure-storage-blob": {
"hashes": [
- "sha256:26c0a4320a34a3c2a1b74528ba6812ebcb632a04cd67b1c7377232c4b01a5897",
- "sha256:7bbc2c9c16678f7a420367fef6b172ba8730a7e66df7f4d7a55d5b3c8216615b"
+ "sha256:13e16ba42fc54ac2c7e8f976062173a5c82b9ec0594728e134aac372965a11b0",
+ "sha256:c5530dc51c21c9564e4eb706cd499befca8819b10dd89716d3fc90d747556243"
],
- "version": "==12.19.0"
+ "version": "==12.19.1"
},
"certifi": {
"hashes": [
@@ -433,19 +433,19 @@
},
"packaging": {
"hashes": [
- "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
- "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
+ "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
+ "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
],
"markers": "python_version >= '3.7'",
- "version": "==23.2"
+ "version": "==24.0"
},
"parsel": {
"hashes": [
- "sha256:2708fc74daeeb4ce471e2c2e9089b650ec940c7a218053e57421e69b5b00f82c",
- "sha256:aff28e68c9b3f1a901db2a4e3f158d8480a38724d7328ee751c1a4e1c1801e39"
+ "sha256:1272baeda331cbc7f3a9a91df4c15c39276dc94b4ff2e8d5cffdf8bb6c382ea1",
+ "sha256:a5a6bcb0c5fc741540ba3075636ff5cb968852a78cc16ae82250e5d486fa7d48"
],
- "markers": "python_version >= '3.7'",
- "version": "==1.8.1"
+ "markers": "python_version >= '3.8'",
+ "version": "==1.9.0"
},
"protego": {
"hashes": [
@@ -488,20 +488,20 @@
},
"pyopenssl": {
"hashes": [
- "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf",
- "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"
+ "sha256:17ed5be5936449c5418d1cd269a1a9e9081bc54c17aed272b45856a3d3dc86ad",
+ "sha256:cabed4bfaa5df9f1a16c0ef64a0cb65318b5cd077a7eda7d6970131ca2f41a6f"
],
"markers": "python_version >= '3.7'",
- "version": "==24.0.0"
+ "version": "==24.1.0"
},
"python-dateutil": {
"hashes": [
- "sha256:78e73e19c63f5b20ffa567001531680d939dc042bf7850431877645523c66709",
- "sha256:cbf2f1da5e6083ac2fbfd4da39a25f34312230110440f424a14c7558bb85d82e"
+ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
+ "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.9.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "version": "==2.9.0.post0"
},
"pytz": {
"hashes": [
@@ -520,11 +520,11 @@
},
"referencing": {
"hashes": [
- "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5",
- "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"
+ "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844",
+ "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"
],
"markers": "python_version >= '3.8'",
- "version": "==0.33.0"
+ "version": "==0.34.0"
},
"requests": {
"hashes": [
@@ -670,15 +670,15 @@
"sha256:fd2b8500d64b909289e3541c201b4d672c0e7b458fc20e77bb37f0d71d93a75a"
],
"index": "pypi",
- "markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
+ "markers": "python_full_version >= '3.6.1' and python_version < '4.0'",
"version": "==0.3.3"
},
"sentry-sdk": {
"hashes": [
- "sha256:becda09660df63e55f307570e9817c664392655a7328bbc414b507e9cb874c67",
- "sha256:f143f3fb4bb57c90abef6e2ad06b5f6f02b2ca13e4060ec5c0549c7a9ccce3fa"
+ "sha256:4a8364b8f7edbf47f95f7163e48334c96100d9c098f0ae6606e2e18183c223e6",
+ "sha256:a654ee7e497a3f5f6368b36d4f04baeab1fe92b3105f7f6965d6ef0de35a9ba4"
],
- "version": "==1.40.6"
+ "version": "==1.42.0"
},
"service-identity": {
"hashes": [
@@ -690,35 +690,35 @@
},
"setuptools": {
"hashes": [
- "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56",
- "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"
+ "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e",
+ "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"
],
"markers": "python_version >= '3.8'",
- "version": "==69.1.1"
+ "version": "==69.2.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"tldextract": {
"hashes": [
- "sha256:9b6dbf803cb5636397f0203d48541c0da8ba53babaf0e8a6feda2d88746813d4",
- "sha256:b9c4510a8766d377033b6bace7e9f1f17a891383ced3c5d50c150f181e9e1cc2"
+ "sha256:4dfc4c277b6b97fa053899fcdb892d2dc27295851ab5fac4e07797b6a21b2e46",
+ "sha256:c9e17f756f05afb5abac04fe8f766e7e70f9fe387adb1859f0f52408ee060200"
],
"markers": "python_version >= '3.8'",
- "version": "==5.1.1"
+ "version": "==5.1.2"
},
"twisted": {
"hashes": [
- "sha256:4ae8bce12999a35f7fe6443e7f1893e6fe09588c8d2bed9c35cdce8ff2d5b444",
- "sha256:987847a0790a2c597197613686e2784fd54167df3a55d0fb17c8412305d76ce5"
+ "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63",
+ "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae"
],
"markers": "python_full_version >= '3.8.0'",
- "version": "==23.10.0"
+ "version": "==24.3.0"
},
"typing-extensions": {
"hashes": [
@@ -790,32 +790,32 @@
"develop": {
"black": {
"hashes": [
- "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8",
- "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8",
- "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd",
- "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9",
- "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31",
- "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92",
- "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f",
- "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29",
- "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4",
- "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693",
- "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218",
- "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a",
- "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23",
- "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0",
- "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982",
- "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894",
- "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540",
- "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430",
- "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b",
- "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2",
- "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6",
- "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"
+ "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f",
+ "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93",
+ "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11",
+ "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0",
+ "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9",
+ "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5",
+ "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213",
+ "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d",
+ "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7",
+ "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837",
+ "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f",
+ "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395",
+ "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995",
+ "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f",
+ "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597",
+ "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959",
+ "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5",
+ "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb",
+ "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4",
+ "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7",
+ "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd",
+ "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==24.2.0"
+ "version": "==24.3.0"
},
"click": {
"hashes": [
@@ -886,11 +886,11 @@
},
"packaging": {
"hashes": [
- "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
- "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
+ "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5",
+ "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"
],
"markers": "python_version >= '3.7'",
- "version": "==23.2"
+ "version": "==24.0"
},
"pathspec": {
"hashes": [
@@ -934,28 +934,28 @@
},
"pytest": {
"hashes": [
- "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd",
- "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"
+ "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7",
+ "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
- "version": "==8.0.2"
+ "version": "==8.1.1"
},
"python-dateutil": {
"hashes": [
- "sha256:78e73e19c63f5b20ffa567001531680d939dc042bf7850431877645523c66709",
- "sha256:cbf2f1da5e6083ac2fbfd4da39a25f34312230110440f424a14c7558bb85d82e"
+ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
+ "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
],
"index": "pypi",
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==2.9.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+ "version": "==2.9.0.post0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"tomli": {
diff --git a/city_scrapers/spiders/bisnd_mcc.py b/city_scrapers/spiders/bisnd_mcc.py
new file mode 100644
index 0000000..60d015e
--- /dev/null
+++ b/city_scrapers/spiders/bisnd_mcc.py
@@ -0,0 +1,100 @@
+from datetime import datetime, time
+
+from city_scrapers_core.constants import COMMISSION
+from city_scrapers_core.items import Meeting
+from city_scrapers_core.spiders import CityScrapersSpider
+from dateutil.parser import parse
+
+
+class BisndMccSpider(CityScrapersSpider):
+ name = "bisnd_mcc"
+ agency = "Morton County Commission"
+ timezone = "America/Chicago"
+ start_urls = [
+ "https://www.mortonnd.org/?Type=B_BASIC&SEC={8182F8B8-3783-4C56-B690-F78FEE7CAC95}" # noqa
+ ]
+ title = "Morton County Commission Meeting"
+ meeting_time = time(17, 30)
+ location = {
+ "address": "Morton County Commission Room",
+ "name": "Morton County Courthouse, 210 2nd Ave NW, Mandan ND",
+ }
+ custom_settings = {
+ "ROBOTSTXT_OBEY": False,
+ }
+
+ def parse(self, response):
+ """
+ Parse HTML table and extract meeting information. Due to a lack of info,
+ we hardcode certain fields, like start time and title
+ """
+ for item in response.css("main[role='main'] table tbody tr:not(:first-child)"):
+ start, description = self._first_col(item)
+ if start is None:
+ self.logger.warning("Invalid date format - skipping")
+ continue
+ # ignore meetings that are more than a year ago
+ if start < datetime.now().replace(year=datetime.now().year - 1):
+ self.logger.info(
+ f"Skipping meeting from {start} as it is more than a year ago"
+ )
+ continue
+ meeting = Meeting(
+ title=self.title,
+ description=description,
+ classification=COMMISSION,
+ start=start,
+ end=None,
+ all_day=False,
+ time_notes="",
+ location=self.location,
+ links=self._parse_links(item),
+ source=response.url,
+ )
+
+ meeting["status"] = self._get_status(meeting)
+ meeting["id"] = self._get_id(meeting)
+
+ yield meeting
+
+ def _first_col(self, item):
+ """Extract date of the meeting and any additional text from
+ the first column of the table row. Date of the meeting is in
+ "Month Day, Year" format."""
+ col_str = item.css("td span::text").extract_first()
+ description = ""
+
+ # Parse text
+ if col_str is None:
+ self.logger.info("No text found in date column")
+ return None, description
+ col_words = col_str.split()
+ if len(col_words) < 3:
+ # date should always be first three words
+ self.logger.info(f"Invalid date format: {col_words}")
+ return None, description
+ date_str = " ".join(col_words[:3])
+ if len(col_words) > 3:
+ # if there's any other text, use that as the description
+ description = " ".join(col_words[3:])
+
+ # parse date
+ try:
+ parsed_date = parse(date_str)
+ # combine date and time
+ parsed_date = datetime.combine(parsed_date.date(), self.meeting_time)
+ return parsed_date, description
+ except ValueError:
+ self.logger.info(f"Invalid date format: {date_str}")
+ return None, description
+
+ def _parse_links(self, item):
+ """Parse or generate links."""
+ links = []
+ for row in item.css("td:not(:first-child) a"):
+ link = {
+ "title": row.css("::text").extract_first(),
+ "href": row.css("::attr(href)").extract_first(),
+ }
+ links.append(link)
+ return links
diff --git a/tests/files/bisnd_mcc.html b/tests/files/bisnd_mcc.html
new file mode 100644
index 0000000..77c69a5
--- /dev/null
+++ b/tests/files/bisnd_mcc.html
@@ -0,0 +1,1658 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+County Commission - Morton County, North Dakota
+ Â
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+In Government:
+
+
+
+
+
+
+
+
+
County Commission Meeting Schedule and Materials
+
Morton County Commission Meetings Schedule and Materials
+The County Commission meets on the 2nd Thursday and the 4th Tuesday of each month (unless otherwise indicated) at 5:30pm, in the Morton County Commission Room, Morton County Courthouse located at 210 2nd Ave NW, Mandan ND. All meetings are open to the public. The following meeting schedule includes agenda, packets, and minutes from meetings, as they become available. Packets are typically posted on the Tuesday prior to a Thursday meeting and on the Friday prior to a Tuesday meeting. County Commission meetings are recorded and played at various dates and times on Government Access, cable channel 2, which is available in the Bismarck Mandan area. All past meetings are available to view online at anytime on www.freetv.org .
+
+
+
+Minutes
+
+
+December 26, 2024
+Agenda
+Packet
+Minutes
+
+
+December 12, 2024
+Agenda
+Packet
+Minutes
+
+
+November 26, 2024
+Agenda
+Packet
+Minutes
+
+
+November 14, 2024
+Agenda
+Packet
+Minutes
+
+
+October 22, 2024
+Agenda
+Packet
+Minutes
+
+
+October 10, 2024
+Agenda
+Packet
+Minutes
+
+
+September 24, 2024
+Agenda
+Packet
+Minutes
+
+
+September 12, 2024
+Agenda
+Packet
+Minutes
+
+
+August 27, 2024
+Agenda
+Packet
+Minutes
+
+
+August 8, 2024
+Agenda
+Packet
+Minutes
+
+
+July 23, 2024
+Agenda
+Packet
+Minutes
+
+
+July 11, 2024
+Agenda
+Packet
+Minutes
+
+
+June 25, 2024
+Agenda
+Packet
+Minutes
+
+
+June 6, 2024
+Agenda
+Packet
+Minutes
+
+
+May 28, 2024
+Agenda
+Packet
+Minutes
+
+
+May 9, 2024
+Agenda
+Packet
+Minutes
+
+
+April 23, 2024
+Agenda
+Packet
+Minutes
+
+
+April 11, 2024
+Agenda
+Packet
+Minutes
+
+
+March 26, 2024
+Agenda
+Packet
+Minutes
+
+
+March 14, 2024
+Agenda
+Packet
+Minutes
+
+
+February 27, 2024
+Agenda
+Packet
+Minutes
+
+
+February 27 2024 LEC Advisory Bd w/ Burleigh Co
+
+
+Minute
+
+
+February 8, 2024
+Agenda
+Packet
+Minutes
+
+
+January 23, 2024
+Agenda
+Packet
+Minutes
+
+
+January 11, 2024
+Agenda
+Packet
+Minutes
+
+
+December 26, 2023
+Agenda
+Packet
+Minutes
+
+
+December 14, 2023
+Agenda
+Packet
+Minutes
+
+
+November 28, 2023
+Agenda
+Packet
+Minutes
+
+
+November 9, 2023
+Agenda
+Packet
+Minutes
+
+
+October 24, 2023
+Agenda
+Packet
+Minutes
+
+
+October 12, 2023
+Agenda
+Packet
+Minutes
+
+
+September 26, 2023
+Agenda
+Packet
+Minutes
+
+
+September 14, 2023
+Agenda
+Packet
+Minutes
+
+
+August 22, 2023
+Agenda
+Packet
+Minutes
+
+
+August 10, 2023
+Agenda
+Packet
+Minutes
+
+
+July 25, 2023
+Agenda
+Packet
+Minutes
+
+
+July 13, 2023
+Agenda
+Packet
+Minutes
+
+
+June 27, 2023
+Agenda
+Packet
+Minutes
+
+
+June 8, 2023 BOE
+Agenda
+Packet
+Minutes
+
+
+June 8, 2023
+Agenda
+Packet
+Minutes
+
+
+May 23, 2023
+Agenda
+Packet
+Minutes
+
+
+May 11, 2023
+Agenda
+Packet
+Minutes
+
+
+April 25, 2023
+Agenda
+Packet
+Minutes
+
+
+April 13, 2023
+Agenda
+Packet
+Minutes
+
+
+March 28, 2023
+Agenda
+Packet
+Minutes
+
+
+March 9, 2023
+Agenda
+Packet
+Minutes
+
+
+
+March 9, 2023
+ Morton/Burleigh Joint Co Commission Meeting
+
+Notice
+
+
+
+
+
+February 28, 2023 Cancelled
+
+Agenda
+Packet
+Minutes
+
+
+February 9, 2023
+Agenda
+Packet
+Minutes
+
+
+January 24, 2023
+Agenda
+Packet
+Minutes
+
+
+January 12, 2023
+Agenda
+Packet
+Minutes
+
+
+December 27, 2022
+Agenda
+Packet
+Minutes
+
+
+December 8, 2022
+Agenda
+Packet
+Minutes
+
+
+November 22, 2022
+Agenda
+Packet
+Minutes
+
+
+
+November 10, 2022 Cancelled
+
+
+Agenda
+Packet
+Minutes
+
+
+October 25, 2022
+Agenda
+Packet
+Minutes
+
+
+October 19, 2022
+Agenda
+
+Minutes
+
+
+October 13, 2022
+Agenda
+Packet
+Minutes
+
+
+September 27, 2022
+Agenda
+Packet
+Minutes
+
+
+September 8, 2022
+Agenda
+Packet
+Minutes
+
+
+August 23, 2022 Cancelled
+Agenda
+Packet
+Minutes
+
+
+August 11, 2022
+Agenda
+Packet
+Minutes
+
+
+July 26, 2022
+Agenda
+Packet
+Minutes
+
+
+July 14, 2022
+Agenda
+Packet
+Minutes
+
+
+June 28, 2022
+Agenda
+Packet
+Minutes
+
+
+June 9, 2022
+Agenda
+Packet
+Minutes
+
+
+June 9, 2022 BOE
+
+
+Minutes
+
+
+May 24, 2022
+Agenda
+Packet
+Minutes
+
+
+May 12, 2022
+Agenda
+Packet
+Minutes
+
+
+April 26, 2022
+Agenda
+Packet
+Minutes
+
+
+April 14, 2022
+Agenda
+Packet
+Minutes
+
+
+March 22, 2022
+Agenda
+Packet
+Minutes
+
+
+March 10, 2022
+Agenda
+Packet
+Minutes
+
+
+February 22, 2022
+Agenda
+Packet
+Minutes
+
+
+February 10, 2022
+Agenda
+Packet
+Minutes
+
+
+
+January 28, 2022 Special Commission Mtg
+
+Agenda
+Packet
+Minutes
+
+
+
+January 25, 2022
+
+Agenda
+Packet
+Minutes
+
+
+
+January 13, 2022
+
+Agenda
+Packet
+Minutes
+
+
+
+December 28, 2021
+
+Agenda
+Packet
+Minutes
+
+
+December 9, 2021
+Agenda
+Packet
+Minutes
+
+
+November 23, 2021
+Agenda
+Packet
+Minutes
+
+
+November 9, 2021
+Agenda
+Packet
+Minutes
+
+
+October 26, 2021
+Agenda
+Packet
+Minutes
+
+
+October 14, 2021
+Agenda
+Packet
+Minutes
+
+
+September 28, 2021
+Agenda
+Packet
+Minutes
+
+
+September 9, 2021
+Agenda
+Packet
+Minutes
+
+
+August 24, 2021
+Agenda
+Packet
+Minutes
+
+
+August 12, 2021
+Agenda
+Packet
+Minutes
+
+
+July 27, 2021
+Agenda
+Packet
+Minutes
+
+
+
+July 21, 2021
+Special Budget Meeting
+
+Agenda
+
+Minutes
+
+
+July 8, 2021
+Agenda
+Packet
+Minutes
+
+
+June 22, 2021
+Agenda
+Packet
+Minutes
+
+
+June 10, 2021
+Agenda
+Packet
+Minutes
+
+
+June 10, 2021 BOE
+
+
+Minutes
+
+
+May 25, 2021
+Agenda
+Packet
+Minutes
+
+
+May 13, 2021
+Agenda
+Packet
+Minutes
+
+
+April 27, 2021
+Agenda
+Packet
+Minutes
+
+
+April 8, 2021
+Agenda
+Packet
+Minutes
+
+
+March 23, 2021
+Agenda
+Packet
+Minutes
+
+
+March 11, 2021
+Agenda
+Packet
+Minutes
+
+
+
+February 23, 2021
+
+Agenda
+Packet
+Minutes
+
+
+February 11, 2021
+Agenda
+Packet
+Minutes
+
+
+January 26, 2021
+Agenda
+Packet
+Minutes
+
+
+January 14, 2021
+Agenda
+Packet
+Minutes
+
+
+
+
+
+
+
+
+December 22, 2020
+Agenda
+Packet
+Minutes
+
+
+December 10, 2020
+Agenda
+Packet
+Minutes
+
+
+November 24, 2020
+Agenda
+ Packet
+Minutes
+
+
+November 12, 2020
+Agenda
+Packet
+Minutes
+
+
+October 27, 2020
+Agenda
+Packet
+Minutes
+
+
+October 8, 2020
+Agenda
+Packet
+Minutes
+
+
+September 22, 2020
+Agenda
+Packet
+Minutes
+
+
+September 10, 2020
+Agenda
+Packet
+Minutes
+
+
+August 25, 2020 CANCELLED
+Agenda
+Packet
+Minutes
+
+
+August 13, 2020
+Agenda
+Packet
+Minutes
+
+
+August 4, 2020 Special Mtg
+Agenda
+Packet
+Minutes
+
+
+July 28, 2002
+Agenda
+Packet
+Minutes
+
+
+July 9, 2020
+Agenda
+Packet
+Minutes
+
+
+June 23, 2020
+Agenda
+Packet
+Minutes
+
+
+ June 4, 2020 BOE
+
+
+Minutes
+
+
+June 4, 2020 Comm/BOE
+Agenda
+Packet
+Minutes
+
+
+May 26, 2020
+Agenda
+Packet
+Minutes
+
+
+May 14, 2020
+Agenda
+Packet
+Minutes
+
+
+April 28, 2020
+Agenda
+Packet
+Minutes
+
+
+April 7, 2020
+Agenda
+Packet
+Minutes
+
+
+March 24, 2020
+Agenda
+Packet
+Minutes
+
+
+March 12, 2020
+Agenda
+Packet
+Minutes
+
+
+February 25, 2020
+Agenda
+Packet
+Minutes
+
+
+February 13, 2020
+Agenda
+Packet
+Minutes
+
+
+January 28, 2020
+Agenda
+Packet
+Minutes
+
+
+January 17, 2020 Special Mtg
+Agenda
+Packet
+Minutes
+
+
+January 9, 2020
+Agenda
+Packet
+Minutes
+
+
+
+
+
+
+
+
+December 26, 2019
+Agenda
+Packet
+Minutes
+
+
+December 12, 2019
+Agenda
+Packet
+Minutes
+
+
+November 26, 2019
+Agenda
+Packet
+Minutes
+
+
+November 14, 2019
+Agenda
+Packet
+Minutes
+
+
+October 29, 2019
+Agenda
+Packet
+Minutes
+
+
+October 10, 2019
+Agenda
+Packet
+Minutes
+
+
+September 24, 2019
+Agenda
+Packet
+Minutes
+
+
+September 12, 2019
+Agenda
+Packet
+Minutes
+
+
+August 27, 2019
+Agenda
+Packet
+Minutes
+
+
+August 8, 2019
+Agenda
+Packet
+Minutes
+
+
+July 30, 2019
+Agenda
+Packet
+
+Minutes
+
+
+
+July 23, 2019
+Agenda
+Packet
+
+Minutes
+
+
+
+July 11, 2019
+Agenda
+Packet
+Minutes
+
+
+June 25, 2019
+Agenda
+Packe t
+Minutes
+
+
+June 25, 2019 BOE
+Agenda
+Packet
+Minutes
+
+
+June 10, 2019
+Agenda
+Packet
+Minutes
+
+
+May 28, 2019
+Agenda
+Packet
+Minutes
+
+
+May 9, 2019
+Agenda
+Packet
+Minutes
+
+
+April 23, 2019
+Agenda
+Packet
+Minutes
+
+
+April 11, 2019
+Agenda
+Packet
+Minutes
+
+
+March 26, 2019
+Agenda
+Packet
+Minutes
+
+
+March 14, 2019
+Agenda
+Packet
+Minutes
+
+
+March 5, 2019
+Agenda
+
+Minutes
+
+
+February 26, 2019
+Agenda
+Packet
+Minutes
+
+
+February 14, 2019
+Agenda
+Packet
+Minutes
+
+
+January 29, 2019
+Agenda
+Packet
+Minutes
+
+
+January 10, 2019
+Agenda
+Packet
+Minutes
+
+
+
+
+Morton County Commission Archive
+
+
+
+
+
+
+
+
+
+Stewardship of Public Trust - Respect for Diverse Voices & Ideas - Responsive and Comprehensive Customer Service - Leadership with Ethics and Integrity - Culture of Teamwork & Collaboration
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/test_bisnd_mcc.py b/tests/test_bisnd_mcc.py
new file mode 100644
index 0000000..8637acd
--- /dev/null
+++ b/tests/test_bisnd_mcc.py
@@ -0,0 +1,79 @@
+from datetime import datetime
+from os.path import dirname, join
+
+import pytest
+from city_scrapers_core.constants import COMMISSION, TENTATIVE
+from city_scrapers_core.utils import file_response
+from freezegun import freeze_time
+
+from city_scrapers.spiders.bisnd_mcc import BisndMccSpider
+
+test_response = file_response(
+ join(dirname(__file__), "files", "bisnd_mcc.html"),
+ url="https://www.mortonnd.org/?Type=B_BASIC&SEC={8182F8B8-3783-4C56-B690-F78FEE7CAC95}", # noqa
+)
+spider = BisndMccSpider()
+
+freezer = freeze_time(datetime(2024, 3, 14, 10, 15))
+freezer.start()
+
+parsed_items = [item for item in spider.parse(test_response)]
+parsed_item = parsed_items[0]
+freezer.stop()
+
+
+def test_title():
+ assert parsed_item["title"] == "Morton County Commission Meeting"
+
+
+def test_description():
+ assert parsed_item["description"] == ""
+
+
+def test_start():
+ assert parsed_item["start"] == datetime(2024, 12, 26, 17, 30)
+
+
+def test_end():
+ assert parsed_item["end"] is None
+
+
+def test_time_notes():
+ assert parsed_item["time_notes"] == ""
+
+
+def test_id():
+ assert (
+ parsed_item["id"] == "bisnd_mcc/202412261730/x/morton_county_commission_meeting"
+ )
+
+
+def test_status():
+ assert parsed_item["status"] == TENTATIVE
+
+
+def test_location():
+ assert parsed_item["location"] == {
+ "address": "Morton County Commission Room",
+ "name": "Morton County Courthouse, 210 2nd Ave NW, Mandan ND",
+ }
+
+
+def test_source():
+ assert (
+ parsed_item["source"]
+ == "https://www.mortonnd.org/?Type=B_BASIC&SEC={8182F8B8-3783-4C56-B690-F78FEE7CAC95}" # noqa
+ )
+
+
+def test_links():
+ assert parsed_item["links"] == []
+
+
+def test_classification():
+ assert parsed_item["classification"] == COMMISSION
+
+
+@pytest.mark.parametrize("item", parsed_items)
+def test_all_day(item):
+ assert item["all_day"] is False