|
import streamlit as st |
|
import pandas as pd |
|
import bisect |
|
import requests |
|
from io import BytesIO |
|
from bs4 import BeautifulSoup |
|
|
|
|
|
|
|
|
|
|
|
@st.cache_data(ttl=3600) |
|
def fetch_ods_file(): |
|
""" |
|
Fetches the .ods file from the visa decisions website and returns its binary content. |
|
|
|
Returns: |
|
- A BytesIO object containing the file content if successful. |
|
- None if the file could not be fetched. |
|
""" |
|
url = "https://www.ireland.ie/en/india/newdelhi/services/visas/processing-times-and-decisions/" |
|
headers = { |
|
"User-Agent": ( |
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " |
|
"(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" |
|
) |
|
} |
|
|
|
response = requests.get(url, headers=headers) |
|
if response.status_code == 200: |
|
soup = BeautifulSoup(response.content, 'html.parser') |
|
links = soup.find_all('a') |
|
|
|
|
|
file_url = None |
|
for link in links: |
|
if "Visa decisions made from 1 January 2024 to" in link.get_text(strip=True): |
|
file_url = link.get('href') |
|
break |
|
|
|
if file_url: |
|
|
|
if not file_url.startswith("http"): |
|
file_url = requests.compat.urljoin(url, file_url) |
|
|
|
file_response = requests.get(file_url, headers=headers) |
|
if file_response.status_code == 200: |
|
return BytesIO(file_response.content) |
|
return None |
|
|
|
@st.cache_data |
|
def prepare_dataframe(file): |
|
""" |
|
Prepares and cleans the DataFrame from the fetched .ods file. |
|
|
|
Args: |
|
file: The .ods file content as BytesIO. |
|
|
|
Returns: |
|
A cleaned and sorted DataFrame ready for searching. |
|
""" |
|
df = pd.read_excel(file, engine='odf') |
|
df.drop(columns=["Unnamed: 0", "Unnamed: 1"], inplace=True, errors="ignore") |
|
df.dropna(how="all", inplace=True) |
|
df.reset_index(drop=True, inplace=True) |
|
|
|
|
|
for idx, row in df.iterrows(): |
|
if row["Unnamed: 2"] == "Application Number" and row["Unnamed: 3"] == "Decision": |
|
df.columns = ["Application Number", "Decision"] |
|
df = df.iloc[idx + 1:] |
|
break |
|
|
|
|
|
df["Application Number"] = df["Application Number"].astype(str).str.strip().astype(int) |
|
df.sort_values(by="Application Number", inplace=True) |
|
df.reset_index(drop=True, inplace=True) |
|
|
|
return df |
|
|
|
|
|
|
|
|
|
|
|
def binary_search_nearest(df, target): |
|
""" |
|
Uses binary search to find the nearest application numbers in the DataFrame. |
|
|
|
Args: |
|
df: The DataFrame containing the application numbers. |
|
target: The target application number to search for. |
|
|
|
Returns: |
|
Two nearest application numbers (before and after the target). |
|
""" |
|
application_numbers = df["Application Number"].tolist() |
|
pos = bisect.bisect_left(application_numbers, target) |
|
|
|
before = application_numbers[pos - 1] if pos > 0 else None |
|
after = application_numbers[pos] if pos < len(application_numbers) else None |
|
|
|
return before, after |
|
|
|
|
|
|
|
|
|
|
|
def search_application(df): |
|
""" |
|
Handles the user input and searches for the application number in the DataFrame. |
|
|
|
Args: |
|
df: The DataFrame containing application numbers and decisions. |
|
""" |
|
user_input = st.text_input("Enter your Application Number (including IRL if applicable):") |
|
|
|
if user_input: |
|
|
|
if "irl" in user_input.lower(): |
|
try: |
|
application_number = int("".join(filter(str.isdigit, user_input.lower().split("irl")[-1]))) |
|
if len(str(application_number)) < 8: |
|
st.warning("Please enter a valid application number with at least 8 digits after IRL.") |
|
return |
|
except ValueError: |
|
st.error("Invalid input after IRL. Please enter only digits.") |
|
return |
|
else: |
|
if not user_input.isdigit() or len(user_input) < 8: |
|
st.warning("Please enter at least 8 digits for your VISA application number.") |
|
return |
|
elif len(user_input) > 8: |
|
st.warning("The application number cannot exceed 8 digits. Please correct your input.") |
|
return |
|
application_number = int(user_input) |
|
|
|
|
|
result = df[df["Application Number"] == application_number] |
|
|
|
if not result.empty: |
|
decision = result.iloc[0]["Decision"] |
|
if decision.lower() == "refused": |
|
st.error(f"Application Number: {application_number}\n\nDecision: **Refused**") |
|
elif decision.lower() == "approved": |
|
st.success(f"Application Number: {application_number}\n\nDecision: **Approved**") |
|
else: |
|
st.info(f"Application Number: {application_number}\n\nDecision: **{decision}**") |
|
else: |
|
st.warning(f"No record found for Application Number: {application_number}.") |
|
|
|
|
|
before, after = binary_search_nearest(df, application_number) |
|
|
|
nearest_records = pd.DataFrame({ |
|
"Nearest Application": ["Before", "After"], |
|
"Application Number": [before, after], |
|
"Decision": [ |
|
df[df["Application Number"] == before]["Decision"].values[0] if before else None, |
|
df[df["Application Number"] == after]["Decision"].values[0] if after else None |
|
], |
|
"Difference": [ |
|
application_number - before if before else None, |
|
after - application_number if after else None |
|
] |
|
}).dropna() |
|
|
|
if not nearest_records.empty: |
|
st.subheader("Nearest Application Numbers") |
|
st.table(nearest_records.reset_index(drop=True)) |
|
else: |
|
st.info("No nearest application numbers found.") |
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
st.title("Visa Application Status Checker") |
|
|
|
|
|
ods_file = fetch_ods_file() |
|
if ods_file: |
|
df = prepare_dataframe(ods_file) |
|
if df is not None: |
|
search_application(df) |
|
else: |
|
st.error("Failed to prepare the data.") |
|
else: |
|
st.error("Failed to fetch the .ods file.") |
|
|
|
if __name__ == "__main__": |
|
main() |
|
|