diff --git a/edenai_apis/api_keys/senseloaf_settings_template.json b/edenai_apis/api_keys/senseloaf_settings_template.json new file mode 100644 index 00000000..b12e7e1f --- /dev/null +++ b/edenai_apis/api_keys/senseloaf_settings_template.json @@ -0,0 +1,7 @@ +{ + "api_key": "", + "email": "", + "password": "", + "comment": "You can either set your API Key, or your Login Credentials (Email and Password) for Authentication" +} + diff --git a/edenai_apis/apis/__init__.py b/edenai_apis/apis/__init__.py index 9780ea94..7f936465 100644 --- a/edenai_apis/apis/__init__.py +++ b/edenai_apis/apis/__init__.py @@ -56,5 +56,6 @@ from .winstonai import WinstonaiApi from .vernai import VernaiApi from .readyredact import ReadyRedactApi +from .senseloaf import SenseloafApi # THIS NEEDS TO BE DONE AUTOMATICALLY diff --git a/edenai_apis/apis/senseloaf/__init__.py b/edenai_apis/apis/senseloaf/__init__.py new file mode 100644 index 00000000..81823fef --- /dev/null +++ b/edenai_apis/apis/senseloaf/__init__.py @@ -0,0 +1 @@ +from .senseloaf_api import SenseloafApi \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/client.py b/edenai_apis/apis/senseloaf/client.py new file mode 100644 index 00000000..74e46af3 --- /dev/null +++ b/edenai_apis/apis/senseloaf/client.py @@ -0,0 +1,352 @@ +from edenai_apis.utils.http import HTTPMethod +from edenai_apis.utils.exception import ProviderException +import requests +from enum import Enum +from typing import Optional +from http import HTTPStatus +import json +from json import JSONDecodeError +import tempfile, os, mimetypes + +from .models import ResponseData + +class Parser(Enum): + + RESUME = 'resume' + JD = 'job_description' + INVOICE = 'invoice' # Coming Soon + RECEIPT = 'receipt' # Coming Soon + INDOID = 'indonesian_id' # Coming Soon + AADHAAR = 'aadhaar' # Coming Soon + + + +class Client: + + BASE_URL = 'https://service.senseloaf.com' + __api_key: Optional[str] + __last_api_response: Optional[dict] + __last_api_response_type: Optional[str] + __last_api_response_code: Optional[str] + + def __init__( + self, + api_key: Optional[str] = None, + email: Optional[str] = None, + password: Optional[str] = None + ) -> None: + if all([i == '' for i in [api_key, email, password]]): + raise ProviderException('Please provide api_key or email and password for authentication') + if api_key == '' and (email == '' or password == ''): + raise ProviderException('Please provide both email and password for authentication') + if api_key: + self.__api_key = api_key.replace('Bearer ', '') + else: + self.__login(email, password) + + + def __request( + self, + method: HTTPMethod, + url: str, + data: Optional[dict] = None, + headers: Optional[dict] = None, + json_field: Optional[dict] = None, + files: Optional[dict] = None, + params: Optional[dict] = None, + return_type: Optional[str] = 'json' + ) -> ResponseData: + response: requests.Response = requests.request( + method=method.value, + url=url, + data=data, + params=params, + files=files, + headers=headers, + json=json_field, + ) + + try: + response.raise_for_status() + + if response.status_code == HTTPStatus.NO_CONTENT: + return ResponseData( + response={}, + response_code=str(response.status_code), + response_type='json' + ) + + if return_type == 'headers': + self.__last_api_response = response.headers + self.__last_api_response_type = 'headers' + self.__last_api_response_code = str(response.status_code) + return ResponseData( + response=response.headers, + response_code=str(response.status_code), + response_type='headers' + ) + elif return_type == 'content': + self.__last_api_response = response.content + self.__last_api_response_type = 'content' + self.__last_api_response_code = str(response.status_code) + return ResponseData( + response={'content': response.content}, + response_code=str(response.status_code), + response_type='content' + ) + elif return_type == 'text': + self.__last_api_response = response.text + self.__last_api_response_type = 'text' + self.__last_api_response_code = str(response.status_code) + return ResponseData( + response={'text': response.text}, + response_code=str(response.status_code), + response_type='text' + ) + else: + self.__last_api_response = response.json() + self.__last_api_response_type = 'json' + self.__last_api_response_code = str(response.status_code) + return ResponseData( + response=response.json(), + response_code=str(response.status_code), + response_type='json' + ) + except requests.exceptions.HTTPError as exc: + message = json.loads(exc.response.text) + if message.get('errorCode') == 'AUTHENTICATION_FAILED': + message['errorMessage'] = 'Authentication Failed. Please check your EmailID/Password Combination or API Key' + message['response']['error']['faultDetail'] = ['Authentication Failed. Please check your EmailID/Password Combination or API Key'] + else: + message = exc.response.text + raise ProviderException( + message=f"{exc}\nError message: {message}", + code=str(response.status_code), + ) from exc + except JSONDecodeError: + raise ProviderException( + message="Internal server error", code=str(response.status_code) + ) + + + def __login(self, email, password): + url = f"{self.BASE_URL}/login" + headers = { + "Content-Type": "application/json", + } + payload = json.dumps({ + "emailId": email, + "password": password + }) + response = self.__request( + method=HTTPMethod.POST, + url=url, + data=payload, + headers=headers, + json_field=None, + files=None, + params=None, + return_type='headers' + ) + self.__api_key = response.response.get('Authorization').replace("Bearer ", '') + + + def __parse_resume_from_file( + self, + file: str + ) -> ResponseData: + url = f'{self.BASE_URL}/api/v2/parse-resume' + files = [ + ('files',(file.split('/')[-1], open(file,'rb'),mimetypes.guess_type(file)[0])) + ] + headers = { + 'Authorization': f'Bearer {self.__api_key}' + } + response = self.__request( + method=HTTPMethod.POST, + url=url, + data=None, + headers=headers, + json_field=None, + files=files, + params=None, + return_type='json' + ) + if response.response_code != '200': + raise ProviderException( + message=response.response, + code=response.response_code + ) + + else: + errors = response.response.get('errors', []) + if len(errors) > 0: + raise ProviderException( + message=errors[0], + code=response.response_code + ) + else: + return response + + + def __parse_resume_from_url( + self, + url: str + ) -> ResponseData: + parser_url = f'{self.BASE_URL}/api/v2/parse-resume-url' + data = json.dumps({ + 'resumeUrl': url + }) + headers = { + 'Authorization': f'Bearer {self.__api_key}' + } + response = self.__request( + method=HTTPMethod.POST, + url=parser_url, + data=data, + headers=headers, + json_field=None, + files=None, + params=None, + return_type='json' + ) + if str(response.response_code) != 200: + raise ProviderException( + message=response.response.get('message'), + code=response.response_code + ) + + else: + errors = response.response.get('errors', []) + if len(errors) > 0: + raise ProviderException( + message=errors[0].get('message'), + code=errors[0].get('code') + ) + else: + return response + + + def __parse_jd_from_file( + self, + file: str + ) -> ResponseData: + url = f'{self.BASE_URL}/api/parse-jd' + files = [ + ('files',(file, open(file,'rb'),'application/pdf')) + ] + headers = { + 'Authorization': f'Bearer {self.__api_key}' + } + response = self.__request( + method=HTTPMethod.POST, + url=url, + data=None, + headers=headers, + json_field=None, + files=files, + params=None, + return_type='json' + ) + if str(response.response_code) != 200: + raise ProviderException( + message=response.response.get('message'), + code=response.response_code + ) + else: + errors = response.response.get('errors', []) + if len(errors) > 0: + raise ProviderException( + message=errors[0].get('message'), + code=errors[0].get('code') + ) + else: + return response + + + def __parse_jd_from_url( + self, + url: str + ) -> ResponseData: + tempdir = tempfile.gettempdir() + filename = url.split('/')[-1] + filepath = os.path.join(tempdir, filename) + with open(filepath, 'wb') as f: + f.write(requests.get(url).content) + return self.__parse_jd_from_file(filepath) + + + def __parse_resume( + self, + file: Optional[str] = '', + url: Optional[str] = '' + ) -> ResponseData: + if file == '' and url == '': + raise ProviderException('Please provide path to file or url for parsing resume') + elif file != '' and url != '': + raise ProviderException('Please provide file or url for parsing resume, not both') + elif file != '': + return self.__parse_resume_from_file(file) + elif url != '': + return self.__parse_resume_from_url(url) + + + + def __parse_jd( + self, + file: Optional[str] = None, + url: Optional[str] = None + ): + if file == '' and url == '': + raise ProviderException('Please provide path to file or url for parsing resume') + elif file != '' and url != '': + raise ProviderException('Please provide file or url for parsing resume, not both') + elif file != '': + return self.__parse_jd_from_file(file) + elif url != '': + return self.__parse_jd_from_url(url) + + + def parse_document( + self, + parse_type: Parser, + file: Optional[str] = '', + url: Optional[str] = '' + ) -> ResponseData: + if parse_type.value == 'resume': + return self.__parse_resume(file, url) + elif parse_type.value == 'job_description': + return self.__parse_jd(file, url) + elif parse_type.value == 'invoice': + raise NotImplementedError( + 'Invoice parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release' + ) + elif parse_type.value == 'receipt': + raise NotImplementedError( + 'Receipt parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release' + ) + elif parse_type.value == 'indonesian_id': + raise NotImplementedError( + 'Indonesian ID card parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release' + ) + elif parse_type.value == 'aadhaar': + raise NotImplementedError( + 'Indian Aadhaar card parsing is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release' + ) + else: + raise NotImplementedError( + f'Parsing type {parse_type} is not implemented yet. Reach out to us at team@senseloaf.com for requesting early release' + ) + + @property + def cache(self): + return ResponseData( + response = self.__last_api_response, + response_code = self.__last_api_response_code, + response_type = self.__last_api_response_type + ) + + def clear_cache(self): + self.__last_api_response = {} + self.__last_api_response_code = '' + self.__last_api_response_type = '' \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/errors.py b/edenai_apis/apis/senseloaf/errors.py new file mode 100644 index 00000000..76795704 --- /dev/null +++ b/edenai_apis/apis/senseloaf/errors.py @@ -0,0 +1,8 @@ +from edenai_apis.utils.exception import ( + ProviderErrorLists, + ProviderInternalServerError +) + +ERRORS: ProviderErrorLists = { + ProviderInternalServerError : [r".*Internal server error.*"], +} \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/info.json b/edenai_apis/apis/senseloaf/info.json new file mode 100644 index 00000000..3b67593f --- /dev/null +++ b/edenai_apis/apis/senseloaf/info.json @@ -0,0 +1,21 @@ +{ + "ocr": { + "resume_parser": { + "constraints": { + "file_types": [ + "application/pdf", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "image/png", + "image/jpeg" + ], + "languages": [ + "en" + ], + "allow_null_language": true + }, + "version": "v3" + } + } + } + \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/models.py b/edenai_apis/apis/senseloaf/models.py new file mode 100644 index 00000000..d60bdaf0 --- /dev/null +++ b/edenai_apis/apis/senseloaf/models.py @@ -0,0 +1,7 @@ +from typing import Dict +from pydantic import StrictStr, BaseModel + +class ResponseData(BaseModel): + response: Dict + response_code: StrictStr + response_type: StrictStr \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/outputs/ocr/resume_parser_output.json b/edenai_apis/apis/senseloaf/outputs/ocr/resume_parser_output.json new file mode 100644 index 00000000..b767189a --- /dev/null +++ b/edenai_apis/apis/senseloaf/outputs/ocr/resume_parser_output.json @@ -0,0 +1,404 @@ +{ + "original_response": { + "rawText": "Functional Resume Sample John W. Smith 2002 Front Range Way Fort Collins, CO 80525 jwsmith@colostate.edu Career Summary Four years experience in early childhood development with a diverse background in the care of special needs children and adults. Adult Care Experience \u2022 Determined work placement for 150 special needs adult clients. \u2022 Maintained client databases and records. \u2022 Coordinated client contact with local health care professionals on a monthly basis. \u2022 Managed 25 volunteer workers. Childcare Experience \u2022 Coordinated service assignments for 20 part-time counselors and 100 client families. \u2022 Oversaw daily activity and outing planning for 100 clients. \u2022 Assisted families of special needs clients with researching financial assistance and healthcare. \u2022 Assisted teachers with managing daily classroom activities. \u2022 Oversaw daily and special student activities. Employment History 1999-2002 Counseling Supervisor, The Wesley Center, Little Rock, Arkansas. 1997-1999 Client Specialist, Rainbow Special Care Center, Little Rock, Arkansas 1996-1997 Teacher\u2019s Assistant, Cowell Elementary, Conway, Arkansas Education University of Arkansas at Little Rock, Little Rock, AR \u2022 BS in Early Childhood Development (1999) \u2022 BA in Elementary Education (1998) \u2022 GPA (4.0 Scale): Early Childhood Development \u2013 3.8, Elementary Education \u2013 3.5, Overall 3.4. \u2022 Dean\u2019s List, Chancellor\u2019s List", + "skills": [ + { + "SkillName": "Developmental Psychology", + "SkillType": [ + "Hard Skills" + ], + "emsiId": "KS121VT753QVH9FM0851", + "senseloafId": 16738, + "rawSkill": "early childhood development" + }, + { + "SkillName": "Special Needs Children", + "SkillType": [ + "Hard Skills" + ], + "emsiId": "BGS4DBAAD99726D5CC5E", + "senseloafId": 15763, + "rawSkill": "special needs children" + }, + { + "SkillName": "Data Base User Interface And Query Software", + "SkillType": [ + "Hard Skills" + ], + "emsiId": "", + "senseloafId": 1262, + "rawSkill": "client databases" + }, + { + "SkillName": "Compliance Software", + "SkillType": [ + "Hard Skills" + ], + "emsiId": "", + "senseloafId": 1637, + "rawSkill": "health" + }, + { + "SkillName": "Planning", + "SkillType": [ + "Soft Skills" + ], + "emsiId": "ESEB1D4619E6E83A061D", + "senseloafId": 5526, + "rawSkill": "planning" + }, + { + "SkillName": "Research", + "SkillType": [ + "Soft Skills" + ], + "emsiId": "KS1203C6N9B52QGB4H67", + "senseloafId": 22689, + "rawSkill": "researching" + }, + { + "SkillName": "Finance", + "SkillType": [ + "Hard Skills" + ], + "emsiId": "KS123MC78KV644P5DDZ0", + "senseloafId": 31576, + "rawSkill": "financial" + }, + { + "SkillName": "Management", + "SkillType": [ + "Soft Skills" + ], + "emsiId": "KS1218W78FGVPVP2KXPX", + "senseloafId": 30255, + "rawSkill": "managing" + } + ], + "education": { + "EducationSection": [ + { + "Collegename": "University of Arkansas", + "Degree": "BS", + "EduEndDate": { + "month": 11, + "year": 1999 + }, + "EduStartDate": { + "month": 0, + "year": 0 + }, + "Location": "Little Rock Little Rock AR", + "Major": "Early Childhood Development", + "Grade": "4.03.83.5", + "DegreeType": "General Studies", + "MaxGrade": "", + "GradeType": "" + } + ], + "HighestEducation": "BS" + }, + "experience": { + "ExperienceSection": [ + { + "CompanyName": "The Wesley Center", + "ExpEndDate": { + "month": 11, + "year": 2002 + }, + "ExpLocation": "Little Rock , Arkansas", + "ExpStartDate": { + "month": 11, + "year": 1999 + }, + "JobTitle": "Counseling Supervisor", + "Description": "" + }, + { + "CompanyName": "Rainbow Special Care Center", + "ExpEndDate": { + "month": 11, + "year": 1999 + }, + "ExpLocation": "Little Rock , Arkansas", + "ExpStartDate": { + "month": 11, + "year": 1997 + }, + "JobTitle": "Client Specialist", + "Description": "" + }, + { + "CompanyName": "Cowell Elementary", + "ExpEndDate": { + "month": 11, + "year": 1997 + }, + "ExpLocation": "Conway , Arkansas", + "ExpStartDate": { + "month": 11, + "year": 1996 + }, + "JobTitle": "Teacher s Assistant", + "Description": "" + } + ], + "TotalExperience": 6.0, + "CurrentCompany": "The Wesley Center", + "CurrentJobRole": "Counseling Supervisor", + "AverageExperience": 2.0 + }, + "socialLinks": { + "urls": [], + "linkedin": "", + "facebook": "", + "github": "", + "stackoverflow": "", + "twitter": "", + "skypeid": "", + "angelList": "" + }, + "contactInfo": { + "firstName": "John", + "lastName": "Smith", + "title": "", + "middleName": "W." + }, + "contactMethod": { + "ContactEmailid": [ + "jwsmith@colostate.edu" + ], + "PostalAddress": { + "Location": { + "Area": "2002 Front Range Way", + "City": "Fort Collins", + "Country": "", + "State": "CO", + "Zip": "80525", + "CountryCode": "", + "Latitude": "", + "Longitude": "" + } + }, + "Telephone": [] + }, + "sections": [ + { + "bbox": { + "h": 75, + "w": 348, + "x": 72, + "y": 38 + }, + "sectionName": "Personal_info", + "sectionText": "Functional Resume Sample John W. Smith 2002 Front Range Way Fort Collins , CO 80525 jwsmith@colostate.edu", + "pageNumber": 1 + }, + { + "bbox": { + "h": 143, + "w": 460, + "x": 71, + "y": 132 + }, + "sectionName": "Summary", + "sectionText": "Career Summary Four years experience in early childhood development with a diverse background in the care of special needs children and adults . Adult Care Experience \u2022 Determined work placement for 150 special needs adult clients . Maintained client databases and records . Coordinated client contact with local health care professionals on a monthly basis . Managed 25 volunteer workers .", + "pageNumber": 1 + }, + { + "bbox": { + "h": 195, + "w": 444, + "x": 71, + "y": 292 + }, + "sectionName": "Experience", + "sectionText": "Childcare Experience Coordinated service assignments for 20 part - time counselors and 100 client families . Oversaw daily activity and outing planning for 100 clients . \u2022 Assisted families of special needs clients with researching financial assistance and healthcare . Assisted teachers with managing daily classroom activities . \u2022 Oversaw daily and special student activities . Employment History 1999-2002 Counseling Supervisor , The Wesley Center , Little Rock , Arkansas . 1997-1999 Client Specialist , Rainbow Special Care Center , Little Rock , Arkansas 1996-1997 Teacher's Assistant , Cowell Elementary , Conway , Arkansas", + "pageNumber": 1 + }, + { + "bbox": { + "h": 120, + "w": 441, + "x": 72, + "y": 504 + }, + "sectionName": "Education", + "sectionText": "Education University of Arkansas at Little Rock , Little Rock , AR \u2022 BS in Early Childhood Development ( 1999 ) \u2022 BA in Elementary Education ( 1998 ) \u2022 GPA ( 4.0 Scale ) : Early Childhood Development - 3.8 , Elementary Education - 3.5 , Overall 3.4 . \u2022 Dean's List , Chancellor's List", + "pageNumber": 1 + } + ] + }, + "standardized_response": { + "extracted_data": { + "personal_infos": { + "name": { + "first_name": "John", + "last_name": "Smith", + "raw_name": "John W. Smith", + "middle": "W.", + "title": "", + "prefix": null, + "sufix": null + }, + "address": { + "formatted_location": "2002 Front Range Way Fort Collins CO 80525", + "postal_code": "80525", + "region": "CO", + "country": "", + "country_code": "", + "raw_input_location": null, + "street": "2002 Front Range Way", + "street_number": null, + "appartment_number": null, + "city": "Fort Collins" + }, + "self_summary": "Career Summary Four years experience in early childhood development with a diverse background in the care of special needs children and adults . Adult Care Experience \u2022 Determined work placement for 150 special needs adult clients . Maintained client databases and records . Coordinated client contact with local health care professionals on a monthly basis . Managed 25 volunteer workers .", + "objective": "", + "date_of_birth": null, + "place_of_birth": null, + "phones": [], + "mails": [ + "jwsmith@colostate.edu" + ], + "urls": [], + "fax": [], + "current_profession": "Counseling Supervisor", + "gender": null, + "nationality": null, + "martial_status": null, + "current_salary": null + }, + "education": { + "total_years_education": null, + "entries": [ + { + "title": "BS", + "start_date": null, + "end_date": "01-11-1999", + "location": { + "formatted_location": "Little Rock Little Rock AR", + "postal_code": null, + "region": null, + "country": null, + "country_code": null, + "raw_input_location": null, + "street": null, + "street_number": null, + "appartment_number": null, + "city": null + }, + "establishment": "University of Arkansas", + "description": null, + "gpa": "4.03.83.5", + "accreditation": "Early Childhood Development" + } + ] + }, + "work_experience": { + "total_years_experience": "6.0", + "entries": [ + { + "title": "Counseling Supervisor", + "start_date": "01-11-1999", + "end_date": "01-11-2002", + "company": "The Wesley Center", + "location": { + "formatted_location": "Little Rock , Arkansas", + "postal_code": null, + "region": null, + "country": null, + "country_code": null, + "raw_input_location": null, + "street": null, + "street_number": null, + "appartment_number": null, + "city": null + }, + "description": "", + "industry": null + }, + { + "title": "Client Specialist", + "start_date": "01-11-1997", + "end_date": "01-11-1999", + "company": "Rainbow Special Care Center", + "location": { + "formatted_location": "Little Rock , Arkansas", + "postal_code": null, + "region": null, + "country": null, + "country_code": null, + "raw_input_location": null, + "street": null, + "street_number": null, + "appartment_number": null, + "city": null + }, + "description": "", + "industry": null + }, + { + "title": "Teacher s Assistant", + "start_date": "01-11-1996", + "end_date": "01-11-1997", + "company": "Cowell Elementary", + "location": { + "formatted_location": "Conway , Arkansas", + "postal_code": null, + "region": null, + "country": null, + "country_code": null, + "raw_input_location": null, + "street": null, + "street_number": null, + "appartment_number": null, + "city": null + }, + "description": "", + "industry": null + } + ] + }, + "languages": [], + "skills": [ + { + "name": "Developmental Psychology", + "type": "Hard Skills" + }, + { + "name": "Special Needs Children", + "type": "Hard Skills" + }, + { + "name": "Data Base User Interface And Query Software", + "type": "Hard Skills" + }, + { + "name": "Compliance Software", + "type": "Hard Skills" + }, + { + "name": "Planning", + "type": "Soft Skills" + }, + { + "name": "Research", + "type": "Soft Skills" + }, + { + "name": "Finance", + "type": "Hard Skills" + }, + { + "name": "Management", + "type": "Soft Skills" + } + ], + "certifications": [], + "courses": [], + "publications": [], + "interests": [] + } + } +} \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/remapping.py b/edenai_apis/apis/senseloaf/remapping.py new file mode 100644 index 00000000..ae131d77 --- /dev/null +++ b/edenai_apis/apis/senseloaf/remapping.py @@ -0,0 +1,195 @@ +from typing import Any, Dict, List, Tuple + +from edenai_apis.features.ocr.resume_parser import ( + ResumeEducation, + ResumeEducationEntry, + ResumeLocation, + ResumePersonalInfo, + ResumePersonalName, + ResumeExtractedData, + ResumeLang, + ResumeParserDataClass, + ResumeSkill, + ResumeWorkExp, + ResumeWorkExpEntry, +) +from edenai_apis.utils.conversion import _convert_dictionary_to_date_string +from .models import ResponseData + +class ResumeMapper: + + __original_response: Dict[str, Any] + __standard_response: Dict[str, Any] + + def __init__( + self, + original_response: ResponseData + ) -> None: + self.__original_response = original_response.response['result'][0]['content'] + self.__standard_response = {} + self.map_response() + + def __map_name(self) -> ResumePersonalName: + name = self.__original_response.get("contactInfo", {}) + raw_name = (name.get('title') + ' ' + name.get('firstName') + ' ' + name.get('middleName') + ' ' + name.get('lastName')).strip(' ').replace(' ', ' ') + return ResumePersonalName( + raw_name=raw_name, + first_name=name.get('firstName'), + last_name=name.get('lastName'), + middle=name.get('middleName'), + title=name.get('title'), + sufix=None, + prefix=None, + ) + + def __map_location(self) -> ResumeLocation: + location = self.__original_response['contactMethod']['PostalAddress']['Location'] + raw_location = (location['Area'] + ' ' + location['City'] + ' ' + location['State'] + ' ' + location['Country'] + ' ' + location['Zip']).strip(' ').replace(' ', ' ') + return ResumeLocation( + raw_input_location=None, + postal_code=location.get("Zip"), + region=location.get("State"), + country_code=location.get("CountryCode"), + country=location.get("Country"), + appartment_number=None, + city=location.get("City"), + street=location.get("Area"), + street_number=None, + formatted_location=raw_location + ) + + def __get_summary(self) -> str: + summaries = [i['sectionText'] for i in self.__original_response['sections'] if i['sectionName'] == 'Summary'] + return ' '.join(summaries) + + def __get_objective(self) -> str: + objectives = [i['sectionText'] for i in self.__original_response['sections'] if i['sectionName'] == 'Objective'] + return ' '.join(objectives) + + def __map_pi(self) -> ResumePersonalInfo: + self.__standard_response['personal_infos'] = ResumePersonalInfo( + name = self.__map_name(), + address = self.__map_location(), + self_summary = self.__get_summary(), + objective = self.__get_objective(), + date_of_birth = None, + place_of_birth = None, + phones = self.__original_response['contactMethod']['Telephone'], + mails = self.__original_response['contactMethod']['ContactEmailid'], + urls = self.__original_response['socialLinks']['urls'], + fax = [], + current_profession = self.__original_response['experience']['CurrentJobRole'], + gender = None, + nationality = None, + martial_status = None, + current_salary = None + ) + return self.__standard_response["personal_infos"] + + def __map_education(self) -> ResumeEducation: + edu_entries: List[ResumeEducationEntry] = [] + for i in self.__original_response['education']['EducationSection']: + location = ResumeLocation( + formatted_location=i['Location'], + raw_input_location=None, + postal_code=None, + region=None, + country_code=None, + country=None, + appartment_number=None, + city=None, + street=None, + street_number=None + ) + start_date = _convert_dictionary_to_date_string(i['EduStartDate']) + end_date = _convert_dictionary_to_date_string(i['EduEndDate']) + edu_entries.append( + ResumeEducationEntry( + title = i['Degree'], + start_date = start_date, + end_date = end_date, + location = location, + establishment = i['Collegename'], + description = None, + gpa = i['Grade'], + accreditation = i['Major'] + ) + ) + self.__standard_response["education"] = ResumeEducation( + entries=edu_entries, total_years_education=None + ) + return self.__standard_response["education"] + + def __map_experience(self) -> ResumeWorkExp: + work_entries: List[ResumeWorkExpEntry] = [] + for i in self.__original_response['experience']['ExperienceSection']: + location = ResumeLocation( + formatted_location=i['ExpLocation'], + raw_input_location=None, + postal_code=None, + region=None, + country_code=None, + country=None, + appartment_number=None, + city=None, + street=None, + street_number=None + ) + start_date = _convert_dictionary_to_date_string(i['ExpStartDate']) + end_date = _convert_dictionary_to_date_string(i['ExpEndDate']) + work_entries.append( + ResumeWorkExpEntry( + title = i['JobTitle'], + company = i['CompanyName'], + start_date = start_date, + end_date = end_date, + location = location, + industry = None, + description = i['Description'] + ) + ) + self.__standard_response["work_experience"] = ResumeWorkExp( + total_years_experience=str(self.__original_response['experience']['TotalExperience']), + entries=work_entries + ) + + return self.__standard_response["work_experience"] + + def __map_skills(self) -> List[ResumeSkill]: + self.__standard_response["skills"] = [] + for i in self.__original_response.get("skills", []) or []: + skill_name = i.get("SkillName") + skill_type = i.get("SkillType", []) + skill_type = None if len(skill_type) == 0 else skill_type[0] + self.__standard_response["skills"].append( + ResumeSkill(name=skill_name, type=skill_type) + ) + return self.__standard_response["skills"] + + def __map_others(self) -> Tuple: + self.__standard_response['languages'] = [] + self.__standard_response['certifications'] = [] + self.__standard_response['publications'] = [] + + return ( + self.__standard_response['languages'], + self.__standard_response['certifications'], + self.__standard_response['publications'] + ) + + def map_response(self) -> None: + self.__map_pi() + self.__map_education() + self.__map_experience() + self.__map_skills() + self.__map_others() + + + def standard_response(self): + return ResumeParserDataClass( + extracted_data = ResumeExtractedData(**self.__standard_response) + ) + + + def original_response(self): + return self.__original_response \ No newline at end of file diff --git a/edenai_apis/apis/senseloaf/senseloaf_api.py b/edenai_apis/apis/senseloaf/senseloaf_api.py new file mode 100644 index 00000000..5e6d71ed --- /dev/null +++ b/edenai_apis/apis/senseloaf/senseloaf_api.py @@ -0,0 +1,43 @@ +from typing import Dict + +from edenai_apis.features import OcrInterface +from edenai_apis.loaders.data_loader import ProviderDataEnum +from edenai_apis.loaders.loaders import load_provider +from edenai_apis.features.provider.provider_interface import ProviderInterface +from edenai_apis.utils.types import ResponseType +from edenai_apis.features.ocr import ResumeParserDataClass + +from .client import Client, Parser +from .remapping import ResumeMapper + + +class SenseloafApi(ProviderInterface, OcrInterface): + provider_name = "senseloaf" + + def __init__(self, api_keys: Dict = {}): + super().__init__() + self.api_settings = load_provider( + ProviderDataEnum.KEY, self.provider_name, api_keys=api_keys + ) + self.client = Client( + self.api_settings.get("api_key", None), + self.api_settings.get("email", None), + self.api_settings.get("password", None) + ) + + def ocr__resume_parser( + self, file: str, file_url: str = "" + ) -> ResponseType[ResumeParserDataClass]: + + original_response = self.client.parse_document( + parse_type = Parser.RESUME, + file = file, + url = file_url + ) + + mapper = ResumeMapper(original_response) + + return ResponseType[ResumeParserDataClass]( + original_response=mapper.original_response(), + standardized_response=mapper.standard_response() + ) \ No newline at end of file diff --git a/edenai_apis/utils/conversion.py b/edenai_apis/utils/conversion.py index b9fe8568..b63c1393 100644 --- a/edenai_apis/utils/conversion.py +++ b/edenai_apis/utils/conversion.py @@ -50,6 +50,19 @@ def convert_string_to_number( except Exception as exc: print(exc) return None + +def _convert_dictionary_to_date_string(dictionary: Dict[str, Any]) -> Tuple[str, None]: + year = dictionary['year'] + month = dictionary['month'] + if year == -1: + year = dt.datetime.now().year + if month == -1: + month = dt.datetime.now().month + try: + ret = dt.datetime(year=dictionary["year"], month=dictionary["month"], day=1).strftime("%d-%m-%Y") + except: + ret = None + return ret def closest_above_value(input_list, input_value):