File size: 10,733 Bytes
6e54bce
 
 
 
 
 
 
 
f19244a
77c06a9
 
6e54bce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77c06a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03fd32d
29a01f3
77c06a9
 
6e54bce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0cb7604
 
 
6e54bce
 
 
 
 
0cb7604
6e54bce
 
 
 
 
 
 
 
 
 
 
 
 
 
77c06a9
6e54bce
 
77c06a9
 
6e54bce
 
 
 
 
 
 
 
 
 
d1a5731
6e54bce
 
 
 
 
 
 
 
 
 
77c06a9
6e54bce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import streamlit as st
import PyPDF2
import io
import docx2txt
from typing import Optional
import re
from pinecone_handler import PineconeHandler
from time_handling import read_timestamp
from datetime import datetime
import os
from settings import DATE_FORMAT

def extract_text_from_pdf(pdf_file) -> str:
    """Extract text content from PDF file"""
    pdf_reader = PyPDF2.PdfReader(pdf_file)
    text = ""
    for page in pdf_reader.pages:
        text += page.extract_text() + "\n"
    return text

def extract_text_from_docx(docx_file) -> str:
    """Extract text content from DOCX file"""
    text = docx2txt.process(docx_file)
    return text

def extract_resume_text(uploaded_file) -> Optional[str]:
    """Extract text from uploaded resume file"""
    if uploaded_file is None:
        return None
        
    # Get the file extension
    file_extension = uploaded_file.name.split('.')[-1].lower()
    
    try:
        # Process based on file type
        if file_extension == 'pdf':
            return extract_text_from_pdf(uploaded_file)
        elif file_extension in ['docx', 'doc']:
            return extract_text_from_docx(uploaded_file)
        elif file_extension == 'txt':
            return str(uploaded_file.read(), "utf-8")
        else:
            st.error(f"Unsupported file format: {file_extension}")
            return None
    except Exception as e:
        st.error(f"Error processing file: {str(e)}")
        return None

def clean_resume_text(text: str) -> str:
    """Clean and process resume text"""
    if not text:
        return ""
        
    # Remove special characters and extra whitespace
    text = re.sub(r'\s+', ' ', text)
    text = text.strip()
    
    return text

def is_description_truncated(description: str) -> bool:
    """Check if the description appears to be truncated"""
    # Check for obvious truncation indicators
    truncation_indicators = [
        lambda x: len(x) >= 995,  # Close to the 1000 char limit
        lambda x: x.rstrip().endswith(('...', '…')),
        lambda x: re.search(r'\w+$', x) and not re.search(r'[.!?]$', x),  # Ends mid-word or without punctuation
    ]
    
    return any(indicator(description) for indicator in truncation_indicators)

def format_job_description(description: str, truncated: bool = False) -> str:
    """Format job description text with proper sections and line breaks"""
    if not description:
        return ""
    
    # Common section headers in job descriptions
    sections = [
        "About us", "About you", "About the role", "About the position",
        "Requirements", "Qualifications", "Skills", "Responsibilities",
        "What you'll do", "What we offer", "Benefits", "Your profile",
        "Required skills", "What you need", "Who you are"
    ]
    
    # Add line breaks before section headers
    formatted_text = description
    for section in sections:
        # Look for section headers with case-insensitive matching
        pattern = re.compile(f'({section}:?)', re.IGNORECASE)
        formatted_text = pattern.sub(r'\n\n\1', formatted_text)
    
    # Handle bullet points (both • and - symbols)
    formatted_text = re.sub(r'[•-]\s*', '\n• ', formatted_text)
    
    # Add line breaks for sentences that look like list items
    formatted_text = re.sub(r'(?<=\w)\.(?=\s*[A-Z])', '.\n', formatted_text)
    
    # Clean up any excessive line breaks
    formatted_text = re.sub(r'\n{3,}', '\n\n', formatted_text)
    
    if truncated:
        formatted_text = formatted_text.rstrip() + "..."
    
    return formatted_text.strip()



def main():
    # Add custom CSS
    st.markdown("""
        <style>
        .big-font {
            font-size: 24px !important;
            font-weight: bold;
            color: #1E3A8A;
        }
        .update-info {
            padding: 10px;
            background-color: #F3F4F6;
            border-radius: 5px;
            margin: 10px 0;
            font-size: 14px;
            color: #4B5563;
        }
        .step {
            margin: 10px 0;
            padding: 10px;
            background-color: white;
            border-radius: 5px;
            border: 1px solid #E5E7EB;
        }
        </style>
    """, unsafe_allow_html=True)
    
    # Main content
    st.markdown('<p class="big-font">AI-Powered Job Search</p>', unsafe_allow_html=True)
    st.markdown("""
        💼 **Transform Your Job Search with AI**
        
        Tired of searching for jobs? Let AI do the work for you! Upload your resume and discover 
        perfectly matched opportunities in minutes - not hours!
    """)
    
    # Sidebar content
    with st.sidebar:
        st.markdown("### How It Works")
        st.markdown("1. **Upload Resume**\n   PDF, DOCX, DOC, or TXT formats")
        st.markdown("2. **Extract Content**\n   AI processes your resume")
        st.markdown("3. **Smart Search**\n   Match against our database")
        st.markdown("4. **Get Matches**\n   View personalized recommendations")
        
        st.markdown("---")  # Divider
        
        # Database update info
        try:
            last_update = read_timestamp()
            last_update_dt = datetime.strptime(last_update, DATE_FORMAT)
            st.markdown("### Database Status")
            st.markdown("🔄 Updates once a day at midnight.")
            st.markdown(f"**Last update:** {last_update_dt.strftime('%B %d, %Y at %I:%M %p')} (Stockholm Time)")
        except Exception as e:
            st.error(f"Error reading timestamp: {str(e)}")
    
    # Initialize PineconeHandler
    try:
        handler = PineconeHandler()
    except Exception as e:
        st.error(f"Error connecting to Pinecone: {str(e)}")
        return
    
    # File uploader
    uploaded_file = st.file_uploader("Upload your resume", type=['pdf', 'docx', 'doc', 'txt'])
    
    # Search parameters
    num_results = st.slider("Number of results", min_value=1, max_value=20, value=5)
    
    if uploaded_file:
        with st.spinner("Processing resume..."):
            # Extract and clean resume text
            resume_text = extract_resume_text(uploaded_file)
            if resume_text:
                clean_text = clean_resume_text(resume_text)
                
                # Preview extracted text
                with st.expander("Preview extracted text"):
                    st.text(clean_text[:500] + "..." if len(clean_text) > 500 else clean_text)
                
                # Add a city filter textbox above the search button
                city_filter = st.text_input("Filter by city (optional)", value="", help="Enter a city to filter job results by location")

                # Search button
                if st.button("Search Jobs"):
                    with st.spinner("Searching for matching jobs..."):
                        try:
                            # Search for similar job ads
                            results = handler.search_similar_ads(clean_text, top_k=num_results, city=city_filter.strip())
                            
                            if results:
                                st.subheader("Matching Jobs")
                                for i, match in enumerate(results, 1):
                                    metadata = match.metadata
                                    score = match.score
                                    
                                    # Create job card
                                    with st.container():
                                        # Header section with key information
                                        col1, col2 = st.columns([2, 1])
                                        with col1:
                                            st.markdown(f"### {metadata['headline']}")
                                        with col2:
                                            st.markdown(f"**Match Score (Cosine):** {score:.2f}")
                                        
                                        # Job details section
                                        if metadata.get('logo_url'):
                                            st.image(metadata['logo_url'], width=100)
                                        st.markdown(f"**Location:** {metadata['city']}")
                                        st.markdown(f"**Occupation:** {metadata['occupation']}")
                                        st.markdown(f"**Published:** {metadata['published']}")
                                        
                                        # Check if description is truncated
                                        description = metadata['description']
                                        is_truncated = is_description_truncated(description)
                                        
                                        # Display initial description preview
                                        formatted_description = format_job_description(
                                            description[:2000] if is_truncated else description,
                                            truncated=is_truncated
                                        )
                                        st.markdown(formatted_description)
                                        
                                        # If truncated, show expandable full description
                                        if is_truncated:
                                            with st.expander("Read Full Description"):
                                                # Try to fetch full description from webpage_url
                                                st.markdown("""
                                                    **Note:** The full description has been truncated in our database. 
                                                    Please visit the original job posting for complete details and for searching the job.
                                                """)
                                                if metadata.get('webpage_url'):
                                                    st.markdown(f"[View Original Job Posting]({metadata['webpage_url']})")
                                        
                                            st.markdown(f"📧 Contact: {metadata['email']}")
                                        
                                        st.markdown("---")
                            else:
                                st.info("No matching jobs found. Try adjusting your search criteria.")
                                
                        except Exception as e:
                            st.error(f"Error searching jobs: {str(e)}")

if __name__ == "__main__":
    main()