Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/appKom/online-opptak into 1…
Browse files Browse the repository at this point in the history
…45-merge-applicant-available-times
  • Loading branch information
fredrir committed Aug 15, 2024
2 parents 0520662 + d119498 commit 60ff2ed
Show file tree
Hide file tree
Showing 71 changed files with 2,332 additions and 1,587 deletions.
2 changes: 1 addition & 1 deletion .env.local.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ AUTH0_CLIENT_SECRET=#client secret
AUTH0_ISSUER=#issuer base url, example: example.us.auth0.com

AWS_SECRET_ACCESS_KEY=#aws secret access key
AWS_ACCESS_KEY_ID=#aws access key id
AWS_ACCESS_KEY_ID=#aws access key id
5 changes: 2 additions & 3 deletions algorithm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ cd algorithm
python -m venv ".venv"
```

Lag så en fil i `.\.venv\Lib\site-packages` som slutter på `.pth` og inneholder den absolutte filstien til `mip_matching`-mappen.

```
.\.venv\Scripts\activate
python -m pip install -r requirements.txt
pip install -e .
pip install -r requirements.txt
```

## TODOs
Expand Down
2 changes: 2 additions & 0 deletions algorithm/bridge/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MONGODB_URI=#url to mongodb database
DB_NAME=#name of db
166 changes: 166 additions & 0 deletions algorithm/bridge/fetch_applicants_and_committees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
from pymongo import MongoClient
from dotenv import load_dotenv
from datetime import datetime, timedelta, timezone
import os
import certifi
from typing import List, Dict

from mip_matching.Committee import Committee
from mip_matching.TimeInterval import TimeInterval
from mip_matching.Applicant import Applicant
from mip_matching.match_meetings import match_meetings, MeetingMatch

def main():
periods = fetch_periods()

for period in periods:
periodId = str(period["_id"])
application_end = datetime.fromisoformat(period["applicationPeriod"]["end"].replace("Z", "+00:00"))

now = datetime.now(timezone.utc)


#or period["name"] == "Juli Opptak"
if (application_end < now and period["hasSentInterviewTimes"] == False):
applicants = fetch_applicants(periodId)
committee_times = fetch_committee_times(periodId)

committee_objects = create_committee_objects(committee_times)

all_committees = {committee.name: committee for committee in committee_objects}

applicant_objects = create_applicant_objects(applicants, all_committees)

print(applicant_objects)
print(committee_objects)

match_result = match_meetings(applicant_objects, committee_objects)

send_to_db(match_result, applicants, periodId)
return match_result

def send_to_db(match_result: MeetingMatch, applicants: List[dict], periodId):
load_dotenv()
formatted_results = format_match_results(match_result, applicants, periodId)
print("Sending to db")
print(formatted_results)

mongo_uri = os.getenv("MONGODB_URI")
db_name = os.getenv("DB_NAME")
client = MongoClient(mongo_uri, tlsCAFile=certifi.where())

db = client[db_name] # type: ignore

collection = db["interviews"]

collection.insert_many(formatted_results)

client.close()

def connect_to_db(collection_name):
load_dotenv()

mongo_uri = os.getenv("MONGODB_URI")
db_name = os.getenv("DB_NAME")

client = MongoClient(mongo_uri, tlsCAFile=certifi.where())

db = client[db_name] # type: ignore

collection = db[collection_name]

return collection, client

def fetch_periods():
collection, client = connect_to_db("periods")

periods = list(collection.find())

client.close()

return periods

def fetch_applicants(periodId):
collection, client = connect_to_db("applications")

applicants = list(collection.find({"periodId": periodId}))

client.close()

return applicants

def fetch_committee_times(periodId):
collection, client = connect_to_db("committees")

committee_times = list(collection.find({"periodId": periodId}))

client.close()

return committee_times

def format_match_results(match_results: MeetingMatch, applicants: List[dict], periodId) -> List[Dict]:
transformed_results = {}

for result in match_results['matchings']:
applicant_id = str(result[0])

if applicant_id not in transformed_results:
transformed_results[applicant_id] = {
"periodId": periodId,
"applicantId": applicant_id,
"interviews": []
}

committee = result[1]
time_interval = result[2]
start = time_interval.start.isoformat()
end = time_interval.end.isoformat()

transformed_results[applicant_id]["interviews"].append({
"start": start,
"end": end,
"committeeName": committee.name
})

return list(transformed_results.values())

def create_applicant_objects(applicants_data: List[dict], all_committees: dict[str, Committee]) -> set[Applicant]:
applicants = set()
for data in applicants_data:
applicant = Applicant(name=str(data['_id']))

optional_committee_names = data.get('optionalCommittees', [])
optional_committees = {all_committees[name] for name in optional_committee_names if name in all_committees}
applicant.add_committees(optional_committees)

preferences = data.get('preferences', {})
preference_committees = {all_committees[committee_name] for committee_name in preferences.values() if committee_name in all_committees}
applicant.add_committees(preference_committees)

for interval_data in data['selectedTimes']:
interval = TimeInterval(
start=datetime.fromisoformat(interval_data['start'].replace("Z", "+00:00")),
end=datetime.fromisoformat(interval_data['end'].replace("Z", "+00:00"))
)
applicant.add_interval(interval)

applicants.add(applicant)
return applicants

def create_committee_objects(committee_data: List[dict]) -> set[Committee]:
committees = set()
for data in committee_data:
committee = Committee(name=data['committee'], interview_length=timedelta(minutes=int(data["timeslot"])))
for interval_data in data['availabletimes']:
interval = TimeInterval(
start=datetime.fromisoformat(interval_data['start'].replace("Z", "+00:00")),
end=datetime.fromisoformat(interval_data['end'].replace("Z", "+00:00"))
)
capacity = interval_data.get('capacity', 1)
committee.add_interval(interval, capacity)
committees.add(committee)
return committees


if __name__ == "__main__":
main()
Binary file modified algorithm/requirements.txt
Binary file not shown.
6 changes: 3 additions & 3 deletions algorithm/src/mip_matching/Applicant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
# Unngår cyclic import
from Committee import Committee
from TimeInterval import TimeInterval
from mip_matching.Committee import Committee
from mip_matching.TimeInterval import TimeInterval

import itertools

Expand Down Expand Up @@ -83,4 +83,4 @@ def __str__(self) -> str:
return self.name

def __repr__(self) -> str:
return str(self)
return str(self)
11 changes: 2 additions & 9 deletions algorithm/src/mip_matching/Committee.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
from __future__ import annotations
from datetime import timedelta
import sys
print(sys.path)
print(__name__)
# sys.path.append("C:\\Users\\Jørgen Galdal\\Documents\\lokalSkoleprogrammering\\appkom\\OnlineOpptak\\algorithm\\mip_matching")

from mip_matching.Applicant import Applicant
from mip_matching.TimeInterval import TimeInterval

from typing import Iterator
# from typing import TYPE_CHECKING
# if TYPE_CHECKING:
# # Unngår cyclic import
from mip_matching.TimeInterval import TimeInterval


class Committee:
Expand Down Expand Up @@ -84,4 +77,4 @@ def __repr__(self):


if __name__ == "__main__":
print("running")
print("running")
26 changes: 0 additions & 26 deletions algorithm/src/mip_matching/TimeInterval.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,29 +85,3 @@ def divide_interval(interval: TimeInterval, length: timedelta) -> list[TimeInter
local_end += length

return result


"""
Dette er gammel kode som nå er flyttet til de passende komité-/søker-klassene.
Foreløpig beholdt for referanse.
"""
# class TimeIntervals:
# def __init__(self, initial_list: list[TimeInterval] = None):
# self.list: list[TimeInterval] = initial_list if initial_list else []

# def add(self, interval: TimeInterval):
# self.list.append(interval)

# def recursive_intersection(self, other: TimeIntervals):
# """
# Returnerer alle tidsintervallene i *other* som er inneholdt i et av *self* sine intervaller"""
# result = TimeIntervals()

# for self_interval, other_interval in itertools.product(self.list, other.list):
# if self_interval.contains(other_interval):
# result.add(other_interval)

# return result

# def __iter__(self):
# return self.list.__iter__()
5 changes: 1 addition & 4 deletions algorithm/src/mip_matching/match_meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
from mip_matching.Applicant import Applicant
import mip

# from typing import TypedDict


class MeetingMatch(TypedDict):
"""Type definition of a meeting match object"""
solver_status: mip.OptimizationStatus
Expand Down Expand Up @@ -83,4 +80,4 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me
"matchings": matchings,
}

return match_object
return match_object
41 changes: 41 additions & 0 deletions components/CommitteeAboutCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { owCommitteeType } from "../lib/types/types";

interface CommitteeAboutCardProps {
committee: owCommitteeType;
hasPeriod: boolean;
}

const CommitteeAboutCard = ({
committee,
hasPeriod,
}: CommitteeAboutCardProps) => {
const { image, name_long, name_short, email, application_description } =
committee;

return (
<div>
<img
src={image?.sm || "/Online_svart_o.svg"}
alt={name_long}
className="w-16 h-16 p-1 mb-2 bg-white rounded-full md:w-24 md:h-24"
/>

<div className="flex items-center gap-2">
<h3 className="text-xl font-bold dark:text-white">
{name_long} {name_long !== name_short && `(${name_short})`}
</h3>
{hasPeriod && (
<span className="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-green-900 dark:text-green-300">
Har opptak!
</span>
)}
</div>
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">{email}</p>
<p className="text-gray-500 whitespace-pre-wrap dark:text-gray-400">
{application_description || "Ingen opptaksbeskrivelse"}
</p>
</div>
);
};

export default CommitteeAboutCard;
Loading

0 comments on commit 60ff2ed

Please sign in to comment.