From da078656015b92d6cb1d5aa6649757332024cf5d Mon Sep 17 00:00:00 2001 From: Ayesha Anwar Date: Fri, 24 Jul 2026 17:52:50 +0500 Subject: [PATCH] feat: migrate data layer to SQLite database and add Education module --- .gitignore | 63 +- faker_pk/__init__.py | 63 +- faker_pk/address.py | 90 ++- faker_pk/company.py | 209 +++--- faker_pk/education.py | 95 +++ faker_pk/faker_pk.db | Bin 0 -> 122880 bytes faker_pk/initialize_db.py | 615 ++++++++++++++++++ faker_pk/personal.py | 197 ++---- faker_pk/provider.py | 121 ++-- faker_pk/utils.py | 50 ++ pyproject.toml | 3 +- tests/test_institution_filtered_by_city.py | 8 + tests/test_institution_filtered_by_level.py | 8 + .../test_institution_filtered_by_province.py | 11 + tests/test_realistic_combinations.py | 149 +++++ tests/test_student_dob.py | 20 + tests/test_student_profile_consistency.py | 14 + tests/test_student_profile_structure.py | 9 + 18 files changed, 1292 insertions(+), 433 deletions(-) create mode 100644 faker_pk/education.py create mode 100644 faker_pk/faker_pk.db create mode 100644 faker_pk/initialize_db.py create mode 100644 tests/test_institution_filtered_by_city.py create mode 100644 tests/test_institution_filtered_by_level.py create mode 100644 tests/test_institution_filtered_by_province.py create mode 100644 tests/test_realistic_combinations.py create mode 100644 tests/test_student_dob.py create mode 100644 tests/test_student_profile_consistency.py create mode 100644 tests/test_student_profile_structure.py diff --git a/.gitignore b/.gitignore index 904ea51..a9ace74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,62 @@ +# Byte-compiled / optimized / DLL files __pycache__/ -*.logs/ -.Python -*.txt +*.py[cod] +*$py.class + +# Database locks / journals / temp backups (do NOT ignore faker_pk/faker_pk.db itself) +*.db-journal +*.db-wal +*.db-shm +*.db.bak + +# Distribution / packaging +build/ +develop-eggs/ dist/ -*.egg-info +downloads/ +eggs/ .eggs/ -build/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +# Virtual environments venv/ -.env +.venv/ +.venv +ENV/ +env/ +active_env/ + +# Testing / coverage +.pytest_cache/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.run/ + +# Editors and IDEs +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? -.vscode/ \ No newline at end of file +# Miscellaneous +.DS_Store +*.logs \ No newline at end of file diff --git a/faker_pk/__init__.py b/faker_pk/__init__.py index 454da21..a932ffa 100644 --- a/faker_pk/__init__.py +++ b/faker_pk/__init__.py @@ -2,16 +2,18 @@ from .address import city, province, full_address from .company import company_name, industry_name, iban, bank_name, salary, job_title_with_industry, job_title from .provider import FakerPKProvider - - +from .education import institution, student_dob, student_profile + + + class FakerPK: """Generate fake Pakistani names, addresses, CNICs, phone numbers, and more.""" - - def _generate_multiple(self, func, count): + + def _generate_multiple(self, func, count, **kwargs): """Generate one or many values based on count.""" if count == 1: - return func() - return [func() for _ in range(count)] + return func(**kwargs) + return [func(**kwargs) for _ in range(count)] # -------------------- # Personal Info @@ -22,11 +24,11 @@ def male_name(self, count=1): def female_name(self, count=1): return self._generate_multiple(female_name, count) - def cnic(self, count=1): - return self._generate_multiple(cnic, count) + def cnic(self, count=1, gender=None): + return self._generate_multiple(cnic, count, gender=gender) - def phone_number(self, count=1): - return self._generate_multiple(phone_number, count) + def phone_number(self, count=1, provider=None): + return self._generate_multiple(phone_number, count, provider=provider) def sim_provider(self, count=1): return self._generate_multiple(sim_provider, count) @@ -43,20 +45,20 @@ def dob(self, count=1): # -------------------- # Address Info # -------------------- - def city(self, count=1): - return self._generate_multiple(city, count) + def city(self, count=1, province=None): + return self._generate_multiple(city, count, province=province) - def province(self, count=1): - return self._generate_multiple(province, count) + def province(self, count=1, city=None): + return self._generate_multiple(province, count, city=city) - def full_address(self, count=1): - return self._generate_multiple(full_address, count) + def full_address(self, count=1, city=None, province=None): + return self._generate_multiple(full_address, count, city=city, province=province) # -------------------- # Company Info # -------------------- - def company_name(self, count=1): - return self._generate_multiple(company_name, count) + def company_name(self, count=1, industry=None): + return self._generate_multiple(company_name, count, industry=industry) def industry_name(self, count=1): return self._generate_multiple(industry_name, count) @@ -64,23 +66,28 @@ def industry_name(self, count=1): def bank_name(self, count=1): return self._generate_multiple(bank_name, count) - def iban(self, count=1): - return self._generate_multiple(iban, count) + def iban(self, count=1, bank=None): + return self._generate_multiple(iban, count, bank=bank) # -------------------- # Job Info # -------------------- def job_title(self, count=1, industry=None): - if count == 1: - return job_title(industry=industry) - return [job_title(industry=industry) for _ in range(count)] - + return self._generate_multiple(job_title, count, industry=industry) + def job_title_with_industry(self, count=1): return self._generate_multiple(job_title_with_industry, count) def salary(self, count=1, industry=None): - """Generate random salary from company.py.""" - if count == 1: - return salary(industry=industry) - return [salary(industry=industry) for _ in range(count)] + return self._generate_multiple(salary, count, industry=industry) + + # -------------------- + # Institution Info + # -------------------- + def institution(self, count=1, level=None, city=None, province=None): + return self._generate_multiple(institution, count, level=level, city=city, province=province) + + def student_profile(self, count=1, level=None, province=None): + return self._generate_multiple(student_profile, count, level=level, province=province) + __all__ = ["FakerPK", "FakerPKProvider"] \ No newline at end of file diff --git a/faker_pk/address.py b/faker_pk/address.py index 463716e..e2febb5 100644 --- a/faker_pk/address.py +++ b/faker_pk/address.py @@ -1,52 +1,44 @@ -import random - -# Mapping of provinces to their major cities/towns -PROVINCE_CITIES = { - "Punjab": [ - "Lahore", "Faisalabad", "Rawalpindi", "Multan", "Gujranwala", - "Sialkot", "Bahawalpur", "Sargodha", "Sahiwal", "Dera Ghazi Khan" - ], - "Sindh": [ - "Karachi", "Hyderabad", "Sukkur", "Larkana", "Mirpur Khas", - "Nawabshah", "Shikarpur", "Khairpur", "Jacobabad", "Thatta" - ], - "Khyber Pakhtunkhwa": [ - "Peshawar", "Mardan", "Abbottabad", "Swat", "Charsadda", - "Bannu", "Kohat", "Dera Ismail Khan", "Haripur", "Mansehra" - ], - "Balochistan": [ - "Quetta", "Gwadar", "Sibi", "Khuzdar", "Turbat", - "Chaman", "Zhob", "Bela", "Makran", "Pasni" - ], - "Gilgit Baltistan": [ - "Gilgit", "Skardu", "Hunza", "Ghizer", "Diamer", - "Astore", "Shigar", "Kharmang" - ], - "Islamabad Capital Territory": ["Islamabad"] -} - - -def city_and_province(): - """Return a tuple of (city, province) based on accurate mapping.""" - province_name = random.choice(list(PROVINCE_CITIES.keys())) - city_name = random.choice(PROVINCE_CITIES[province_name]) - return city_name, province_name - - -def city(): - """Return a random Pakistani city.""" - return city_and_province()[0] - - -def province(): - """Return the province corresponding to the random city.""" - return city_and_province()[1] - - -def full_address(): - """Generate a realistic full Pakistani address.""" +import random +from .utils import query_value, query_row + + +def city(province=None): + """Return a random Pakistani city. Optionally filter by province.""" + if province: + return query_value( + "SELECT city FROM locations WHERE province = ? ORDER BY RANDOM() LIMIT 1", + (province,) + ) + return query_value("SELECT city FROM locations ORDER BY RANDOM() LIMIT 1") + + +def province(city=None): + """Return a random province. If city is given, return its matching province.""" + if city: + return query_value( + "SELECT province FROM locations WHERE city = ? LIMIT 1", (city,) + ) + return query_value("SELECT province FROM locations ORDER BY RANDOM() LIMIT 1") + + +def full_address(city=None, province=None): + """Generate a realistic full Pakistani address with real postal code.""" + if city: + row = query_row( + "SELECT city, province, postal_code FROM locations WHERE city = ? LIMIT 1", + (city,) + ) + elif province: + row = query_row( + "SELECT city, province, postal_code FROM locations WHERE province = ? ORDER BY RANDOM() LIMIT 1", + (province,) + ) + else: + row = query_row( + "SELECT city, province, postal_code FROM locations ORDER BY RANDOM() LIMIT 1" + ) + + city_name, province_name, postal_code = row house_no = f"House No. {random.randint(1, 999)}" street = f"Street No. {random.randint(1, 30)}" - city_name, province_name = city_and_province() - postal_code = random.randint(10000, 99999) return f"{house_no}, {street}, {city_name}, {province_name}, {postal_code}" \ No newline at end of file diff --git a/faker_pk/company.py b/faker_pk/company.py index 3ccb795..f1a3743 100644 --- a/faker_pk/company.py +++ b/faker_pk/company.py @@ -1,144 +1,87 @@ -import random - -COMPANIES = [ - "Tech Solutions", "Global Enterprises", "NexGen Software", "Bright Future Ltd", - "Innovatech", "Pak Logistics", "Star Industries", "FutureTech Labs", "NextEra Solutions", - "Digital Horizons", "GreenField Enterprises", "Alpha Systems", "Skyline Technologies", - "Visionary Solutions", "Prime Consulting", "BlueWave Software", "Omega Solutions", - "Sunrise Industries", "Quantum Tech", "Everest Solutions", "Peak Dynamics", - "Galaxy Enterprises", "Crescent Innovations", "Summit Tech", "Aurora Systems", - "Infinity Solutions", "Pioneer Technologies", "Vertex Labs", "NextGen Analytics", - "Momentum Solutions", "Nova Systems", "Titan Tech", "Elite Software", "GlobalTech", - "Fusion Enterprises", "Legacy Solutions", "Vortex Innovations", "Ascend Tech", - "CoreLogic", "Hyperion Labs", "Luminous Software", "EverTech", "Matrix Solutions", - "Sapphire Systems", "Digital Minds", "Vertex Solutions", "Zenith Tech", "Precision Labs", - "Altair Enterprises", "Phoenix Systems" -] - -BANKS = [ - "Habib Bank", "MCB Bank", "UBL", "Bank Alfalah", "Standard Chartered", "Allied Bank", "Meezan Bank", "Bank of Punjab", "HBL" -] - +import random +from .utils import query_value, query_row + + +def _normalize_industry(industry): + """Map a human-readable industry name to its internal code. + Accepts both full names ('Information Technology') and codes ('IT'). + """ + # Check if it's already a valid code + code = query_value("SELECT code FROM industries WHERE code = ?", (industry,)) + if code: + return code + # Try mapping from full name + code = query_value("SELECT code FROM industries WHERE name = ?", (industry,)) + if code: + return code + raise ValueError(f"Industry '{industry}' not found.") + + def bank_name(): - return random.choice(BANKS) - -def iban(): - # Basic mock IBAN: PK + 2-digit checksum + 4-digit bank code + random 16 digits - return f"PK{random.randint(10,99)}{random.randint(1000,9999)}{random.randint(10**15,10**16-1)}" - -def company_name(): - return random.choice(COMPANIES) - - -# Industries -INDUSTRIES = { - "Information Technology": "IT", - "Finance": "Finance", - "Healthcare": "Healthcare", - "Education": "Education", - "Marketing & Media": "Marketing", - "Government / Public Sector": "Government", - "Engineering / Manufacturing": "Engineering", - "Hospitality / Retail": "Retail", - "Entrepreneur / Startup": "Entrepreneur", - "Legal / Consulting": "Consulting" -} -# Job titles mapped to industries - -JOB_TITLE_MAPPING = { - "IT": [ - "Software Engineer", "Frontend Developer", "Backend Developer", - "Full Stack Developer", "DevOps Engineer", "Data Scientist", - "Machine Learning Engineer", "AI Researcher", "Cybersecurity Engineer", - "Cloud Solutions Architect", "Mobile App Developer", "Blockchain Developer", - "QA / Test Engineer", "UI/UX Designer", "Network Engineer", "Database Administrator", - "IT Support Specialist", "Systems Analyst" - ], - "Finance": [ - "Accountant", "Auditor", "Financial Analyst", "Investment Analyst", - "Tax Consultant", "Risk Manager", "Credit Analyst", "Loan Officer", - "Treasury Manager", "Financial Controller" - ], - "Healthcare": [ - "Doctor", "Nurse", "Pharmacist", "Lab Technician", "Radiologist", - "Medical Researcher", "Physiotherapist", "Dietitian", "Surgeon", "Psychologist" - ], - "Education": [ - "Teacher", "Lecturer", "Professor", "Research Associate", - "Academic Coordinator", "Curriculum Designer", "Educational Consultant" - ], - "Marketing": [ - "Graphic Designer", "Content Writer", "Copywriter", "Video Editor", - "Animator", "Photographer", "Digital Marketing Specialist", - "Social Media Manager", "Art Director", "Marketing Manager", "Sales Executive" - ], - "Government": [ - "Civil Servant", "Policy Analyst", "Administrative Officer", - "Diplomat", "Law Enforcement Officer", "Urban Planner" - ], - "Engineering": [ - "Civil Engineer", "Mechanical Engineer", "Electrical Engineer", - "Chemical Engineer", "Industrial Engineer", "Production Manager", - "Quality Assurance Engineer" - ], - "Retail": [ - "Hotel Manager", "Chef", "Waiter", "Waitress", "Store Manager", - "Sales Associate", "Customer Service Representative" - ], - "Entrepreneur": [ - "Entrepreneur", "Startup Founder", "Business Development Manager", - "Operations Manager", "Strategy Analyst", "Consultant" - ], - "Consulting": [ - "Legal Advisor", "Lawyer", "Advocate", "Compliance Officer", "Consultant" - ] -} - -# Functions + """Return a random Pakistani bank name.""" + return query_value("SELECT name FROM banks ORDER BY RANDOM() LIMIT 1") + + +def iban(bank=None): + """Generate a realistic Pakistani IBAN. + If bank is specified, use that bank's actual IBAN code. + """ + if bank: + code = query_value("SELECT iban_code FROM banks WHERE name = ?", (bank,)) + if not code: + raise ValueError(f"Bank '{bank}' not found.") + else: + code = query_value("SELECT iban_code FROM banks ORDER BY RANDOM() LIMIT 1") + + checksum = str(random.randint(10, 99)) + account = str(random.randint(10**15, 10**16 - 1)) + return f"PK{checksum}{code}{account}" + + +def company_name(industry=None): + """Return a random company name. Optionally filter by industry.""" + if industry: + code = _normalize_industry(industry) + return query_value( + "SELECT name FROM companies WHERE industry_code = ? ORDER BY RANDOM() LIMIT 1", + (code,) + ) + return query_value("SELECT name FROM companies ORDER BY RANDOM() LIMIT 1") + + +def industry_name(): + """Return a random industry name (human-readable).""" + return query_value("SELECT name FROM industries ORDER BY RANDOM() LIMIT 1") + def job_title(industry=None): + """Return a random job title. Optionally filter by industry.""" if industry: - # Map human-readable input to internal keys (e.g. "Information Technology" -> "IT") - normalized = INDUSTRIES.get(industry, industry) - if normalized not in JOB_TITLE_MAPPING: - raise ValueError(f"Industry '{industry}' not found.") + code = _normalize_industry(industry) + return query_value( + "SELECT title FROM job_titles WHERE industry_code = ? ORDER BY RANDOM() LIMIT 1", + (code,) + ) + return query_value("SELECT title FROM job_titles ORDER BY RANDOM() LIMIT 1") - return random.choice(JOB_TITLE_MAPPING[normalized]) - # Randomly pick an industry first - selected_industry = random.choice(list(JOB_TITLE_MAPPING.keys())) - return random.choice(JOB_TITLE_MAPPING[selected_industry]) def job_title_with_industry(): - selected_industry = random.choice(list(JOB_TITLE_MAPPING.keys())) - title = random.choice(JOB_TITLE_MAPPING[selected_industry]) - return f"{title} - {selected_industry}" + """Return a string of 'job_title - industry_code'.""" + row = query_row("SELECT title, industry_code FROM job_titles ORDER BY RANDOM() LIMIT 1") + return f"{row[0]} - {row[1]}" -def industry_name(): - return random.choice(list(INDUSTRIES.keys())) def salary(industry=None): - """ - Generate a random salary in PKR based on industry rough ranges. - """ - # Rough salary ranges (monthly PKR) - ranges = { - "IT": (50000, 250000), - "Finance": (40000, 200000), - "Healthcare": (30000, 180000), - "Education": (25000, 120000), - "Marketing": (30000, 150000), - "Government": (25000, 120000), - "Engineering": (35000, 180000), - "Retail": (20000, 100000), - "Entrepreneur": (50000, 300000), - "Consulting": (40000, 200000) - } + """Generate a random salary in PKR based on industry salary ranges.""" if industry: - normalized = INDUSTRIES.get(industry, industry) - if normalized in ranges: - low, high = ranges[normalized] - else: - low, high = random.choice(list(ranges.values())) - else: - low, high = random.choice(list(ranges.values())) - return random.randint(low, high) + code = _normalize_industry(industry) + row = query_row( + "SELECT min_salary, max_salary FROM industries WHERE code = ?", (code,) + ) + if row: + return random.randint(row[0], row[1]) + + row = query_row( + "SELECT min_salary, max_salary FROM industries ORDER BY RANDOM() LIMIT 1" + ) + return random.randint(row[0], row[1]) \ No newline at end of file diff --git a/faker_pk/education.py b/faker_pk/education.py new file mode 100644 index 0000000..2bb496f --- /dev/null +++ b/faker_pk/education.py @@ -0,0 +1,95 @@ +import random +from datetime import date, timedelta +from .utils import query_value, query_row + + +# Age ranges per institution level +AGE_RANGES = { + "school": (5, 14), + "college": (14, 18), + "university": (18, 25) +} + + +def institution(level=None, city=None, province=None): + """Return a random institution name, optionally filtered by level, city, or province.""" + conditions = [] + params = [] + + if level: + conditions.append("i.type = ?") + params.append(level) + if city: + conditions.append("i.city = ?") + params.append(city) + if province: + conditions.append("l.province = ?") + params.append(province) + + where = f"WHERE {' AND '.join(conditions)}" if conditions else "" + + query = f""" + SELECT i.name FROM institutions i + JOIN locations l ON i.city = l.city + {where} + ORDER BY RANDOM() LIMIT 1 + """ + return query_value(query, tuple(params)) + + +def student_dob(level="university"): + """Generate an age-appropriate DOB for a student at the given level.""" + min_age, max_age = AGE_RANGES.get(level, (18, 25)) + today = date.today() + start = today.replace(year=today.year - max_age) + end = today.replace(year=today.year - min_age) + delta = (end - start).days + return start + timedelta(days=random.randint(0, delta)) + + +def student_profile(level=None, province=None): + """Generate a complete, coherent student profile. + + Returns a dict with name, gender, cnic, institution, city, province, dob. + All fields are consistent (institution matches city/province, dob matches level). + """ + from .personal import male_name, female_name, cnic + + # Default to random level if not specified + if not level: + level = random.choice(["school", "college", "university"]) + + # Pick a random institution, optionally in the given province + conditions = ["i.type = ?"] + params = [level] + if province: + conditions.append("l.province = ?") + params.append(province) + + where = f"WHERE {' AND '.join(conditions)}" + row = query_row(f""" + SELECT i.name, i.city, l.province FROM institutions i + JOIN locations l ON i.city = l.city + {where} + ORDER BY RANDOM() LIMIT 1 + """, tuple(params)) + + if not row: + raise ValueError(f"No institution found for level='{level}', province='{province}'") + + inst_name, city_name, province_name = row + + # Generate gender-consistent personal data + gender = random.choice(["male", "female"]) + name = male_name() if gender == "male" else female_name() + + return { + "name": name, + "gender": gender, + "cnic": cnic(gender=gender), + "institution": inst_name, + "level": level, + "city": city_name, + "province": province_name, + "dob": student_dob(level) + } \ No newline at end of file diff --git a/faker_pk/faker_pk.db b/faker_pk/faker_pk.db new file mode 100644 index 0000000000000000000000000000000000000000..7a847e96ceba46f9ac29687a49691f9cd9c5ec99 GIT binary patch literal 122880 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 YB#H-I3x#oTnPiJEddjo->u7TS0eFFXG5`Po literal 0 HcmV?d00001 diff --git a/faker_pk/initialize_db.py b/faker_pk/initialize_db.py new file mode 100644 index 0000000..11214e8 --- /dev/null +++ b/faker_pk/initialize_db.py @@ -0,0 +1,615 @@ +import sqlite3 +import os + +MALE_NAMES = [ + "Ahmed", "Muhammad", "Ali", "Hassan", "Hussain", "Bilal", "Hamza", "Umar", "Usman", "Abdullah", + "Abdul Rehman", "Abdul Basit", "Abdul Hadi", "Abdul Wahab", "Abdul Samad", "Abdul Qadir", "Abdul Majeed", + "Abdul Rauf", "Abdul Aziz","Abdul Kareem", "Abdul Aleem", "Abdul Ghaffar", "Abdul Ghani", "Abdul Haq", + "Abdul Shakoor", "Abdul Sattar", "Abdul Wasi", "Ahmad","Zeeshan", "Danish", "Faizan", "Fahad", + "Waleed", "Zain", "Saad", "Ahsan", "Adeel", "Asad","Arsalan", "Shahzaib", "Shehryar", "Salman", + "Noman", "Omer", "Tahir", "Talha", "Kashif", "Kamran","Shahid", "Naveed", "Imran", "Junaid", "Farhan", + "Faisal", "Khalid", "Raza", "Rizwan", "Adnan","Arif", "Yasir", "Irfan", "Zubair", "Shayan", "Sameer", "Umair", + "Huzaifa", "Ayaan", "Rayyan","Azaan", "Areeb", "Raheel", "Sufyan", "Haris", "Anas", "Arham", "Asim", "Moiz", + "Ibtisam", "Saif", "Ilyas", "Ismail", "Ibrahim", "Eesa", "Musa", "Yousuf", "Dawood", "Yunus","Hashir", + "Nuh", "Luqman", "Taimoor", "Murtaza", "Baqir", "Shahmeer", "Shaheer", "Daniyal", "Abdul Malik", + "Zarar", "Zaryab", "Aafaq", "Abrar", "Adil", "Amaan", "Amjad", "Anees", "Anwar", "Aqeel", + "Arqam", "Arsal", "Asghar", "Ashar", "Atif", "Awais", "Ayaz", "Azhar", "Azlan", "Barkat", + "Basim", "Babar", "Burhan", "Ehtisham", "Ehsan", "Faraz", "Farid", "Fawad", "Feroz", "Ghazanfar", + "Haider", "Hammad", "Hameed", "Hasan", "Haseeb", "Hashim", "Hisham","Adeel", "Huzaifah", "Ijaz", "Imad", + "Inam", "Javed", "Kamal", "Khalil", "Khizar", "Mahad", "Mahir", "Mansoor", "Maaz", "Mazhar","Abdul Rahman", + "Mehdi", "Muneeb", "Mustafa", "Naeem", "Nouman", "Qasim", "Rameez", "Rehan", "Sadiq", "Safeer", + "Saifullah", "Sarfaraz", "Shahbaz", "Shafqat", "Shafiq", "Sharjeel", "Shehzad", "Sohail", "Subhan", "Sultan", + "Tabish", "Talal", "Tauseef", "Tufail", "Ubaid", "Umer", "Usama", "Wajid", "Waqas", "Wasif", + "Yasir", "Yawar", "Yameen", "Yasin", "Zakariya", "Zaman", "Zawwar", "Zia", "Zohaib", "Zubair", + "Zain ul Abidin","Taha", "Irtaza", "Raza", "Ehtesham", "Mirza", "Azeem", "Saqib", "Shabbir", + "Tahseen", "Salman", "Fahim", "Jawad", "Sarmad", "Nabeel", "Faiq", "Rashid", "Rahim", "Habib", + "Munir", "Zameer", "Akram", "Zafar", "Wasim", "Nauman", "Nasir", "Khalil", "Jibran", "Kashan", + "Adi", "Fakhir", "Sabtain", "Farooq", "Faiz", "Nisar", "Salman","Gohar","ghazangfar", + "Rifat", "Tahmid", "Zohair", "Zaeem", "Sarmal", "Arsal", "Areez", "Sarim","Aoun", + "Zayyan", "Razaq", "Asfar", "Affan", "Hanzala", "Hammad", "Ziyad", "Adeel", + "Karim", "Qadeer", "Hanan", "Rameen", "Taha", "Shahroz", "Sameer", "Yasrab", "Ammar", + "Shakir", "Rauf", "Danish", "Hamdaan", "Maher", "Uzair", "Shareef", + "Zarrar", "Faris", "Azmat", "Riaz", "Munawwar", "Kaleem", "Sufyan", "Zameel", "Sajid", "Sarim", + "Tabriz", "Yasin", "Attiq", "Zaid", "Murtaza", "Aneeb", "Moazzam", + "Fida", "Najam", "Tauqeer", "Shakeel", "Najeeb", "Basit", "Faizan","Farhan", "Haris", "Ahsan", "Taimoor", "Fahad", "Waris", +] + +FEMALE_NAMES = [ + "Aliya", "Amna", "Anaya", "Anisa", "Asia", "Aasma", "Abida", "Adeela", "Adeelah", + "Afifa", "Afsheen", "Hifza", "Afreen", "Aiman", "Aina", "Aiza", "Aleena", + "Aleesha", "Aleeza", "Ayla", "Alishba", "Amal", "Ammara", "Amber", "Ameena", "Amira", "Anabia", + "Anila", "Aniqa", "Anisa", "Anum", "Anusha", "Anzela", "Aqsa", "Arfa", "Arisha", + "Urwa", "Asfa", "Asma", "Asmara", "Atiya", "Ayesha", "Ayra", "Azka", + "Azra", "Bisma", "Batool", "Benish", "Bushra", "Dur-e-Fatima","Dur e Fishan", "Dua", "Ayman", "Esha", + "Eiman","Erum", "Faiza", "Fakhra", "Falaq", "Falak", + "Fariha", "Farwah", "Farzana", "Fatima", "Fauzia", "Fiza", "Ghazal", "Ghazala", "Gulnaz", + "Mahrukh", "Habiba", "Hafsa", "Haleema", "Hania", "Hadia", "Hareem", "Haseena", "Hiba", "Hifza", + "Hina", "Hira", "Humaira", "Humna", "Iffat", "Ifra", "Iqra", + "Irum", "Isha", "Ishaal", "Eshwa", "Isra", "Jameela", "Javeria", "Jannat", "Yasmeen", + "Jiya", "Kainat", "Khadija", "Khansa", "Kiran", "Komal", "Laiba", "Laila", "Laraib", + "Lehna", "Lubna", "Mahira", "Mahjabeen", "Mahnoor", "Maha", "Maliha", "Maria", + "Mariam", "Marwa","Maheen", "Mehak", "Mehr", "Mehwish", "Minal", "Mishal", "Misbah", + "Mona", "Mubashira", "Muqaddas", "Meesha", "Nabeela", "Nadia", "Nafisa", "Naila", "Najma", "Natasha", + "Naureen", "Nayyab", "Neha", "Nida", "Nimra", "Nishat", "Noreen", "Nosheen", "Nusrat","Faqiha", + "Naima", "Zubaidah", "Parveen", "Qandeel", "Qurat tul Ain", "Rabiya", "Rabia", "Rafia", "Rafiya", "Rameen", + "Rania", "Rozena", "Rashida", "Rida", "Rimsha", "Rizwana", "Soha", "Romana", "Roohi", "Ruba", + "Rubina", "Rukhsar", "Rumaisa","Rumaisha","Rimsha", "Ruqayya", "Saba", "Sabeen", "Sabahat", "Sadia", "Sadra", "Sania", + "Sahar", "Saira", "Saria","Sajida", "Sakina", "Salma", "Samina", "Samiya", "Sana", "Sanober","Sarah", "Sasha", + "Subha", "Subhana", "Suhana", "Sumaira", "Sumaiya", "Sundus", "Tabinda", "Tabassum", "Taha", "Tahira", + "Tahreem", "Tahirah", "Tania", "Tanisha", "Tanzeela", "Tayyaba", "Tehreem", "Tooba", "Ujala", "Umama","Umaima","Ajwa", + "Umm e Habiba", "Umm e Hani", "Umm e Kulsoom", "Umm e Hani", "Umm e Rubab","Rutab","Rahaf", "Umm e Salma", "Urooj", + "Urwa", "Uzma", "Wajiha","Wardah", "Yashma", "Yasmeen", "Yumna", "Zainab", "Zakia","Haram", + "Zeenia", "Zahra", "Zaib", "Zakia", "Zeba","Zareen", "Zarish", "Zarmeen","Atfa","Tooba","Anshara","Zeba","Naina","Namal", + "Zarqa", "Zartaj", "Zaryab", "Zehra", "Zeenat", "Zimal", "Zobia", "Zohra", "Farwa", "Farheen", "Gulshan", + "Zonaira", "Zoya","Zoha", "Zubaida", "Zulekha", "Zunaisha","Adeeba", "Areeba","Fakhira", "Fariha", "Faryal", + "Maheen", "Arooba","Bareera","Bushra", "Dania", "Dua", "Eshaal","Urwa","Uswa","Mawra","Shibrah","Haya", + "Hajra", "Haleemah", "Hamna", "Huriya","Hurain","Nayarra", "Insha","Ishaal","Noor","Saffa","Minahil", + "Jannat", "Kinza","Khizra","Saima","Madeeha","Maleeha","Komal","Shanza","Shanzay","Faiqa","Hareem","Gohar", + "Laiba", "Laila", "Mehwish", "Maida", "Memoona", "Momina","Marjan", "Mehreen", "MehruNisa", "Minal", + "Maira", "Naheed","Naveed", "Naila","Noshaba","Parveen", "Rabi", "Rafia", "Ramsha","Iraj","Rija","Reshma", + "Aruba", "Rukhsana","Rehanna" , "Saba", "Sabiha","Nabeeha","Saeeda", "Sahar","Sameera", "Samra", "Samia", +] + +LAST_NAMES = [ + "Abbasi", "Abbas", "Abid", "Afzal", "Ahmad","Akbar", "Akhter", "Alam", "Ali","Sethi","Anwar", + "Amjad", "Anjum", "Ansari", "Arif", "Asad", "Ashfaq", "Asghar", "Aslam", "Atif", "Awan", + "Azam", "Azhar", "Babar", "Baig", "Bajwa", "Bakht", "Baloch", "Bangash", "Basit", "Batool", + "Bhatti", "Bukhari", "Butt", "Chaudhry", "Cheema", "Chishti", "Dar", "Danish", "Daud", "Deen", + "Durrani", "Ejaz", "Fahim", "Faheem", "Farid", "Farooq", "Farrukh", "Fazal", "Feroz", "Ghafoor", + "Ghani", "Ghazanfar", "Ghaznavi", "Ghauri", "Gohar", "Habib", "Hafeez", "Hafiz", + "Haider", "Hameed", "Hamid", "Hanif", "Hashim", "Hasnain", "Hassan", "Hayat", "Hussain", "Hyder", + "Iftikhar", "Ijaz", "Ilyas", "Imam", "Imran", "Inam", "Iqbal", "Irshad", "Ismail", "Ishaq", + "Jahangir", "Jamal", "Jamali", "Jamshed", "Javed", "Jawad", "Kabir", "Qadir", "Kaleem", + "Kamran", "Kamil", "Karim", "Kashif", "Kazmi", "Khalid", "Khalil", "Khan", "Khizar", "Khurram", + "Latif", "Mahmood", "Malik", "Manzoor", "Masood", "Mazhar", "Mehmood", "Mir", "Mirza", "Moin", + "Mohsin", "Moinuddin", "Monis", "Mubashir", "Mujeeb", "Mukhtar", "Munir", "Murad", "Mustafa", "Murtaza", + "Nadeem", "Naeem", "Naseem", "Nasir", "Nawaz", "Niaz", "Noor", "Noman", "Numan", "Obaid", + "Qadir", "Qaiser", "Qamar", "Qasim", "Qayyum", "Qureshi", "Rafiq", "Rafique", "Rahim", "Raja", + "Rameez", "Rana", "Rasheed", "Rauf", "Raza", "Razzaq", "Rehman", "Riaz", "Rizwan", + "Sabir", "Sadiq", "Safeer", "Shafi", "Saeed", "Shafiullah", "Sajid", "Saleem", "Salman", + "Sami", "Sarfaraz","Shafi", "Shafique", "Shahid", "Shakeel", "Sharif", "Shaukat", "Sheikh", + "Shehzad", "Sheraz", "Shoukat", "Siddiq","Sadiq", "Siddique", "Sohail", "Suleman", "Sultan", "Tahir", "Talib", + "Tariq", "Tufail", "Ubaid", "Umar", "Usman", "Waheed", "Wali", "Waseem", "Yaseen", "Yasin", + "Yousaf", "Younas", "Zafar", "Zahid", "Zakir", "Zaman", "Zameer", + "Abbass", "Aftab", "Akram", "Alvi", "Ashraf", "Aziz", "Badar", "Bari", "Bashir", + "Basharat", "Burhan", "Chughtai", "Zawar", "Ejaz", "Ehsan", "Faisal", "Farhan", "Fazal", + "Gul", "Gulzar", "Haqqani", "Hashmi", "Hussaini", "Jalil", "Jamil", "Junaid", "Kabir", + "Kamal", "Khawaja", "Khattak", "Kiani", "Khoso", "Khokhar", "Khosa", "Lodhi", "Mahmud", + "Malook", "Mandokhel", "Memon", "Mughal", "Naqvi", "Naseer", "Niazi", "Orakzai", "Pirzada", + "Qadri", "Qasmi", "Rashidi", "Saboor", "Sadaqat", "Sajjad", "Saleh", "Samiullah", "Sarwar", "Shafiullah", + "Shah", "Shahbaz", "Shahzad", "Shams", "Sharafat", "Sharifuddin", "Sikandar", "Sohaib", "Subhan", + "Tabassum", "Taha", "Talha", "Tanveer", "Tauqeer", "Tariq", "Wahid", "Wajid", "Waqas", + "Yaseer", "Zaheer", "Zaman", "Zarrar", "Zeeshan", "Agha","Yousafzai", "Asmat", "Atta", "Baber", "Baloch", "Chohan", + "Dasti", "Dogar", "Gandapur", "Gill", "Gulshan", "Khatri", + "Hingoro", "Hoti", "Jakhrani", "Jatoi", "Junejo", "Kalhoro", "Kakar", "Kashmiri", "Khoso", + "Langah", "Laghari", "Liaqat", "Lodhi", "Mahsud", "Magsi", "Marwat", "Mashwani", + "Mengal", "Mirani", "Naseeruddin", "Niazi","Qasoori","Warind", + "Rajput", "Rind", "Samar", "Samad", "Sandhu", "Shahwani", "Sheerazi", "Sial", + "Soomro", "Soomra", "Talpur", "Tanoli", "Tarin", "Tiwana", "Toor", + "Turk", "Wazir", "Yusufzai", "Zehri", "Zuberi", "Kiyani", "Khakwani", "Taj", "Meer", + "Mirwani", "Bhutta", "Randhawa", "Ghuman", "Gillani", "Naqash", "Abbassi", "Bohra", "Rajpoot", + "Siddiqui", "Qaim", "Shuja", "Irfan" +] + +BANKS_WITH_CODES = [ + ("Habib Bank", "HABB"), + ("MCB Bank", "MUCB"), + ("UBL", "UNIL"), + ("Bank Alfalah", "ALFH"), + ("Standard Chartered", "SCBL"), + ("Allied Bank", "ABPA"), + ("Meezan Bank", "MEZN"), + ("Bank of Punjab", "BPUN") +] + +SIM_PREFIXES = { + "Jazz": ["300", "301", "302", "303", "304", "305", "306", "307", "308", "309", "320", "321", "322", "323", "324", "325"], + "Zong": ["310", "311", "312", "313", "314", "315", "316", "317", "318", "319"], + "Ufone": ["330", "331", "332", "333", "334", "335", "336", "337"], + "Telenor": ["340", "341", "342", "343", "344", "345", "346", "347", "348", "349"] +} + +CASTES = ["Sheikh", "Ansari", "Raja", "Malik", "Qureshi", "Chaudhry", "Jutt", "Butt", "Rajput", "Rana","Mahar"] +SECTS = ["Sunni", "Shia"] + +# Province with City postcard +PROVINCE_CITIES = { + "Punjab": [ + ("Lahore", 54000), ("Faisalabad", 38000), ("Rawalpindi", 46000), + ("Multan", 60000), ("Gujranwala", 52250), ("Sialkot", 51310), + ("Bahawalpur", 63100), ("Sargodha", 40100), ("Sahiwal", 57000), + ("Dera Ghazi Khan", 32200),("Rahim yar khan",64200) + ], + "Sindh": [ + ("Karachi", 74000), ("Hyderabad", 71000), ("Sukkur", 65200), + ("Larkana", 77150), ("Mirpur Khas", 69000), ("Nawabshah", 67450), + ("Shikarpur", 64200), ("Khairpur", 66020), ("Jacobabad", 79000), + ("Thatta", 73130) + ], + "Khyber Pakhtunkhwa": [ + ("Peshawar", 25000), ("Mardan", 23200), ("Abbottabad", 22010), + ("Swat", 19130), ("Charsadda", 24420), ("Bannu", 28100), + ("Kohat", 26000), ("Dera Ismail Khan", 29050), ("Haripur", 22620), + ("Mansehra", 21300) + ], + "Balochistan": [ + ("Quetta", 87300), ("Gwadar", 91200), ("Sibi", 82000), + ("Khuzdar", 89100), ("Turbat", 92600), ("Chaman", 86000), + ("Zhob", 85200), ("Bela", 90150), ("Makran", 92500), + ("Pasni", 91300) + ], + "Gilgit Baltistan": [ + ("Gilgit", 15100), ("Skardu", 16100), ("Hunza", 15700), + ("Ghizer", 15200), ("Diamer", 14100), ("Astore", 14200), + ("Shigar", 16300), ("Kharmang", 16200) + ], + "Islamabad Capital Territory": [ + ("Islamabad", 44000) + ] +} + +INDUSTRIES_RANGES = [ + ("Information Technology", "IT", 50000, 250000), + ("Finance", "Finance", 40000, 200000), + ("Healthcare", "Healthcare", 30000, 180000), + ("Education", "Education", 25000, 120000), + ("Marketing & Media", "Marketing", 30000, 150000), + ("Government / Public Sector", "Government", 25000, 120000), + ("Engineering / Manufacturing", "Engineering", 35000, 180000), + ("Hospitality / Retail", "Retail", 20000, 100000), + ("Entrepreneur / Startup", "Entrepreneur", 50000, 300000), + ("Legal / Consulting", "Consulting", 40000, 200000), + ("Art & Entertainment", "Art", 25000, 200000), + ("Politics", "Politics", 50000, 300000), + ("Agriculture", "Agriculture", 15000, 80000), + ("Domestic & Personal Services", "Services", 12000, 60000), + ("Defense & Public Safety", "Defense", 30000, 150000) +] + +JOB_TITLE_MAPPING = { + "IT": [ + "Software Engineer", "Frontend Developer", "Backend Developer", + "Full Stack Developer", "DevOps Engineer", "Data Scientist","Game Developer","AI Engineer","IOT Engineer", + "Machine Learning Engineer", "AI Researcher", "Cybersecurity Engineer", + "Cloud Solutions Architect", "Mobile App Developer", "Blockchain Developer", + "QA / Test Engineer", "UI/UX Designer", "Network Engineer", "Database Administrator", + "IT Support Specialist", "Systems Analyst","Cloud Engineer","Data Analyst","Data Engineer" + ], + "Finance": [ + "Accountant", "Auditor", "Financial Analyst", "Investment Analyst", + "Tax Consultant", "Risk Manager", "Credit Analyst", "Loan Officer", + "Treasury Manager", "Financial Controller","CA","CFO" + ], + "Healthcare": [ + "Doctor", "Nurse", "Pharmacist", "Lab Technician", "Radiologist","Cardiologist","Gynecologist","Dermatologist","Cosmetologist", + "Medical Researcher", "Physiotherapist", "Dietitian", "Surgeon", "Psychologist","Oncologist","Pediatrician","Orthopedic Surgeon" + ], + "Education": [ + "Teacher", "Lecturer", "Associate Professor","Assistant Professor","Teaching Assistant(TA)", "Research Associate", + "Academic Coordinator", "Curriculum Designer", "Educational Consultant" + ], + "Marketing": [ + "Graphic Designer", "Content Writer", "Copywriter", "Video Editor", + "Animator", "Photographer", "Digital Marketing Specialist","Social Media Influencer", "SEO Specialist", + "Social Media Manager", "Art Director", "Marketing Manager", "Sales Executive" + ], + "Government": [ + "Civil Servant", "Policy Analyst", "Administrative Officer","Public Relations Officer", "Intelligence Analyst", + "Diplomat", "Law Enforcement Officer", "Urban Planner","Public Health Administrator","Emergency Management Specialist" + ], + "Engineering": [ + "Civil Engineer", "Mechanical Engineer", "Electrical Engineer", + "Chemical Engineer", "Industrial Engineer", "Production Manager", + "Quality Assurance Engineer","Environmental Engineer","Aerospace Engineer" + ], + "Retail": [ + "Hotel Manager", "Chef", "Waiter", "Waitress", "Store Manager","Product Manager", + "Sales Associate", "Customer Service Representative","Event Manager", "Tour Guide", "Travel Agent", + ], + "Entrepreneur": [ + "Entrepreneur", "Startup Founder", "Business Development Manager", + "Operations Manager", "Strategy Analyst", "Consultant","Investor Relations Manager", "Venture Capitalist" + ], + "Consulting": [ + "Legal Advisor", "Lawyer", "Advocate", "Compliance Officer", "Consultant","Business Analyst", + "Management Consultant", "Strategy Consultant" + ], + "Art": [ + "Painter", "Dancer", "Singer", "Actor", "Interior Designer","Event Planner", "Makeup Artist","Sketcher","Home Decorator", + "Sculptor", "Musician", "Choreographer", "Director", "Photographer","Fashion Designer","Cinematographer","Illustrator","UI/UX Designer" + ], + "Politics": [ + "Politician", "Member of National Assembly", "Senator","Governor", + "Minister", "Mayor", "Councillor", "Campaign Manager", "Political Consultant" + ], + "Agriculture": [ + "Farmer", "Farm Manager", "Tractor Driver", "Agronomist", + "Dairy Farmer", "Harvesting Worker", "Irrigation Specialist","Agricultural Engineer", "Livestock Specialist" + ], + "Services": [ + "Housekeeper", "Maid", "Private Driver", "Gardener", "Plumber", "Electrician","Carpenter","Miner","Welder","Mechanic", + "Security Guard", "Cook", "Nanny / Babysitter", "Caregiver", "Personal Assistant", "Laundry Worker","Driver" + ], + "Defense": [ + "Soldier", "Army Captain", "Police Officer", "Sub-Inspector", + "Firefighter", "Security Officer", "Rescue Warden", "Paramedic", + "Civil Defense Officer", "Emergency Medical Technician (EMT)", + ] +} + +COMPANY_INDUSTRIES = { + # 1. Information Technology (IT) + "Systems Limited": "IT", + "NetSol Technologies": "IT", + "Folio3 Software": "IT", + "Contour Software": "IT", + "NexGen Digital Solutions": "IT", + + # 2. Finance + "Habib Metropolitan Financials": "Finance", + "Pakistan Stock Exchange": "Finance", + "Lakson Investments": "Finance", + "JS Bank Limited": "Finance", + "Alfalah Capital": "Finance", + + # 3. Healthcare + "Shaukat Khanum Hospital": "Healthcare", + "Aga Khan University Hospital": "Healthcare", + "Chughtai Lab": "Healthcare", + "Indus Hospital Network": "Healthcare", + "Getz Pharma": "Healthcare", + + # 4. Education + "Beaconhouse School System": "Education", + "The City School": "Education", + "Roots Millennium Schools": "Education", + "KIPS Academy": "Education", + "NUST University": "Education", + + # 5. Marketing & Media + "Symmetry Group": "Marketing", + "Brainchild Communications": "Marketing", + "Adcom Leo Burnett": "Marketing", + "Interflow Communications": "Marketing", + "Red Communication Arts": "Marketing", + + # 6. Government / Public Sector + "Federal Board of Revenue (FBR)": "Government", + "NADRA Pakistan": "Government", + "Pakistan Post": "Government", + "WAPDA Pakistan": "Government", + "Capital Development Authority (CDA)": "Government", + + # 7. Engineering & Manufacturing + "Indus Motor Company (Toyota)": "Engineering", + "Pak Suzuki Motor Company": "Engineering", + "Descon Engineering": "Engineering", + "Lucky Cement Limited": "Engineering", + "Pak Arab Refinery (PARCO)": "Engineering", + + # 8. Hospitality / Retail + "Imtiaz Super Market": "Retail", + "Metro Cash & Carry Pakistan": "Retail", + "Al-Fatah Department Store": "Retail", + "Khaadi Retail": "Retail", + "Pearl Continental Hotels": "Retail", + + # 9. Entrepreneur / Startup + "Bazaar Technologies": "Entrepreneur", + "Dastgyr Technologies": "Entrepreneur", + "Jugnu Startup Labs": "Entrepreneur", + "Retailo Pakistan": "Entrepreneur", + "SastaTicket.pk": "Entrepreneur", + + # 10. Legal / Consulting + "A.F. Ferguson & Co. (PwC)": "Consulting", + "Abacus Consulting": "Consulting", + "KPMG Taseer Hadi": "Consulting", + "EY Ford Rhodes": "Consulting", + "BDO Ebrahim & Co.": "Consulting", + + # 11. Art & Entertainment + "Canvas Art Gallery": "Art", + "National College of Arts (NCA)": "Art", + "Coke Studio Pakistan": "Art", + "Ajoka Theatre Group": "Art", + "Ghazal Harmonies Studio": "Art", + + # 12. Politics + "Senate Secretariat": "Politics", + "National Assembly of Pakistan": "Politics", + "Policy Research Institute (PRIP)": "Politics", + "Citizens Coalition for Reforms": "Politics", + "Election Commission of Pakistan": "Politics", + + # 13. Agriculture + "Fauji Fertilizer Company (FFC)": "Agriculture", + "Engro Fertilizers": "Agriculture", + "Green Punjab Agri Farms": "Agriculture", + "Sindh Seed Corporation": "Agriculture", + "Zarai Taraqiati Bank (ZTBL)": "Agriculture", + + # 14. Domestic & Personal Services + "Domestic Ease Services": "Services", + "Lahore Security Services": "Services", + "Pak Plumbers & Electricians Network": "Services", + "Care & Cleaning Pakistan": "Services", + "Safe Hands Nanny Agency": "Services", + + # 15. Defense & Public Safety + "Pakistan Army": "Defense", + "Punjab Police Department": "Defense", + "Sindh Police Department": "Defense", + "Rescue 1122": "Defense", + "Civil Defense Department": "Defense", + "Fauji Security Services": "Defense" +} + +COMPANIES = list(COMPANY_INDUSTRIES.keys()) + +INSTITUTIONS = [ + # Universities — Punjab + ("University of the Punjab", "university", "Lahore"), + ("UCP - University of Central Punjab", "university", "Lahore"), + ("LUMS", "university", "Lahore"), + ("UET Lahore", "university", "Lahore"), + ("COMSATS Lahore", "university", "Lahore"), + ("GCU Lahore", "university", "Lahore"), + ("BZU Multan", "university", "Multan"), + ("NTU Faisalabad", "university", "Faisalabad"), + + # Universities — Sindh + ("University of Karachi", "university", "Karachi"), + ("NED University", "university", "Karachi"), + ("IBA Karachi", "university", "Karachi"), + ("SZABIST Karachi", "university", "Karachi"), + ("University of Sindh", "university", "Hyderabad"), + + # Universities — KPK + ("AWKUM - Abdul Wali Khan University", "university", "Peshawar"), + ("University of Peshawar", "university", "Peshawar"), + ("COMSATS Abbottabad", "university", "Abbottabad"), + ("Islamia College University", "university", "Peshawar"), + + # Universities — Islamabad + ("NUST", "university", "Islamabad"), + ("COMSATS Islamabad", "university", "Islamabad"), + ("IIUI - International Islamic University", "university", "Islamabad"), + ("Quaid-i-Azam University", "university", "Islamabad"), + ("Air University", "university", "Islamabad"), + ("FAST-NUCES Islamabad", "university", "Islamabad"), + + # Universities — Balochistan + ("University of Balochistan", "university", "Quetta"), + ("BUITEMS", "university", "Quetta"), + + # Colleges — Punjab + ("Government College Lahore", "college", "Lahore"), + ("Forman Christian College", "college", "Lahore"), + ("Punjab College Faisalabad", "college", "Faisalabad"), + ("Superior College Multan", "college", "Multan"), + + # Colleges — Sindh + ("DJ Sindh Government Science College", "college", "Karachi"), + ("Adamjee Government Science College", "college", "Karachi"), + + # Colleges — KPK + ("Edwardes College Peshawar", "college", "Peshawar"), + ("Islamia College Peshawar", "college", "Peshawar"), + + # Colleges — Islamabad + ("F.G. Sir Syed College", "college", "Islamabad"), + ("Islamabad Model College", "college", "Islamabad"), + + # Schools — spread across cities + ("Beaconhouse School", "school", "Lahore"), + ("The City School", "school", "Karachi"), + ("Roots Millennium", "school", "Islamabad"), + ("Froebel's International", "school", "Islamabad"), + ("Aitchison College", "school", "Lahore"), + ("Karachi Grammar School", "school", "Karachi"), + ("Peshawar Model School", "school", "Peshawar"), + ("Army Public School Rawalpindi", "school", "Rawalpindi"), +] + + +def initialize_database(db_path: str) -> None: + """Creates the schema and populates the SQLite database with all fake data records.""" + conn = sqlite3.connect(db_path) + try: + cursor = conn.cursor() + + # Create Schema + cursor.execute("DROP TABLE IF EXISTS industries") + cursor.execute(""" + CREATE TABLE industries ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + code TEXT NOT NULL UNIQUE, + min_salary INTEGER NOT NULL, + max_salary INTEGER NOT NULL + ) + """) + + cursor.execute("DROP TABLE IF EXISTS job_titles") + cursor.execute(""" + CREATE TABLE job_titles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + industry_code TEXT NOT NULL, + FOREIGN KEY (industry_code) REFERENCES industries(code) + ) + """) + + cursor.execute("DROP TABLE IF EXISTS companies") + cursor.execute(""" + CREATE TABLE companies ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + industry_code TEXT, + FOREIGN KEY (industry_code) REFERENCES industries(code) + ) + """) + + cursor.execute("DROP TABLE IF EXISTS locations") + cursor.execute(""" + CREATE TABLE locations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + city TEXT NOT NULL UNIQUE, + province TEXT NOT NULL, + postal_code INTEGER NOT NULL + ) + """) + + cursor.execute("DROP TABLE IF EXISTS names") + cursor.execute(""" + CREATE TABLE names ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + type TEXT NOT NULL + ) + """) + + cursor.execute("DROP TABLE IF EXISTS sim_providers") + cursor.execute(""" + CREATE TABLE sim_providers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE + ) + """) + + cursor.execute("DROP TABLE IF EXISTS sim_prefixes") + cursor.execute(""" + CREATE TABLE sim_prefixes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + provider_name TEXT NOT NULL, + prefix TEXT NOT NULL UNIQUE, + FOREIGN KEY (provider_name) REFERENCES sim_providers(name) + ) + """) + + cursor.execute("DROP TABLE IF EXISTS castes") + cursor.execute(""" + CREATE TABLE castes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE + ) + """) + + cursor.execute("DROP TABLE IF EXISTS sects") + cursor.execute(""" + CREATE TABLE sects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE + ) + """) + + cursor.execute("DROP TABLE IF EXISTS banks") + cursor.execute(""" + CREATE TABLE banks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + iban_code TEXT NOT NULL UNIQUE + ) + """) + + cursor.execute("DROP TABLE IF EXISTS institutions") + cursor.execute(""" + CREATE TABLE institutions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + type TEXT NOT NULL, + city TEXT NOT NULL, + FOREIGN KEY (city) REFERENCES locations(city) + ) + """) + + # Insert Data + # 1. Industries + cursor.executemany( + "INSERT INTO industries (name, code, min_salary, max_salary) VALUES (?, ?, ?, ?)", + INDUSTRIES_RANGES + ) + + # 2. Job Titles + job_titles_data = [] + for code, titles in JOB_TITLE_MAPPING.items(): + for title in titles: + job_titles_data.append((title, code)) + cursor.executemany("INSERT INTO job_titles (title, industry_code) VALUES (?, ?)", job_titles_data) + + # 3. Companies + companies_data = [(name, code) for name, code in COMPANY_INDUSTRIES.items()] + cursor.executemany("INSERT INTO companies (name, industry_code) VALUES (?, ?)", companies_data) + + # 4. Locations + locations_data = [] + for province, cities in PROVINCE_CITIES.items(): + for city, code in cities: + locations_data.append((city, province, code)) + cursor.executemany("INSERT INTO locations (city, province, postal_code) VALUES (?, ?, ?)", locations_data) + + names_data = ( + [(name, 'male') for name in MALE_NAMES] + + [(name, 'female') for name in FEMALE_NAMES] + + [(name, 'last') for name in LAST_NAMES] + ) + cursor.executemany("INSERT INTO names (name, type) VALUES (?, ?)", names_data) + + # 6. SIM Providers and Prefixes + providers_data = [(provider,) for provider in SIM_PREFIXES.keys()] + cursor.executemany("INSERT INTO sim_providers (name) VALUES (?)", providers_data) + + prefixes_data = [] + for provider, prefixes in SIM_PREFIXES.items(): + for prefix in prefixes: + prefixes_data.append((provider, prefix)) + cursor.executemany("INSERT INTO sim_prefixes (provider_name, prefix) VALUES (?, ?)", prefixes_data) + + # 7. Castes + castes_data = [(caste,) for caste in CASTES] + cursor.executemany("INSERT INTO castes (name) VALUES (?)", castes_data) + + # 8. Sects + sects_data = [(sect,) for sect in SECTS] + cursor.executemany("INSERT INTO sects (name) VALUES (?)", sects_data) + + # 9. Banks + cursor.executemany("INSERT INTO banks (name, iban_code) VALUES (?, ?)", BANKS_WITH_CODES) + + #10. Institutions + cursor.executemany( + "INSERT INTO institutions (name, type, city) VALUES (?, ?, ?)", + INSTITUTIONS + ) + + + conn.commit() + finally: + conn.close() + + +if __name__ == "__main__": + db_file = os.path.join(os.path.dirname(__file__), "faker_pk.db") + initialize_database(db_file) + print("Database successfully initialized!") \ No newline at end of file diff --git a/faker_pk/personal.py b/faker_pk/personal.py index 19d52bb..0c27090 100644 --- a/faker_pk/personal.py +++ b/faker_pk/personal.py @@ -1,164 +1,77 @@ import random from datetime import date, timedelta - -MALE_NAMES = [ - "Ahmed", "Muhammad", "Ali", "Hassan", "Hussain", "Bilal", "Hamza", "Umar", "Usman", "Abdullah", - "Abdul Rahman", "Abdul Rehman", "Abdul Basit", "Abdul Hadi", "Abdul Wahab", "Abdul Samad", "Abdul Qadir", "Abdul Majeed", "Abdul Rauf", "Abdul Aziz", - "Abdul Kareem", "Abdul Aleem", "Abdul Ghaffar", "Abdul Ghani", "Abdul Haq", "Abdul Malik", "Abdul Shakoor", "Abdul Sattar", "Abdul Wasi", "Ahmad", - "Zeeshan", "Danish", "Faizan", "Fahad", "Waleed", "Zain", "Saad", "Ahsan", "Adeel", "Asad", - "Arsalan", "Shahzaib", "Shehryar", "Salman", "Noman", "Omer", "Tahir", "Talha", "Kashif", "Kamran", - "Shahid", "Naveed", "Imran", "Junaid", "Farhan", "Faisal", "Khalid", "Raza", "Rizwan", "Adnan", - "Arif", "Yasir", "Irfan", "Zubair", "Shayan", "Sameer", "Umair", "Huzaifa", "Ayaan", "Rayyan", - "Azaan", "Areeb", "Raheel", "Sufyan", "Haris", "Anas", "Arham", "Asim", "Moiz", "Hashir", - "Ibtisam", "Saif", "Ilyas", "Ismail", "Ibrahim", "Eesa", "Musa", "Yousuf", "Dawood", "Yunus", - "Nuh", "Luqman", "Taimoor", "Murtaza", "Baqir", "Rayan", "Shahmeer", "Shaheer", "Daniyal", "Arsab", - "Zarar", "Zaryab", "Aafaq", "Abrar", "Adil", "Amaan", "Amjad", "Anees", "Anwar", "Aqeel", - "Arqam", "Arsal", "Asghar", "Ashar", "Atif", "Awais", "Ayaz", "Azhar", "Azlan", "Barkat", - "Basim", "Babar", "Burhan", "Ehtisham", "Ehsan", "Faraz", "Farid", "Fawad", "Feroz", "Ghazanfar", - "Haider", "Hammad", "Hamid", "Hasan", "Haseeb", "Hashim", "Hisham", "Huzaifah", "Ijaz", "Imad", - "Inam", "Javed", "Kamal", "Khalil", "Khizar", "Mahad", "Mahir", "Mansoor", "Maaz", "Mazhar", - "Mehdi", "Muneeb", "Mustafa", "Naeem", "Nouman", "Qasim", "Rameez", "Rehan", "Sadiq", "Safeer", - "Saifullah", "Sarfaraz", "Shahbaz", "Shafqat", "Shafiq", "Sharjeel", "Shehzad", "Sohail", "Subhan", "Sultan", - "Tabish", "Talal", "Tauseef", "Tufail", "Ubaid", "Umer", "Usama", "Wajid", "Waqas", "Wasif", - "Yasir", "Yawar", "Yameen", "Yasin", "Zakariya", "Zaman", "Zawwar", "Zia", "Zohaib", "Zubair", - "Zainul Abidin", "Irham", "Taha", "Irtaza", "Reza", "Ehtesham", "Mirza", "Azim", "Saqib", "Shabbir", - "Tahseen", "Salman", "Fahim", "Jawad", "Sarmad", "Nabeel", "Faiq", "Rashid", "Rahim", "Habib", - "Munir", "Zameer", "Akram", "Zafar", "Wasim", "Nauman", "Nasir", "Khalil", "Jibran", "Kashan", - "Emaan", "Adi", "Fakhir", "Sibtain", "Farooq", "Saifur", "Faiz", "Nisar", "Salman", "Arqam", - "Rifat", "Tahmid", "Zohair", "Zaeem", "Jawwad", "Sarmal", "Arsal", "Areez", "Rizq", "Sarim", - "Zayyan", "Razaq", "Azfar", "Affan", "Hanzala", "Hammad", "Fuzail", "Ziyad", "Adeel", "Jameel", - "Karim", "Qadeer", "Hanan", "Rameen", "Tahal", "Shahroz", "Saamir", "Tahaib", "Yasrab", "Ammar", - "Shakir", "Rauf", "Sameel", "Tahseen", "Dani", "Azaib", "Hamdaan", "Maheer", "Uzair", "Sharif", - "Zarrar", "Firas", "Azmat", "Riaz", "Munawwar", "Kaleem", "Sufyan", "Zameel", "Sajid", "Sarim", - "Tabriz", "Yasin", "Aniq", "Rizal", "Zaid", "Irteza", "Aabid", "Zawar", "Aneeb", "Moazzam", - "Fida", "Najam", "Tauqeer", "Shakeel", "Rashad", "Rameel", "Najeeb", "Basit", "Fawzan" -] - -FEMALE_NAMES = [ - "Aaliya", "Aaminah", "Aamna", "Aaniya", "Aanisa", "Aasia", "Aasma", "Abida", "Adeela", "Adeelah", - "Afifa", "Afsheen", "Afza", "Afreen", "Aisha", "Aiman", "Aina", "Aini", "Aiza", "Aleena", - "Aleesha", "Aleya", "Aliya", "Alishba", "Amal", "Amara", "Amber", "Ameena", "Amira", "Anabia", - "Anaya", "Anila", "Aniqa", "Anisa", "Anum", "Anusha", "Anzela", "Aqsa", "Arfa", "Arisha", - "Arwa", "Asfa", "Asfiya", "Asma", "Asmara", "Atiya", "Ayesha", "Ayra", "Aysha", "Azka", - "Azra", "Basma", "Batool", "Benish", "Bisma", "Bushra", "Dur-e-Fatima", "Dua", "Eeman", "Eesha", - "Eiman", "Eliza", "Eman", "Emna", "Erum", "Esma", "Faiza", "Fakhra", "Falaq", "Falak", - "Fariha", "Farisha", "Farwah", "Farzana", "Fatima", "Fauzia", "Fiza", "Ghazal", "Ghazala", "Gulnaz", - "Gulrukh", "Habiba", "Hafsa", "Haleema", "Hania", "Hanin", "Hareem", "Haseena", "Hiba", "Hifza", - "Hina", "Hira", "Humaira", "Humna", "Iffat", "Ifra", "Imaan", "Inaya", "Iqra", "Iram", - "Irum", "Isha", "Ishaal", "Ishmal", "Isra", "Jameela", "Javeria", "Jannat", "Jasmin", "Jaweria", - "Jiya", "Kainat", "Khadija", "Khadijah", "Khansa", "Kiran", "Komal", "Laiba", "Laila", "Laraib", - "Lina", "Lubna", "Mahira", "Mahjabeen", "Mahnoor", "Mahrukh", "Maha", "Maliha", "Marium", "Maria", - "Mariam", "Marwa", "Maryam", "Maheen", "Mehak", "Mehr", "Mehwish", "Minal", "Mishal", "Misbah", - "Mona", "Mubashira", "Muqaddas", "Mysha", "Nabeela", "Nadia", "Nafisa", "Naila", "Najma", "Nashitah", - "Natasha", "Naureen", "Nayyab", "Neha", "Nida", "Nimra", "Nishat", "Noreen", "Nosheen", "Nusrat", - "Nuha", "Obaidah", "Parveen", "Qandeel", "Quratulain", "Rabiya", "Rabia", "Rafia", "Rafiya", "Rameen", - "Rania", "Raniah", "Rashida", "Rida", "Rimsha", "Rizwana", "Roha", "Romana", "Roohi", "Ruba", - "Rubina", "Rukhsar", "Rumaisa", "Ruqayya", "Saba", "Sabeen", "Sabahat", "Sadia", "Sadiaa", "Sadiya", - "Safia", "Sahar", "Saira", "Sajida", "Sakina", "Salma", "Sameena", "Samina", "Samiya", "Sana", - "Saniya", "Sanober", "Sara", "Sarah", "Sasha", "Shabana", "Shagufta", "Shaheen", "Shaista", "Shakila", - "Shamaila", "Shamim", "Shanza", "Shazia", "Sheeba", "Shehnaz", "Shifa", "Shiza", "Sobia", "Sonia", - "Subha", "Subhana", "Suhana", "Sumaira", "Sumaiya", "Sundus", "Tabinda", "Tabassum", "Taha", "Tahira", - "Tahreem", "Tahirah", "Tania", "Tanisha", "Tanzeela", "Tayyaba", "Tehreem", "Tooba", "Ujala", "Umama", - "Umayrah", "Ume Habiba", "Ume Hani", "Ume Kulsoom", "Umme Hani", "Umme Rubab", "Umme Salma", "Umaima", "Umairah", "Urooj", - "Urwa", "Uzma", "Wajiha", "Warda", "Wardah", "Yasira", "Yasmeen", "Yumna", "Zainab", "Zakia", - "Zainah", "Zahida", "Zahra", "Zaib", "Zakia", "Zakiaa", "Zameena", "Zareen", "Zarish", "Zarmeen", - "Zarqa", "Zartaj", "Zaryab", "Zehra", "Zeenat", "Zia", "Zimal", "Zinia", "Zobia", "Zohra", - "Zonaira", "Zoya", "Zubaida", "Zulekha", "Zunaira", "Zunisha", "Zunnoor", "Aamira", "Adeeba", "Areeba", - "Armeen", "Arooba", "Asiya", "Aqleema", "Aqra", "Armina", "Asra", "Ayisha", "Azima", "Bareera", - "Bushra", "Dania", "Daniah", "Dua", "Eimaan", "Elina", "Elaf", "Erum", "Esha", "Eshaal", - "Fakhira", "Faria", "Fariyal", "Fawzia", "Fareeha", "Farheen", "Fizza", "Gulshan", "Gulzar", "Hadia", - "Hajra", "Haleemah", "Hamna", "Haniyah", "Harema", "Huma", "Humera", "Huriya", "Inara", "Insha", - "Iqra", "Irsa", "Ishaqueen", "Ishrat", "Jannat", "Jawaria", "Javeriya", "Kanza", "Khadeeja", "Khizra", - "Komila", "Laiba", "Laila", "Lamees", "Mahgul", "Mahnoor", "Mahwish", "Maiza", "Mamoona", "Manahil", - "Mariam", "Marrium", "Marjan", "Maryam", "Mehreen", "Mehrunisa", "Minal", "Mishka", "Mishaal", "Mishkaat", - "Mona", "Muqaddas", "Myra", "Naheed", "Naila", "Nimrah", "Noreen", "Noshaba", "Nusrat", "Pari", - "Parveen", "Qurat", "Rabi", "Rafia", "Ramsha", "Rania", "Rija", "Rimsha", "Rishma", "Roohi", - "Romaisa", "Ruba", "Rukhsana", "Saba", "Sabiha", "Saeeda", "Sahar", "Sajida", "Sakina", "Salma", - "Sameera", "Samra", "Sania", "Saniya", "Saniah", "Saniaa", "Sania", "Saniaa", "Saniah", "Samiha" -] - -LAST_NAMES = [ - "Abbasi", "Abbas", "Abid", "Afzal", "Ahmad", "Ahmed", "Akbar", "Akhter", "Alam", "Ali", - "Amjad", "Anjum", "Ansari", "Arif", "Asad", "Ashfaq", "Asghar", "Aslam", "Atif", "Awan", - "Azam", "Azhar", "Babar", "Baig", "Bajwa", "Bakht", "Baloch", "Bangash", "Basit", "Batool", - "Bhatti", "Bukhari", "Butt", "Chaudhry", "Cheema", "Chishti", "Dar", "Danish", "Daud", "Deen", - "Durrani", "Ejaz", "Fahim", "Faheem", "Farid", "Farooq", "Farrukh", "Fazal", "Feroz", "Ghafoor", - "Ghani", "Ghazanfar", "Ghaznavi", "Ghauri", "Ghulam", "Gohar", "Habib", "Hadi", "Hafeez", "Hafiz", - "Haider", "Hameed", "Hamid", "Hanif", "Hashim", "Hasnain", "Hassan", "Hayat", "Hussain", "Hyder", - "Iftikhar", "Ijaz", "Ilyas", "Imam", "Imran", "Inam", "Iqbal", "Irshad", "Ismail", "Ishaq", - "Jadoon", "Jahangir", "Jamal", "Jamali", "Jamshed", "Javed", "Jawad", "Kabir", "Kadir", "Kaleem", - "Kamran", "Kamil", "Karim", "Kashif", "Kazmi", "Khalid", "Khalil", "Khan", "Khizar", "Khurram", - "Latif", "Mahmood", "Malik", "Manzoor", "Masood", "Mazhar", "Mehmood", "Mir", "Mirza", "Moin", - "Mohsin", "Moinuddin", "Monis", "Mubashir", "Mujeeb", "Mukhtar", "Munir", "Murad", "Mustafa", "Murtaza", - "Nadeem", "Naeem", "Naseem", "Nasir", "Nawaz", "Niaz", "Noor", "Noman", "Numan", "Obaid", - "Qadir", "Qaiser", "Qamar", "Qasim", "Qayyum", "Qureshi", "Rafiq", "Rafique", "Rahim", "Raja", - "Rameez", "Rana", "Rasheed", "Rashid", "Rauf", "Raza", "Razzaq", "Rehman", "Riaz", "Rizwan", - "Sabir", "Sadiq", "Safeer", "Safi", "Saeed", "Safiullah", "Sajid", "Salim", "Saleem", "Salman", - "Sami", "Sarfaraz", "Sarfraz", "Shafi", "Shafique", "Shahid", "Shakeel", "Sharif", "Shaukat", "Sheikh", - "Shehzad", "Sheraz", "Shoukat", "Siddiq", "Siddique", "Sohail", "Suleman", "Sultan", "Tahir", "Talib", - "Tariq", "Tufail", "Ubaid", "Umar", "Usman", "Waheed", "Wali", "Waseem", "Yaseen", "Yasin", - "Yousaf", "Younas", "Yousuf", "Zafar", "Zahid", "Zakir", "Zaman", "Zameer", "Zarif", "Zubair", - "Abbass", "Aftab", "Akram", "Alvi", "Anwar", "Ashraf", "Aziz", "Badar", "Bari", "Bashir", - "Basharat", "Bostan", "Burhan", "Chughtai", "Dawar", "Ejaz", "Ehsan", "Faisal", "Farhan", "Fazal", - "Gul", "Gulzar", "Haqqani", "Hashmi", "Hayee", "Hussaini", "Jalil", "Jamil", "Junaid", "Kabir", - "Kamal", "Khawaja", "Khattak", "Kiani", "Khoso", "Khokhar", "Khosa", "Langrial", "Lodhi", "Mahmud", - "Malook", "Mandokhel", "Memon", "Mughal", "Naqvi", "Naseer", "Niazi", "Orakzai", "Pathan", "Pirzada", - "Qadri", "Qasmi", "Rashidi", "Saboor", "Sadaqat", "Sajjad", "Saleh", "Samiullah", "Sarwar", "Shafiullah", - "Shah", "Shahbaz", "Shahzad", "Shams", "Sharafat", "Sharifuddin", "Shoaib", "Sikandar", "Sohaib", "Subhan", - "Tabassum", "Taha", "Talha", "Tanveer", "Tauqeer", "Tariq", "Uddin", "Wahid", "Wajid", "Waqas", - "Yar", "Yaseer", "Zaheer", "Zaman", "Zarrar", "Zeeshan", "Zia", "Zubairi", "Agha", "Aliani", - "Ansari", "Arain", "Asmat", "Atta", "Baber", "Balochi", "Bangulzai", "Bhinder", "Chohan", "Chughtai", - "Dasti", "Daudpota", "Dogar", "Domki", "Fani", "Gabol", "Gandapur", "Gill", "Goraya", "Gulshan", - "Hingoro", "Hoti", "Jakhrani", "Jatoi", "Junejo", "Kalhoro", "Kakar", "Kashmiri", "Khoso", "Khatri", - "Kundi", "Laghari", "Langah", "Leghari", "Liaqat", "Lodhi", "Mahsud", "Magsi", "Marwat", "Mashwani", - "Mengal", "Mirani", "Mitha", "Mugheri", "Mundokhel", "Naseeruddin", "Niazi", "Orakzai", "Qaimkhani", "Qasoori", - "Rajput", "Rind", "Samar", "Samad", "Sandhu", "Sarai", "Sayyid", "Shahwani", "Sheerazi", "Sial", - "Sindhu", "Soomro", "Soomra", "Suddiqi", "Syed", "Talpur", "Tanoli", "Tarin", "Tiwana", "Toor", - "Turk", "Wazir", "Yusufzai", "Zehri", "Zuberi", "Awan", "Kiyani", "Khakwani", "Taj", "Meer", - "Mirwani", "Chohan", "Bhutta", "Randhawa", "Sandhu", "Ghuman", "Raja", "Gillani", "Kazmi", "Naqash", - "Abbassi", "Bohra", "Hanjra", "Rajpoot", "Siddiqui", "Qaim", "Shuja", "Qamar", "Irfan", "Talpur" -] - -SIM_PROVIDERS = ["Jazz", "Zong", "Ufone", "Telenor", "Warid", "Onic"] - -def sim_provider(): - return random.choice(SIM_PROVIDERS) - - -CASTES = ["Sheikh", "Ansari", "Raja", "Malik", "Qureshi", "Chaudhry", "Jutt", "Butt", "Rajput", "Rana"] -SECTS = ["Sunni", "Shia"] +from .utils import query_value, query_list + + +def sim_provider(): + """Return a random SIM provider name.""" + return query_value("SELECT name FROM sim_providers ORDER BY RANDOM() LIMIT 1") + def caste(): - return random.choice(CASTES) + """Return a random caste name.""" + return query_value("SELECT name FROM castes ORDER BY RANDOM() LIMIT 1") + def sect(): - return random.choice(SECTS) - + """Return a random sect name.""" + return query_value("SELECT name FROM sects ORDER BY RANDOM() LIMIT 1") + + def dob(start_year=1950, end_year=2006): - """Generate a random date of birth between start_year and end_year""" + """Generate a random date of birth between start_year and end_year.""" start = date(start_year, 1, 1) - end = date(end_year, 12, 31) + end = date(end_year, 12, 31) delta = end - start random_days = random.randint(0, delta.days) return start + timedelta(days=random_days) -def male_name(): + +def male_name(): """Generate a random male full name.""" - return random.choice(MALE_NAMES) + " " + random.choice(LAST_NAMES) - + first = query_value("SELECT name FROM names WHERE type = 'male' ORDER BY RANDOM() LIMIT 1") + last = query_value("SELECT name FROM names WHERE type = 'last' ORDER BY RANDOM() LIMIT 1") + return f"{first} {last}" + + def female_name(): """Generate a random female full name.""" - return random.choice(FEMALE_NAMES) + " " + random.choice(LAST_NAMES) + first = query_value("SELECT name FROM names WHERE type = 'female' ORDER BY RANDOM() LIMIT 1") + last = query_value("SELECT name FROM names WHERE type = 'last' ORDER BY RANDOM() LIMIT 1") + return f"{first} {last}" -def cnic(): + +def cnic(gender=None): + """Generate a random CNIC. + If gender is 'male' or 'm', the last digit is odd. + If gender is 'female' or 'f', the last digit is even. + """ part1 = str(random.randint(10000, 99999)) part2 = str(random.randint(1000000, 9999999)) - part3 = str(random.randint(0, 9)) - return f"{part1}-{part2}-{part3}" -def phone_number(): - prefixes = ["300", "301", "302", "303", "304", "305", "306", "307", "308", "309", - "310", "311", "312", "313", "314", "315", "316", "317", "318", "319"] + if gender and gender.lower() in ('male', 'm'): + part3 = str(random.choice([1, 3, 5, 7, 9])) + elif gender and gender.lower() in ('female', 'f'): + part3 = str(random.choice([0, 2, 4, 6, 8])) + else: + part3 = str(random.randint(0, 9)) + + return f"{part1}-{part2}-{part3}" + + +def phone_number(provider=None): + """Generate a random Pakistani phone number. + If provider is specified, use that provider's prefixes only. + """ + if provider: + prefixes = query_list( + "SELECT prefix FROM sim_prefixes WHERE provider_name = ?", (provider,) + ) + if not prefixes: + raise ValueError(f"SIM provider '{provider}' not found.") + else: + prefixes = query_list("SELECT prefix FROM sim_prefixes") + prefix = random.choice(prefixes) remaining = str(random.randint(1000000, 9999999)) return f"+92{prefix}{remaining}" \ No newline at end of file diff --git a/faker_pk/provider.py b/faker_pk/provider.py index 3bfbdfa..dfb5c4c 100644 --- a/faker_pk/provider.py +++ b/faker_pk/provider.py @@ -1,114 +1,89 @@ -from faker.providers import BaseProvider -import random -from .personal import ( - male_name, female_name, cnic, phone_number, sim_provider, caste, sect, dob -) +from faker.providers import BaseProvider +from .personal import male_name, female_name, cnic, phone_number, sim_provider, caste, sect, dob from .address import city, province, full_address -from .company import ( - company_name, industry_name, job_title, job_title_with_industry, - salary, bank_name, iban -) - +from .company import company_name, industry_name, job_title, job_title_with_industry, salary, bank_name, iban +from .education import institution, student_dob, student_profile class FakerPKProvider(BaseProvider): """Faker provider for Pakistani personal info, addresses, companies, jobs, and more.""" + def _generate_multiple(self, func, count, **kwargs): + """Generate one or many values based on count.""" + if count == 1: + return func(**kwargs) + return [func(**kwargs) for _ in range(count)] + # -------------------- # Personal Info # -------------------- def pk_male_name(self, count=1): - if count == 1: - return male_name() - return [male_name() for _ in range(count)] + return self._generate_multiple(male_name, count) def pk_female_name(self, count=1): - if count == 1: - return female_name() - return [female_name() for _ in range(count)] + return self._generate_multiple(female_name, count) - def pk_cnic(self, count=1): - if count == 1: - return cnic() - return [cnic() for _ in range(count)] + def pk_cnic(self, count=1, gender=None): + return self._generate_multiple(cnic, count, gender=gender) - def pk_phone_number(self, count=1): - if count == 1: - return phone_number() - return [phone_number() for _ in range(count)] + def pk_phone_number(self, count=1, provider=None): + return self._generate_multiple(phone_number, count, provider=provider) def pk_sim_provider(self, count=1): - if count == 1: - return sim_provider() - return [sim_provider() for _ in range(count)] + return self._generate_multiple(sim_provider, count) def pk_caste(self, count=1): - if count == 1: - return caste() - return [caste() for _ in range(count)] + return self._generate_multiple(caste, count) def pk_sect(self, count=1): - if count == 1: - return sect() - return [sect() for _ in range(count)] + return self._generate_multiple(sect, count) def pk_dob(self, count=1): - if count == 1: - return dob() - return [dob() for _ in range(count)] + return self._generate_multiple(dob, count) # -------------------- # Address Info # -------------------- - def pk_city(self, count=1): - if count == 1: - return city() - return [city() for _ in range(count)] + def pk_city(self, count=1, province=None): + return self._generate_multiple(city, count, province=province) - def pk_province(self, count=1): - if count == 1: - return province() - return [province() for _ in range(count)] + def pk_province(self, count=1, city=None): + return self._generate_multiple(province, count, city=city) - def pk_full_address(self, count=1): - if count == 1: - return full_address() - return [full_address() for _ in range(count)] + def pk_full_address(self, count=1, city=None, province=None): + return self._generate_multiple(full_address, count, city=city, province=province) # -------------------- # Company Info # -------------------- - def pk_company_name(self, count=1): - if count == 1: - return company_name() - return [company_name() for _ in range(count)] + def pk_company_name(self, count=1, industry=None): + return self._generate_multiple(company_name, count, industry=industry) def pk_industry_name(self, count=1): - if count == 1: - return industry_name() - return [industry_name() for _ in range(count)] + return self._generate_multiple(industry_name, count) + + def pk_bank_name(self, count=1): + return self._generate_multiple(bank_name, count) + + def pk_iban(self, count=1, bank=None): + return self._generate_multiple(iban, count, bank=bank) + # -------------------- + # Job Info + # -------------------- def pk_job_title(self, count=1, industry=None): - if count == 1: - return job_title(industry=industry) - return [job_title(industry=industry) for _ in range(count)] + return self._generate_multiple(job_title, count, industry=industry) def pk_job_title_with_industry(self, count=1): - if count == 1: - return job_title_with_industry() - return [job_title_with_industry() for _ in range(count)] + return self._generate_multiple(job_title_with_industry, count) def pk_salary(self, count=1, industry=None): - if count == 1: - return salary(industry=industry) - return [salary(industry=industry) for _ in range(count)] - - def pk_bank_name(self, count=1): - if count == 1: - return bank_name() - return [bank_name() for _ in range(count)] + return self._generate_multiple(salary, count, industry=industry) + # -------------------- + # Institution Info + # -------------------- + def institution(self, count=1, level=None, city=None, province=None): + return self._generate_multiple(institution, count, level=level, city=city, province=province) - def pk_iban(self, count=1): - if count == 1: - return iban() - return [iban() for _ in range(count)] + def student_profile(self, count=1, level=None, province=None): + return self._generate_multiple(student_profile, count, level=level, province=province) \ No newline at end of file diff --git a/faker_pk/utils.py b/faker_pk/utils.py index e69de29..e7dab92 100644 --- a/faker_pk/utils.py +++ b/faker_pk/utils.py @@ -0,0 +1,50 @@ +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(__file__), "faker_pk.db") +_db_initialized = False + + +def ensure_db_exists(): + """Initialize the database if it does not exist yet.""" + global _db_initialized + if _db_initialized: + return + if not os.path.exists(DB_PATH): + try: + from .initialize_db import initialize_database + initialize_database(DB_PATH) + except Exception as e: + import warnings + warnings.warn(f"faker-pk: could not auto-initialize database: {e}") + _db_initialized = True + + +def _run(query, params, fetch): + """Internal helper: open connection, run query, return fetched result.""" + ensure_db_exists() + with sqlite3.connect(DB_PATH) as conn: + cursor = conn.cursor() + cursor.execute(query, params) + return fetch(cursor) + + +def query_value(query, params=()): + """Return the first column of the first matching row.""" + row = _run(query, params, lambda c: c.fetchone()) + return row[0] if row else None + + +def query_row(query, params=()): + """Return the first matching row as a tuple.""" + return _run(query, params, lambda c: c.fetchone()) + + +def query_list(query, params=()): + """Return a flat list of the first column from all matching rows.""" + return _run(query, params, lambda c: [r[0] for r in c.fetchall()]) + + +def query_rows(query, params=()): + """Return all matching rows as a list of tuples.""" + return _run(query, params, lambda c: c.fetchall()) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4aeae90..3714935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] - +[tool.setuptools.package-data] +faker_pk = ["*.db"] [project.urls] "Homepage" = "https://github.com/Khubaib8281/faker-pk" \ No newline at end of file diff --git a/tests/test_institution_filtered_by_city.py b/tests/test_institution_filtered_by_city.py new file mode 100644 index 0000000..c9d6592 --- /dev/null +++ b/tests/test_institution_filtered_by_city.py @@ -0,0 +1,8 @@ +from faker_pk import FakerPK +from faker_pk.utils import query_value + +def test_institution_filtered_by_city(faker_pk: FakerPK): + inst = faker_pk.institution(city="Lahore") + assert isinstance(inst, str) + inst_city = query_value("SELECT city FROM institutions WHERE name = ?", (inst,)) + assert inst_city == "Lahore" diff --git a/tests/test_institution_filtered_by_level.py b/tests/test_institution_filtered_by_level.py new file mode 100644 index 0000000..ef5406d --- /dev/null +++ b/tests/test_institution_filtered_by_level.py @@ -0,0 +1,8 @@ +from faker_pk import FakerPK +from faker_pk.utils import query_value + +def test_institution_filtered_by_level(faker_pk: FakerPK): + inst = faker_pk.institution(level="university") + assert isinstance(inst, str) + inst_type = query_value("SELECT type FROM institutions WHERE name = ?", (inst,)) + assert inst_type == "university" diff --git a/tests/test_institution_filtered_by_province.py b/tests/test_institution_filtered_by_province.py new file mode 100644 index 0000000..95ffd39 --- /dev/null +++ b/tests/test_institution_filtered_by_province.py @@ -0,0 +1,11 @@ +from faker_pk import FakerPK +from faker_pk.utils import query_value + +def test_institution_filtered_by_province(faker_pk: FakerPK): + inst = faker_pk.institution(province="Khyber Pakhtunkhwa") + assert isinstance(inst, str) + inst_province = query_value( + "SELECT l.province FROM institutions i JOIN locations l ON i.city = l.city WHERE i.name = ?", + (inst,) + ) + assert inst_province == "Khyber Pakhtunkhwa" diff --git a/tests/test_realistic_combinations.py b/tests/test_realistic_combinations.py new file mode 100644 index 0000000..3a90409 --- /dev/null +++ b/tests/test_realistic_combinations.py @@ -0,0 +1,149 @@ +import os +import pytest +from faker_pk.utils import query_value + + +class TestCNICGender: + """Verify CNIC last digit aligns with gender.""" + + def test_male_cnic_ends_odd(self, faker_pk): + for _ in range(50): + c = faker_pk.cnic(gender='male') + last_digit = int(c[-1]) + assert last_digit % 2 == 1, f"Male CNIC should end odd, got {c}" + + def test_female_cnic_ends_even(self, faker_pk): + for _ in range(50): + c = faker_pk.cnic(gender='female') + last_digit = int(c[-1]) + assert last_digit % 2 == 0, f"Female CNIC should end even, got {c}" + + def test_shorthand_gender_m(self, faker_pk): + for _ in range(20): + c = faker_pk.cnic(gender='m') + assert int(c[-1]) % 2 == 1 + + def test_shorthand_gender_f(self, faker_pk): + for _ in range(20): + c = faker_pk.cnic(gender='f') + assert int(c[-1]) % 2 == 0 + + def test_no_gender_still_valid(self, faker_pk): + c = faker_pk.cnic() + assert isinstance(c, str) + assert len(c) == 15 + + +class TestAddressConsistency: + """Verify city, province, and postal code combinations are realistic.""" + + def test_city_filtered_by_province(self, faker_pk): + city = faker_pk.city(province="Sindh") + # Verify this city actually belongs to Sindh in the DB + province = query_value( + "SELECT province FROM locations WHERE city = ?", (city,) + ) + assert province == "Sindh" + + def test_province_for_known_city(self, faker_pk): + province = faker_pk.province(city="Lahore") + assert province == "Punjab" + + def test_full_address_with_city(self, faker_pk): + addr = faker_pk.full_address(city="Karachi") + assert "Karachi" in addr + assert "Sindh" in addr + assert "74000" in addr + + def test_full_address_with_province(self, faker_pk): + addr = faker_pk.full_address(province="Balochistan") + assert "Balochistan" in addr + # Verify the city in the address actually belongs to Balochistan + balochistan_cities = query_value( + "SELECT city FROM locations WHERE province = 'Balochistan' AND ? LIKE '%' || city || '%'", + (addr,) + ) + assert balochistan_cities is not None + + +class TestBankIBAN: + """Verify IBAN contains the correct bank identifier code.""" + + def test_iban_with_specific_bank(self, faker_pk): + iban_code = query_value( + "SELECT iban_code FROM banks WHERE name = ?", ("Meezan Bank",) + ) + iban = faker_pk.iban(bank="Meezan Bank") + assert iban.startswith("PK") + assert iban_code in iban + + def test_iban_format(self, faker_pk): + iban = faker_pk.iban() + assert iban.startswith("PK") + assert len(iban) > 10 + + def test_invalid_bank_raises(self, faker_pk): + with pytest.raises(ValueError): + faker_pk.iban(bank="Nonexistent Bank") + + +class TestPhoneProvider: + """Verify phone numbers use correct provider prefixes.""" + + def test_phone_with_specific_provider(self, faker_pk): + from faker_pk.utils import query_list + zong_prefixes = query_list( + "SELECT prefix FROM sim_prefixes WHERE provider_name = ?", ("Zong",) + ) + phone = faker_pk.phone_number(provider="Zong") + # Strip +92, the next 3 digits should be a Zong prefix + prefix = phone[3:6] + assert prefix in zong_prefixes, f"Prefix {prefix} not in Zong prefixes" + + def test_invalid_provider_raises(self, faker_pk): + with pytest.raises(ValueError): + faker_pk.phone_number(provider="NonexistentProvider") + + +class TestCompanyIndustry: + """Verify company names filter correctly by industry.""" + + def test_company_filtered_by_industry(self, faker_pk): + company = faker_pk.company_name(industry="IT") + # Verify this company actually belongs to IT in the DB + industry_code = query_value( + "SELECT industry_code FROM companies WHERE name = ?", (company,) + ) + assert industry_code == "IT" + + def test_invalid_industry_raises(self, faker_pk): + with pytest.raises(ValueError): + faker_pk.company_name(industry="FakeIndustry") + + +class TestAutoInitialization: + """Verify the database auto-creates if the .db file is missing.""" + + def test_db_recreates_at_new_path(self, tmp_path): + import faker_pk.utils as utils_module + + test_db_path = str(tmp_path / "faker_pk.db") + original_path = utils_module.DB_PATH + + try: + # Point utils to a path where no DB exists + utils_module.DB_PATH = test_db_path + utils_module._db_initialized = False + + # Confirm no DB exists yet + assert not os.path.exists(test_db_path) + + # This query should trigger auto-initialization + result = utils_module.query_value("SELECT COUNT(*) FROM names") + assert result is not None + assert result > 0 + assert os.path.exists(test_db_path) + finally: + # Restore original path and reset flag + utils_module.DB_PATH = original_path + utils_module._db_initialized = False \ No newline at end of file diff --git a/tests/test_student_dob.py b/tests/test_student_dob.py new file mode 100644 index 0000000..991f2a5 --- /dev/null +++ b/tests/test_student_dob.py @@ -0,0 +1,20 @@ +from datetime import date +from faker_pk.education import student_dob + +def test_student_dob_age_ranges(): + today = date.today() + + for _ in range(20): + dob = student_dob(level="school") + age = (today - dob).days // 365 + assert 5 <= age <= 14 + + for _ in range(20): + dob = student_dob(level="college") + age = (today - dob).days // 365 + assert 14 <= age <= 18 + + for _ in range(20): + dob = student_dob(level="university") + age = (today - dob).days // 365 + assert 18 <= age <= 25 diff --git a/tests/test_student_profile_consistency.py b/tests/test_student_profile_consistency.py new file mode 100644 index 0000000..13387b6 --- /dev/null +++ b/tests/test_student_profile_consistency.py @@ -0,0 +1,14 @@ +from faker_pk import FakerPK +from faker_pk.utils import query_row + +def test_student_profile_consistency(faker_pk: FakerPK): + for _ in range(20): + profile = faker_pk.student_profile(province="Punjab") + assert profile["province"] == "Punjab" + row = query_row( + "SELECT i.city, l.province FROM institutions i JOIN locations l ON i.city = l.city WHERE i.name = ?", + (profile["institution"],) + ) + assert row is not None + assert row[0] == profile["city"] + assert row[1] == profile["province"] diff --git a/tests/test_student_profile_structure.py b/tests/test_student_profile_structure.py new file mode 100644 index 0000000..ac052b9 --- /dev/null +++ b/tests/test_student_profile_structure.py @@ -0,0 +1,9 @@ +from faker_pk import FakerPK + +def test_student_profile_structure(faker_pk: FakerPK): + profile = faker_pk.student_profile() + assert isinstance(profile, dict) + required_keys = ["name", "gender", "cnic", "institution", "level", "city", "province", "dob"] + for key in required_keys: + assert key in profile + assert profile[key] is not None