File size: 8,531 Bytes
cca012f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import sqlite3
import json
import os
from typing import List, Optional, Dict, Any
from tech_radar.db.models import JobPosting

class DatabaseManager:
    """Thread-safe SQLite database manager for TechRadar-MCP across all domains & cities."""

    def __init__(self, db_path: str = "tech_radar.db"):
        self.db_path = db_path
        self._init_db()

    def _get_connection(self) -> sqlite3.Connection:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        return conn

    def _init_db(self):
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS job_postings (
                    id TEXT PRIMARY KEY,
                    title TEXT NOT NULL,
                    company TEXT NOT NULL,
                    tech_domain TEXT NOT NULL,
                    city TEXT NOT NULL,
                    area TEXT NOT NULL,
                    salary_min_lpa REAL NOT NULL,
                    salary_max_lpa REAL NOT NULL,
                    experience_min_years INTEGER NOT NULL,
                    experience_max_years INTEGER NOT NULL,
                    tech_stack TEXT NOT NULL, -- JSON List
                    requirements TEXT NOT NULL,
                    work_mode TEXT NOT NULL,
                    company_tier TEXT NOT NULL,
                    posted_date TEXT NOT NULL,
                    url TEXT
                )
            """)
            conn.commit()

    def save_job_posting(self, job: JobPosting) -> bool:
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT OR REPLACE INTO job_postings (
                    id, title, company, tech_domain, city, area, salary_min_lpa, salary_max_lpa,
                    experience_min_years, experience_max_years, tech_stack, requirements,
                    work_mode, company_tier, posted_date, url
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                job.id, job.title, job.company, job.tech_domain, job.city, job.area,
                job.salary_min_lpa, job.salary_max_lpa,
                job.experience_min_years, job.experience_max_years,
                json.dumps(job.tech_stack), job.requirements,
                job.work_mode, job.company_tier, job.posted_date, job.url
            ))
            conn.commit()
            return True

    def get_job_by_id(self, job_id: str) -> Optional[JobPosting]:
        with self._get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT * FROM job_postings WHERE id = ?", (job_id,))
            row = cursor.fetchone()
            if not row:
                return None
            return self._row_to_job(row)

    def search_jobs(
        self,
        domain: Optional[str] = None,
        city: Optional[str] = None,
        query: Optional[str] = None,
        experience_level: Optional[int] = None,
        min_salary_lpa: Optional[float] = None,
        tech_stack_filter: Optional[List[str]] = None,
        limit: int = 100
    ) -> List[JobPosting]:
        with self._get_connection() as conn:
            cursor = conn.cursor()
            sql = "SELECT * FROM job_postings WHERE 1=1"
            params = []

            if domain and domain.lower() != "all":
                sql += " AND LOWER(tech_domain) = LOWER(?)"
                params.append(domain)

            if city and city.lower() != "all":
                sql += " AND LOWER(city) = LOWER(?)"
                params.append(city)

            if experience_level is not None:
                sql += " AND experience_min_years <= ? AND experience_max_years >= ?"
                params.extend([experience_level, experience_level])

            if min_salary_lpa is not None:
                sql += " AND salary_max_lpa >= ?"
                params.append(min_salary_lpa)

            if query:
                sql += " AND (LOWER(title) LIKE LOWER(?) OR LOWER(company) LIKE LOWER(?) OR LOWER(requirements) LIKE LOWER(?) OR LOWER(area) LIKE LOWER(?))"
                q = f"%{query}%"
                params.extend([q, q, q, q])

            sql += " ORDER BY salary_max_lpa DESC LIMIT ?"
            params.append(limit)

            cursor.execute(sql, params)
            rows = cursor.fetchall()
            jobs = [self._row_to_job(r) for r in rows]

            if tech_stack_filter:
                filter_set = {s.lower() for s in tech_stack_filter}
                jobs = [
                    j for j in jobs
                    if any(ts.lower() in filter_set for ts in j.tech_stack)
                ]

            return jobs

    def get_all_jobs(self) -> List[JobPosting]:
        return self.search_jobs(limit=500)

    def get_market_analytics(self, city: str = "All", domain: str = "All") -> Dict[str, Any]:
        with self._get_connection() as conn:
            cursor = conn.cursor()
            sql = "SELECT COUNT(*), AVG((salary_min_lpa + salary_max_lpa) / 2.0), MIN(salary_min_lpa), MAX(salary_max_lpa) FROM job_postings WHERE 1=1"
            params = []

            if city and city.lower() != "all":
                sql += " AND LOWER(city) = LOWER(?)"
                params.append(city)

            if domain and domain.lower() != "all":
                sql += " AND LOWER(tech_domain) = LOWER(?)"
                params.append(domain)

            cursor.execute(sql, params)
            count, avg_sal, min_sal, max_sal = cursor.fetchone()

            if not count or count == 0:
                return {
                    "city": city,
                    "domain": domain,
                    "total_jobs": 0,
                    "avg_salary_lpa": 0,
                    "salary_range": "N/A",
                    "top_frameworks": [],
                    "top_hubs": [],
                    "top_companies": []
                }

            sql_details = "SELECT tech_stack, area, company FROM job_postings WHERE 1=1"
            params_details = []
            if city and city.lower() != "all":
                sql_details += " AND LOWER(city) = LOWER(?)"
                params_details.append(city)
            if domain and domain.lower() != "all":
                sql_details += " AND LOWER(tech_domain) = LOWER(?)"
                params_details.append(domain)

            cursor.execute(sql_details, params_details)
            rows = cursor.fetchall()

            skill_counts = {}
            hub_counts = {}
            company_set = set()

            for r in rows:
                stacks = json.loads(r["tech_stack"])
                for s in stacks:
                    skill_counts[s] = skill_counts.get(s, 0) + 1
                area = r["area"]
                hub_counts[area] = hub_counts.get(area, 0) + 1
                company_set.add(r["company"])

            sorted_skills = [
                {"skill": k, "count": v, "percentage": round((v / count) * 100, 1)}
                for k, v in sorted(skill_counts.items(), key=lambda x: x[1], reverse=True)[:10]
            ]
            sorted_hubs = [
                {"hub": k, "count": v}
                for k, v in sorted(hub_counts.items(), key=lambda x: x[1], reverse=True)[:5]
            ]

            return {
                "city": city,
                "domain": domain,
                "total_jobs": count,
                "avg_salary_lpa": round(avg_sal or 0, 1),
                "salary_range": f"₹{int(min_sal or 0)}L - ₹{int(max_sal or 0)}L PA",
                "top_frameworks": sorted_skills,
                "top_hubs": sorted_hubs,
                "top_companies": list(company_set)[:10]
            }

    def _row_to_job(self, row: sqlite3.Row) -> JobPosting:
        return JobPosting(
            id=row["id"],
            title=row["title"],
            company=row["company"],
            tech_domain=row["tech_domain"],
            city=row["city"],
            area=row["area"],
            salary_min_lpa=row["salary_min_lpa"],
            salary_max_lpa=row["salary_max_lpa"],
            experience_min_years=row["experience_min_years"],
            experience_max_years=row["experience_max_years"],
            tech_stack=json.loads(row["tech_stack"]),
            requirements=row["requirements"],
            work_mode=row["work_mode"],
            company_tier=row["company_tier"],
            posted_date=row["posted_date"],
            url=row["url"]
        )