Handwritten Math Solver

 Install requirements

pip install tensorflow Pillow opencv-python numpy sympy

Train a digit model once (MNIST) — train_mnist_cnn.py

This trains a small CNN on MNIST and saves mnist_cnn.h5.

# train_mnist_cnn.py

import tensorflow as tf

from tensorflow import keras

from tensorflow.keras import layers


def build_model():

    model = keras.Sequential([

        layers.Input(shape=(28, 28, 1)),

        layers.Conv2D(32, 3, activation='relu'),

        layers.Conv2D(32, 3, activation='relu'),

        layers.MaxPooling2D(),

        layers.Dropout(0.25),


        layers.Conv2D(64, 3, activation='relu'),

        layers.Conv2D(64, 3, activation='relu'),

        layers.MaxPooling2D(),

        layers.Dropout(0.25),


        layers.Flatten(),

        layers.Dense(128, activation='relu'),

        layers.Dropout(0.5),

        layers.Dense(10, activation='softmax')

    ])

    model.compile(optimizer='adam',

                  loss='sparse_categorical_crossentropy',

                  metrics=['accuracy'])

    return model


def main():

    (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

    x_train = x_train.astype("float32") / 255.0

    x_test = x_test.astype("float32") / 255.0

    x_train = x_train[..., None]

    x_test = x_test[..., None]


    model = build_model()

    model.fit(x_train, y_train, batch_size=128, epochs=5, validation_split=0.1)

    test_loss, test_acc = model.evaluate(x_test, y_test, verbose=0)

    print(f"Test accuracy: {test_acc:.4f}")

    model.save("mnist_cnn.h5")

    print("Saved model to mnist_cnn.h5")


if __name__ == "__main__":

    main()

The GUI solver — handwritten_math_solver.py

# handwritten_math_solver.py
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageDraw, ImageOps
import numpy as np
import cv2
import io
from sympy import sympify, simplify
from tensorflow.keras.models import load_model
# ---- Config ----
MODEL_PATH = "mnist_cnn.h5"
CANVAS_SIZE = 400           # drawing canvas (square)
DRAW_WIDTH = 14             # brush thickness (thicker = easier OCR)
MIN_CONTOUR_AREA = 60       # filter noise
PADDING = 8                 # pad per glyph before resize to 28x28
# Heuristics thresholds for operators
MINUS_AR_THRESH = 2.0       # width/height > this → likely '-'
MINUS_HEIGHT_FRAC = 0.45    # symbol height relative to median digit height (shorter → minus)
PLUS_PEAK_FRAC = 0.6        # vertical and horizontal central peaks to consider '+'
class MathSolverApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Handwritten Math Solver (digits + +/−)")
        # Canvas to draw
        self.canvas = tk.Canvas(root, width=CANVAS_SIZE, height=CANVAS_SIZE, bg="white", cursor="cross")
        self.canvas.grid(row=0, column=0, columnspan=3, padx=10, pady=10)
        # PIL image to accumulate strokes (black on white)
        self.image = Image.new("L", (CANVAS_SIZE, CANVAS_SIZE), color=255)
        self.draw = ImageDraw.Draw(self.image)
        # Bind drawing
        self.last_x, self.last_y = None, None
        self.canvas.bind("<ButtonPress-1>", self.pen_down)
        self.canvas.bind("<B1-Motion>", self.paint)
        self.canvas.bind("<ButtonRelease-1>", self.pen_up)
        # Buttons
        tk.Button(root, text="Recognize & Solve", command=self.recognize_and_solve).grid(row=1, column=0, pady=6)
        tk.Button(root, text="Clear", command=self.clear_canvas).grid(row=1, column=1, pady=6)
        tk.Button(root, text="Quit", command=root.quit).grid(row=1, column=2, pady=6)
        # Output
        self.expr_var = tk.StringVar(value="Expression: ")
        self.result_var = tk.StringVar(value="Result: ")
        self.step_text = tk.Text(root, width=60, height=10, wrap="word")
        tk.Label(root, textvariable=self.expr_var, anchor="w").grid(row=2, column=0, columnspan=3, sticky="w", padx=10)
        tk.Label(root, textvariable=self.result_var, anchor="w").grid(row=3, column=0, columnspan=3, sticky="w", padx=10)
        tk.Label(root, text="Steps:").grid(row=4, column=0, sticky="w", padx=10)
        self.step_text.grid(row=5, column=0, columnspan=3, padx=10, pady=4)
        # Load model
        try:
            self.model = load_model(MODEL_PATH)
        except Exception as e:
            messagebox.showerror("Model Error",
                                 f"Could not load {MODEL_PATH}.\nTrain it first with train_mnist_cnn.py.\n\n{e}")
            self.model = None
    # ---------- Drawing handlers ----------
    def pen_down(self, event):
        self.last_x, self.last_y = event.x, event.y
    def paint(self, event):
        if self.last_x is not None and self.last_y is not None:
            # Draw on Tk canvas
            self.canvas.create_line(self.last_x, self.last_y, event.x, event.y,
                                    width=DRAW_WIDTH, fill="black", capstyle=tk.ROUND, smooth=True)
            # Draw on PIL image
            self.draw.line([self.last_x, self.last_y, event.x, event.y],
                           fill=0, width=DRAW_WIDTH)
        self.last_x, self.last_y = event.x, event.y
    def pen_up(self, event):
        self.last_x, self.last_y = None, None
    def clear_canvas(self):
        self.canvas.delete("all")
        self.image = Image.new("L", (CANVAS_SIZE, CANVAS_SIZE), color=255)
        self.draw = ImageDraw.Draw(self.image)
        self.expr_var.set("Expression: ")
        self.result_var.set("Result: ")
        self.step_text.delete("1.0", tk.END)
    # ---------- Core pipeline ----------
    def recognize_and_solve(self):
        if self.model is None:
            messagebox.showwarning("Model", "Model not loaded.")
            return
        # Convert PIL to OpenCV
        img = np.array(self.image)
        expr, tokens_dbg = self.image_to_expression(img)
        if not expr:
            messagebox.showwarning("Parse", "Could not parse any symbols. Try writing bigger/cleaner.")
            return
        self.expr_var.set(f"Expression: {expr}")
        try:
            # Use sympy to evaluate
            sym_expr = sympify(expr)
            simplified = simplify(sym_expr)
            self.result_var.set(f"Result: {simplified}")
            # Show steps (simple for now)
            self.step_text.delete("1.0", tk.END)
            self.step_text.insert(tk.END, "Tokens (left→right):\n")
            self.step_text.insert(tk.END, " ".join(tokens_dbg) + "\n\n")
            self.step_text.insert(tk.END, f"SymPy parsed: {sym_expr}\n")
            if str(sym_expr) != str(simplified):
                self.step_text.insert(tk.END, f"Simplified: {simplified}\n")
            else:
                self.step_text.insert(tk.END, "No further simplification needed.\n")
        except Exception as e:
            messagebox.showerror("Evaluation Error", f"Failed to evaluate expression:\n{e}")
    def image_to_expression(self, gray_img: np.ndarray) -> tuple[str, list]:
        """
        Segment symbols, classify digits with CNN, infer + / - with projection heuristics.
        Returns (expression_string, debug_tokens)
        """
        # 1) Binarize & clean
        # Invert: handwriting is black (0), background white (255) => for OpenCV we want white-on-black for morphology ops.
        inv = 255 - gray_img
        # Threshold
        _, th = cv2.threshold(inv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        # Morph open small noise
        kernel = np.ones((3,3), np.uint8)
        th = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel, iterations=1)
        # 2) Find contours (symbols)
        contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        boxes = []
        for cnt in contours:
            x, y, w, h = cv2.boundingRect(cnt)
            area = w * h
            if area < MIN_CONTOUR_AREA:
                continue
            boxes.append((x, y, w, h))
        if not boxes:
            return "", []
        # Sort left-to-right
        boxes.sort(key=lambda b: b[0])
        # Median height (helps operator heuristics)
        med_h = np.median([h for (_, _, _, h) in boxes])
        tokens = []
        debug_tokens = []
        for (x, y, w, h) in boxes:
            crop = th[y:y+h, x:x+w]  # white ink on black background
            # Operator heuristic first (minus / plus)
            op = self.classify_operator(crop, w, h, med_h)
            if op is not None:
                tokens.append(op)
                debug_tokens.append(f"[{op}]")
                continue
            # Otherwise, digit classification
            digit = self.classify_digit(crop)
            if digit is None:
                # If not digit and not recognized operator, skip (or treat as minus attempt)
                # Safer to skip
                continue
            tokens.append(str(digit))
            debug_tokens.append(str(digit))
        # Merge digits & operators into expression string
        expr = self.tokens_to_expression(tokens)
        return expr, debug_tokens
    def classify_digit(self, crop_bin: np.ndarray) -> int | None:
        """
        Prepare glyph for MNIST CNN (28x28, centered), and predict 0-9.
        crop_bin: white ink on black background (binary)
        """
        # Make sure it's binary (0/255)
        crop = (crop_bin > 0).astype(np.uint8) * 255
        # Add padding
        crop = cv2.copyMakeBorder(crop, PADDING, PADDING, PADDING, PADDING, cv2.BORDER_CONSTANT, value=0)
        # Find tight box again after pad
        ys, xs = np.where(crop > 0)
        if len(xs) == 0 or len(ys) == 0:
            return None
        x0, x1 = xs.min(), xs.max()
        y0, y1 = ys.min(), ys.max()
        crop = crop[y0:y1+1, x0:x1+1]
        # Resize to 20x20 then center in 28x28 (like MNIST preprocessing)
        h, w = crop.shape
        if h > w:
            new_h = 20
            new_w = int(w * (20.0 / h))
        else:
            new_w = 20
            new_h = int(h * (20.0 / w))
        if new_h <= 0: new_h = 1
        if new_w <= 0: new_w = 1
        resized = cv2.resize(crop, (new_w, new_h), interpolation=cv2.INTER_AREA)
        canvas = np.zeros((28, 28), dtype=np.uint8)
        y_off = (28 - new_h) // 2
        x_off = (28 - new_w) // 2
        canvas[y_off:y_off+new_h, x_off:x_off+new_w] = resized
        # Normalize for model: MNIST is black background (0) with white strokes (1)
        img = canvas.astype("float32") / 255.0
        img = img[..., None]  # (28,28,1)
        pred = self.model.predict(img[None, ...], verbose=0)[0]
        cls = int(np.argmax(pred))
        conf = float(np.max(pred))
        # Optional confidence filtering
        if conf < 0.40:
            return None
        return cls
    def classify_operator(self, crop_bin: np.ndarray, w: int, h: int, med_h: float) -> str | None:
        """
        Very lightweight heuristics:
        - '-' : wide, short, one thick horizontal stroke (width/height large, height << median digit height)
        - '+' : strong central vertical and horizontal projections (peaks)
        """
        # Work on binary with 1s where stroke is present
        b = (crop_bin > 0).astype(np.uint8)
        # Aspect ratio heuristic for '-'
        if h > 0:
            ar = w / float(h)
        else:
            ar = 0
        # height relative to median digit height
        h_frac = h / float(med_h) if med_h > 0 else 1.0
        # Horizontal projection profile (sum along columns) and vertical profile (sum along rows)
        vproj = b.sum(axis=0)  # per column
        hproj = b.sum(axis=1)  # per row
        v_center_peak = vproj[len(vproj)//2] / (b.shape[0] + 1e-6)
        h_center_peak = hproj[len(hproj)//2] / (b.shape[1] + 1e-6)
        # Minus: flat, wide, short
        if ar >= MINUS_AR_THRESH and h_frac <= MINUS_HEIGHT_FRAC:
            return "-"
        # Plus: vertical & horizontal strong central strokes
        if v_center_peak >= PLUS_PEAK_FRAC and h_center_peak >= PLUS_PEAK_FRAC:
            return "+"
        return None
    def tokens_to_expression(self, tokens: list[str]) -> str:
        """
        Combine tokens into a valid expression.
        - Collapse consecutive digits into multi-digit numbers.
        - Keep '+' and '-' as operators.
        - Remove illegal leading/trailing operators.
        """
        # Collapse digits
        out = []
        num_buf = []
        for t in tokens:
            if t.isdigit():
                num_buf.append(t)
            else:
                # flush number
                if num_buf:
                    out.append("".join(num_buf))
                    num_buf = []
                # operator allowed only if last is number
                if len(out) > 0 and out[-1][-1].isdigit() and t in {"+", "-"}:
                    out.append(t)
        # flush at end
        if num_buf:
            out.append("".join(num_buf))
        # Join safely
        expr = ""
        for item in out:
            if item in {"+", "-"}:
                expr += f" {item} "
            else:
                expr += item
        return expr.strip()
if __name__ == "__main__":
    root = tk.Tk()
    app = MathSolverApp(root)
    root.mainloop()


AI Resume Ranker

import os

import re

import argparse

import glob

from typing import List, Tuple, Dict, Optional


import pandas as pd

import numpy as np


from sklearn.feature_extraction.text import TfidfVectorizer, ENGLISH_STOP_WORDS

from sklearn.metrics.pairwise import cosine_similarity


# Optional parsers

import docx2txt

import PyPDF2


# Optional NLP

try:

    import spacy

    SPACY_OK = True

except Exception:

    SPACY_OK = False



# --------------------------- File Readers ---------------------------


def read_txt(path: str) -> str:

    with open(path, "r", encoding="utf-8", errors="ignore") as f:

        return f.read()


def read_docx(path: str) -> str:

    try:

        return docx2txt.process(path) or ""

    except Exception:

        return ""


def read_pdf(path: str) -> str:

    text = []

    try:

        with open(path, "rb") as f:

            reader = PyPDF2.PdfReader(f)

            for page in reader.pages:

                t = page.extract_text() or ""

                text.append(t)

    except Exception:

        pass

    return "\n".join(text)


def load_text_any(path: str) -> str:

    ext = os.path.splitext(path)[1].lower()

    if ext == ".txt":

        return read_txt(path)

    elif ext == ".docx":

        return read_docx(path)

    elif ext == ".pdf":

        return read_pdf(path)

    else:

        return ""



# --------------------------- Skills ---------------------------


DEFAULT_SKILLS = [

    # Generic

    "python","java","c++","javascript","typescript","sql","nosql","git","docker","kubernetes","linux",

    "aws","azure","gcp","bash","shell","rest","graphql","microservices",

    # Data/AI

    "pandas","numpy","scikit-learn","sklearn","tensorflow","pytorch","keras","nltk","spacy",

    "spark","hadoop","airflow","dbt","powerbi","tableau","matplotlib","seaborn",

    # Web/Backend

    "django","flask","fastapi","spring","node","express","react","angular","vue",

    # DevOps/Cloud

    "terraform","ansible","jenkins","ci/cd","prometheus","grafana","elk","rabbitmq","kafka",

    # Testing

    "pytest","unittest","selenium","cypress",

    # Security & Other

    "oauth","jwt","scrum","agile","jira"

]


def load_skills_file(path: Optional[str]) -> List[str]:

    if not path:

        return DEFAULT_SKILLS

    skills = []

    with open(path, "r", encoding="utf-8", errors="ignore") as f:

        for line in f:

            s = line.strip().lower()

            if s:

                skills.append(s)

    return sorted(set(skills))



# --------------------------- NLP Cleaning ---------------------------


def build_spacy_pipeline(use_spacy: bool):

    if use_spacy and SPACY_OK:

        try:

            nlp = spacy.load("en_core_web_sm", disable=["ner","parser","textcat"])

            return nlp

        except Exception:

            return None

    return None


CLEAN_RE = re.compile(r"[^a-z0-9+#./\- ]+")


def normalize_text(text: str) -> str:

    text = text.lower()

    text = text.replace("\n", " ").replace("\t", " ")

    text = CLEAN_RE.sub(" ", text)

    text = re.sub(r"\s+", " ", text).strip()

    return text


def lemmatize_spacy(nlp, text: str) -> str:

    if not nlp:

        return text

    doc = nlp(text)

    return " ".join(tok.lemma_ for tok in doc if not tok.is_space)



# --------------------------- Feature Engineering ---------------------------


def skill_overlap_score(text: str, jd_skills: List[str]) -> float:

    """

    Compute a skill overlap score (0..1) = Jaccard-like:

    |skills_in_resume ∩ skills_in_jd| / |skills_in_jd|

    """

    text_tokens = set(re.findall(r"[a-z0-9+#.\-]+", text.lower()))

    resume_skills = set()

    for skill in jd_skills:

        tokens = skill.split()

        if len(tokens) == 1:

            if skill in text_tokens:

                resume_skills.add(skill)

        else:

            if skill in text:

                resume_skills.add(skill)

    if not jd_skills:

        return 0.0

    return len(resume_skills) / float(len(set(jd_skills)))



# --------------------------- Ranking ---------------------------


def rank_resumes(

    jd_text: str,

    resume_texts: Dict[str, str],

    use_spacy: bool = False,

    weights: Tuple[float, float] = (0.7, 0.3),

    custom_skills: Optional[List[str]] = None

) -> pd.DataFrame:


    w_sem, w_skill = weights

    assert abs((w_sem + w_skill) - 1.0) < 1e-6, "weights must sum to 1"


    # Prepare spaCy if requested

    nlp = build_spacy_pipeline(use_spacy)


    # Normalize & (optionally) lemmatize

    jd_clean = normalize_text(jd_text)

    if nlp:

        jd_clean = lemmatize_spacy(nlp, jd_clean)


    cleaned = {}

    for fname, txt in resume_texts.items():

        t = normalize_text(txt)

        if nlp:

            t = lemmatize_spacy(nlp, t)

        cleaned[fname] = t


    # TF-IDF across JD + Resumes

    vectorizer = TfidfVectorizer(stop_words="english", max_features=40000, ngram_range=(1,2))

    corpus = [jd_clean] + [cleaned[f] for f in cleaned]

    tfidf = vectorizer.fit_transform(corpus)


    # Cosine similarity of resumes against JD (index 0)

    sims = cosine_similarity(tfidf[0:1], tfidf[1:]).flatten()


    # Skills from JD text (intersect default skills + those present in JD)

    base_skills = custom_skills if custom_skills is not None else DEFAULT_SKILLS

    jd_skill_candidates = [s for s in base_skills if s in jd_clean]

    # Fallback: if no skills found in JD, keep base set (less strict)

    jd_skillset = jd_skill_candidates if jd_skill_candidates else base_skills


    # Skill overlap score per resume

    files = list(cleaned.keys())

    skill_scores = []

    for f in files:

        s = skill_overlap_score(cleaned[f], jd_skillset)

        skill_scores.append(s)


    # Final score

    final = w_sem * sims + w_skill * np.array(skill_scores)


    df = pd.DataFrame({

        "resume_file": files,

        "semantic_similarity": np.round(sims, 4),

        "skill_overlap": np.round(skill_scores, 4),

        "final_score": np.round(final, 4)

    }).sort_values("final_score", ascending=False).reset_index(drop=True)


    return df



# --------------------------- CLI ---------------------------


def main():

    parser = argparse.ArgumentParser(description="AI Resume Ranker — rank resumes against a job description.")

    parser.add_argument("--jd", required=True, help="Job description file (.txt/.pdf/.docx)")

    parser.add_argument("--resumes", required=True, help="Folder containing resumes (.txt/.pdf/.docx)")

    parser.add_argument("--export", default="ranked_resumes.csv", help="Path to export CSV results")

    parser.add_argument("--skills", default=None, help="Optional skills file (one skill per line)")

    parser.add_argument("--use-spacy", action="store_true", help="Enable spaCy lemmatization (install en_core_web_sm)")

    parser.add_argument("--weights", nargs=2, type=float, default=[0.7, 0.3],

                        help="Weights for [semantic_similarity skill_overlap], must sum to 1.0 (default 0.7 0.3)")

    args = parser.parse_args()


    # Load JD

    jd_text = load_text_any(args.jd)

    if not jd_text.strip():

        raise SystemExit(f"Could not read job description: {args.jd}")


    # Load resumes

    patterns = ["*.pdf", "*.docx", "*.txt"]

    files = []

    for p in patterns:

        files.extend(glob.glob(os.path.join(args.resumes, p)))

    if not files:

        raise SystemExit(f"No resumes found in: {args.resumes}")


    resume_texts = {}

    for f in files:

        txt = load_text_any(f)

        if txt.strip():

            resume_texts[os.path.basename(f)] = txt


    # Load skills (optional)

    custom_skills = load_skills_file(args.skills) if args.skills else None


    # Rank

    df = rank_resumes(

        jd_text=jd_text,

        resume_texts=resume_texts,

        use_spacy=args.use_spacy,

        weights=(args.weights[0], args.weights[1]),

        custom_skills=custom_skills

    )


    # Save

    df.to_csv(args.export, index=False)

    print("\nTop matches:")

    print(df.head(10).to_string(index=False))

    print(f"\nSaved results to: {args.export}")



if __name__ == "__main__":

    main()


Interactive Periodic Table

import json

import os

import tkinter as tk

from tkinter import ttk, messagebox

import webbrowser

from functools import partial

import threading


import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import (needed by matplotlib 3D)

import numpy as np


# ----------------- Config -----------------

DATA_FILE = "elements.json"

GRID_COLUMNS = 18

GRID_ROWS = 9  # we'll use rows 1-7 + an extra for Lanthanides/Actinides placeholder


# ----------------- Default JSON Creator -----------------

DEFAULT_ELEMENTS = {

    # symbol: data

    "H": {"name": "Hydrogen", "atomic_number": 1, "atomic_mass": 1.008, "group": 1, "period": 1,

          "category": "Nonmetal", "uses": ["Fuel", "Chemical feedstock"], "electron_configuration": "1s1"},

    "He": {"name": "Helium", "atomic_number": 2, "atomic_mass": 4.0026, "group": 18, "period": 1,

           "category": "Noble Gas", "uses": ["Balloons", "Cryogenics"], "electron_configuration": "1s2"},

    "Li": {"name": "Lithium", "atomic_number": 3, "atomic_mass": 6.94, "group": 1, "period": 2,

           "category": "Alkali Metal", "uses": ["Batteries"], "electron_configuration": "[He] 2s1"},

    "Be": {"name": "Beryllium", "atomic_number": 4, "atomic_mass": 9.0122, "group": 2, "period": 2,

           "category": "Alkaline Earth Metal", "uses": ["Aerospace alloys"], "electron_configuration": "[He] 2s2"},

    "B": {"name": "Boron", "atomic_number": 5, "atomic_mass": 10.81, "group": 13, "period": 2,

          "category": "Metalloid", "uses": ["Glass", "Detergents"], "electron_configuration": "[He] 2s2 2p1"},

    "C": {"name": "Carbon", "atomic_number": 6, "atomic_mass": 12.011, "group": 14, "period": 2,

          "category": "Nonmetal", "uses": ["Organic chemistry", "Materials"], "electron_configuration": "[He] 2s2 2p2"},

    "N": {"name": "Nitrogen", "atomic_number": 7, "atomic_mass": 14.007, "group": 15, "period": 2,

          "category": "Nonmetal", "uses": ["Fertilizers", "Industrial gas"], "electron_configuration": "[He] 2s2 2p3"},

    "O": {"name": "Oxygen", "atomic_number": 8, "atomic_mass": 15.999, "group": 16, "period": 2,

          "category": "Nonmetal", "uses": ["Respiration", "Combustion"], "electron_configuration": "[He] 2s2 2p4"},

    "F": {"name": "Fluorine", "atomic_number": 9, "atomic_mass": 18.998, "group": 17, "period": 2,

          "category": "Halogen", "uses": ["Toothpaste (fluoride)"], "electron_configuration": "[He] 2s2 2p5"},

    "Ne": {"name": "Neon", "atomic_number": 10, "atomic_mass": 20.1797, "group": 18, "period": 2,

           "category": "Noble Gas", "uses": ["Neon signs"], "electron_configuration": "[He] 2s2 2p6"},

    "Na": {"name": "Sodium", "atomic_number": 11, "atomic_mass": 22.989, "group": 1, "period": 3,

           "category": "Alkali Metal", "uses": ["Salt", "Chemicals"], "electron_configuration": "[Ne] 3s1"},

    "Mg": {"name": "Magnesium", "atomic_number": 12, "atomic_mass": 24.305, "group": 2, "period": 3,

           "category": "Alkaline Earth Metal", "uses": ["Alloys", "Electronics"], "electron_configuration": "[Ne] 3s2"},

    "Al": {"name": "Aluminium", "atomic_number": 13, "atomic_mass": 26.9815, "group": 13, "period": 3,

           "category": "Post-transition Metal", "uses": ["Packaging", "Aerospace"], "electron_configuration": "[Ne] 3s2 3p1"},

    "Si": {"name": "Silicon", "atomic_number": 14, "atomic_mass": 28.085, "group": 14, "period": 3,

           "category": "Metalloid", "uses": ["Semiconductors"], "electron_configuration": "[Ne] 3s2 3p2"},

    "P": {"name": "Phosphorus", "atomic_number": 15, "atomic_mass": 30.9738, "group": 15, "period": 3,

          "category": "Nonmetal", "uses": ["Fertilizers"], "electron_configuration": "[Ne] 3s2 3p3"},

    "S": {"name": "Sulfur", "atomic_number": 16, "atomic_mass": 32.06, "group": 16, "period": 3,

          "category": "Nonmetal", "uses": ["Sulfuric acid", "Fertilizers"], "electron_configuration": "[Ne] 3s2 3p4"},

    "Cl": {"name": "Chlorine", "atomic_number": 17, "atomic_mass": 35.45, "group": 17, "period": 3,

           "category": "Halogen", "uses": ["Water purification"], "electron_configuration": "[Ne] 3s2 3p5"},

    "Ar": {"name": "Argon", "atomic_number": 18, "atomic_mass": 39.948, "group": 18, "period": 3,

           "category": "Noble Gas", "uses": ["Lighting", "Welding"], "electron_configuration": "[Ne] 3s2 3p6"},

    "K": {"name": "Potassium", "atomic_number": 19, "atomic_mass": 39.0983, "group": 1, "period": 4,

          "category": "Alkali Metal", "uses": ["Fertilizers"], "electron_configuration": "[Ar] 4s1"},

    "Ca": {"name": "Calcium", "atomic_number": 20, "atomic_mass": 40.078, "group": 2, "period": 4,

           "category": "Alkaline Earth Metal", "uses": ["Bones", "Construction"], "electron_configuration": "[Ar] 4s2"}

}


def ensure_data_file():

    if not os.path.exists(DATA_FILE):

        with open(DATA_FILE, "w", encoding="utf-8") as f:

            json.dump(DEFAULT_ELEMENTS, f, indent=2)

        print(f"Created default {DATA_FILE} with first 20 elements. You can edit/expand it.")


# ----------------- Utility: Load Data -----------------

def load_elements():

    ensure_data_file()

    with open(DATA_FILE, "r", encoding="utf-8") as f:

        data = json.load(f)

    # Normalize: attach group/period if missing

    elements = {}

    for sym, info in data.items():

        elements[sym] = info

    return elements


# ----------------- Orbital Visualizer -----------------

def show_orbital_visual(element_data):

    """

    Very simple 3D shells visualization.

    Each shell (n) is drawn as a translucent sphere surface of radius proportional to n.

    Electron counts per shell approximated from electron_configuration (simple parse).

    """

    # parse approximate shells from electron_configuration string by counting numbers in brackets or 's','p','d'

    cfg = element_data.get("electron_configuration", "")

    # crude parsing to get principal quantum numbers occurrences like 1s2 2s2 2p6 etc.

    shells = []

    import re

    matches = re.findall(r'(\d)(?:[spdf])\d*', cfg)

    if matches:

        # get unique shell numbers and sort

        shells = sorted(set(int(m) for m in matches))

    else:

        # fallback: estimate shells from atomic number

        an = element_data.get("atomic_number", 1)

        # very rough heuristic: shell count = ceil(sqrt(Z)/2) — just for visualization

        shells = list(range(1, int(max(1, round((an**0.5)/2))) + 2))


    fig = plt.figure(figsize=(6,6))

    ax = fig.add_subplot(111, projection='3d')

    ax.set_facecolor("white")

    ax._axis3don = False


    # Draw concentric sphere surfaces for shells

    for n in shells:

        # create sphere

        u = np.linspace(0, 2 * np.pi, 60)

        v = np.linspace(0, np.pi, 30)

        r = 0.6 * n  # radius scale

        x = r * np.outer(np.cos(u), np.sin(v))

        y = r * np.outer(np.sin(u), np.sin(v))

        z = r * np.outer(np.ones_like(u), np.cos(v))

        ax.plot_surface(x, y, z, color=plt.cm.viridis(n/ max(1, len(shells))), alpha=0.15, linewidth=0)

        # put some electron points randomly distributed on shell

        np.random.seed(n*10)

        m = min(12, 4 + n*4)

        theta = np.random.uniform(0, 2*np.pi, m)

        phi = np.random.uniform(0, np.pi, m)

        ex = r * np.sin(phi) * np.cos(theta)

        ey = r * np.sin(phi) * np.sin(theta)

        ez = r * np.cos(phi)

        ax.scatter(ex, ey, ez, s=30, color=plt.cm.viridis(n / max(1, len(shells))))


    ax.set_title(f"Shells for {element_data.get('name', '')} ({element_data.get('symbol','')})", pad=20)

    max_r = 0.6 * (max(shells) + 1)

    ax.set_xlim(-max_r, max_r)

    ax.set_ylim(-max_r, max_r)

    ax.set_zlim(-max_r, max_r)

    plt.show()


# ----------------- GUI -----------------

class PeriodicTableApp:

    def __init__(self, root):

        self.root = root

        self.root.title("Interactive Periodic Table")

        self.elements = load_elements()  # key: symbol

        # create a mapping by atomic_number for quick lookup

        self.by_atomic = {info["atomic_number"]: sym for sym, info in self.elements.items() if "atomic_number" in info}

        self.buttons = {}  # symbol -> button

        self.build_ui()


    def build_ui(self):

        top = tk.Frame(self.root)

        top.pack(padx=8, pady=8, anchor="w")


        tk.Label(top, text="Search (symbol / name / atomic #):").pack(side="left")

        self.search_var = tk.StringVar()

        search_entry = tk.Entry(top, textvariable=self.search_var)

        search_entry.pack(side="left", padx=4)

        search_entry.bind("<Return>", lambda e: self.do_search())

        tk.Button(top, text="Search", command=self.do_search).pack(side="left", padx=4)

        tk.Button(top, text="Open elements.json", command=self.open_data_file).pack(side="left", padx=6)


        # Table frame

        table_frame = tk.Frame(self.root)

        table_frame.pack(padx=8, pady=8)


        # create empty grid

        for r in range(1, 8):  # periods 1..7

            for c in range(1, GRID_COLUMNS + 1):

                placeholder = tk.Frame(table_frame, width=46, height=36, bd=0)

                placeholder.grid(row=r, column=c, padx=1, pady=1)


        # Place element buttons based on group/period columns

        for sym, info in self.elements.items():

            group = info.get("group")

            period = info.get("period")

            if group is None or period is None:

                # skip elements without position (lanthanides/actinides not positioned)

                continue

            r = period

            c = group

            btn = tk.Button(table_frame, text=f"{sym}\n{info.get('atomic_number','')}",

                            width=6, height=3, command=partial(self.show_element_popup, sym))

            btn.grid(row=r, column=c, padx=1, pady=1)

            self.buttons[sym] = btn


        # Add legend / info area

        info_frame = tk.Frame(self.root)

        info_frame.pack(fill="x", padx=8, pady=6)

        tk.Label(info_frame, text="Click an element to view details and 3D shell visualization.", fg="gray").pack(side="left")


    def open_data_file(self):

        path = os.path.abspath(DATA_FILE)

        if os.path.exists(path):

            webbrowser.open(f"file://{path}")

        else:

            messagebox.showinfo("Info", f"{DATA_FILE} not found.")


    def do_search(self):

        q = self.search_var.get().strip().lower()

        if not q:

            return

        # search by symbol

        sym_match = None

        for sym, info in self.elements.items():

            if sym.lower() == q:

                sym_match = sym

                break

        if sym_match:

            self.flash_button(sym_match)

            self.show_element_popup(sym_match)

            return

        # search by name

        for sym, info in self.elements.items():

            if info.get("name", "").lower() == q:

                self.flash_button(sym)

                self.show_element_popup(sym)

                return

        # search by atomic number

        if q.isdigit():

            an = int(q)

            sym = self.by_atomic.get(an)

            if sym:

                self.flash_button(sym)

                self.show_element_popup(sym)

                return

        # partial name match

        for sym, info in self.elements.items():

            if q in info.get("name", "").lower():

                self.flash_button(sym)

                self.show_element_popup(sym)

                return

        messagebox.showinfo("Search", "No matching element found.")


    def flash_button(self, sym):

        btn = self.buttons.get(sym)

        if not btn:

            return

        orig = btn.cget("bg")

        def _flash():

            for _ in range(4):

                btn.config(bg="yellow")

                btn.update()

                self.root.after(200)

                btn.config(bg=orig)

                btn.update()

                self.root.after(200)

        threading.Thread(target=_flash, daemon=True).start()


    def show_element_popup(self, sym):

        info = self.elements.get(sym)

        if not info:

            messagebox.showerror("Error", "Element data not found.")

            return

        popup = tk.Toplevel(self.root)

        popup.title(f"{sym} - {info.get('name','')}")

        popup.geometry("420x320")


        left = tk.Frame(popup)

        left.pack(side="left", fill="both", expand=True, padx=8, pady=8)


        tk.Label(left, text=f"{info.get('name','')} ({sym})", font=("Arial", 16, "bold")).pack(anchor="w")

        tk.Label(left, text=f"Atomic Number: {info.get('atomic_number','')}", font=("Arial", 12)).pack(anchor="w")

        tk.Label(left, text=f"Atomic Mass: {info.get('atomic_mass','')}", font=("Arial", 12)).pack(anchor="w")

        tk.Label(left, text=f"Category: {info.get('category','')}", font=("Arial", 12)).pack(anchor="w")

        tk.Label(left, text=f"Electron Configuration: {info.get('electron_configuration','')}", font=("Arial", 11)).pack(anchor="w", pady=6)

        uses = info.get("uses", [])

        tk.Label(left, text="Uses / Applications:", font=("Arial", 12, "underline")).pack(anchor="w", pady=(6,0))

        uses_text = tk.Text(left, height=6, wrap="word")

        uses_text.pack(fill="both", expand=True)

        uses_text.insert("1.0", "\n".join(uses) if isinstance(uses, list) else str(uses))

        uses_text.config(state="disabled")


        # right side controls

        right = tk.Frame(popup)

        right.pack(side="right", fill="y", padx=6, pady=6)

        tk.Button(right, text="Visualize Shells (3D)", command=lambda: threading.Thread(target=show_orbital_visual, args=(info,), daemon=True).start()).pack(pady=6)

        tk.Button(right, text="Close", command=popup.destroy).pack(pady=6)


# ----------------- Run App -----------------

def main():

    root = tk.Tk()

    app = PeriodicTableApp(root)

    root.mainloop()


if __name__ == "__main__":

    main()


Smart Receipt Scanner & Expense Categorizer

import cv2

import pytesseract

import pandas as pd

import matplotlib.pyplot as plt

import re

import os


# Set tesseract path if needed (Windows users)

# pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"


# Predefined keywords for categorization

CATEGORY_KEYWORDS = {

    "Food": ["restaurant", "cafe", "pizza", "burger", "coffee", "food", "dine"],

    "Travel": ["uber", "ola", "taxi", "flight", "airlines", "train", "bus", "travel"],

    "Utilities": ["electricity", "water", "gas", "internet", "wifi", "bill", "utility"],

    "Shopping": ["mall", "store", "supermarket", "shopping", "market", "groceries"],

    "Other": []

}


def preprocess_image(image_path):

    """Convert image to grayscale and apply threshold for better OCR results."""

    img = cv2.imread(image_path)

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    gray = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

    return gray


def extract_text(image):

    """Extract text from the image using pytesseract."""

    return pytesseract.image_to_string(image)


def categorize_expense(text):

    """Categorize based on keywords found in the text."""

    text_lower = text.lower()

    for category, keywords in CATEGORY_KEYWORDS.items():

        for word in keywords:

            if word in text_lower:

                return category

    return "Other"


def extract_amount(text):

    """Find the largest number in text assuming it's the total amount."""

    amounts = re.findall(r"\d+\.\d{2}", text)

    if amounts:

        return max(map(float, amounts))

    return 0.0


def process_receipts(folder_path):

    """Process all receipt images in a folder."""

    records = []

    for file in os.listdir(folder_path):

        if file.lower().endswith((".png", ".jpg", ".jpeg")):

            img_path = os.path.join(folder_path, file)

            pre_img = preprocess_image(img_path)

            text = extract_text(pre_img)

            category = categorize_expense(text)

            amount = extract_amount(text)

            records.append({"File": file, "Category": category, "Amount": amount, "Text": text})

    return pd.DataFrame(records)


def plot_expenses(df):

    """Plot expenses by category."""

    category_totals = df.groupby("Category")["Amount"].sum()

    category_totals.plot(kind="bar", color="skyblue")

    plt.title("Expenses by Category")

    plt.xlabel("Category")

    plt.ylabel("Total Amount")

    plt.xticks(rotation=45)

    plt.tight_layout()

    plt.show()


if __name__ == "__main__":

    folder = "receipts"  # Folder containing receipt images

    df = process_receipts(folder)

    print(df)

    plot_expenses(df)


Voice-Controlled Desktop Assistant

import speech_recognition as sr

import pyttsx3

import subprocess

import os

import webbrowser

import time

from datetime import datetime

import threading


# -------------------- Configuration --------------------

# Change these to match your environment

MUSIC_FOLDER = os.path.expanduser("~/Music")  # folder to play music from

SEARCH_ROOT = os.path.expanduser("~")         # root folder for file searches

DEFAULT_BROWSER = None                        # None will use webbrowser.open

# Map simple app names to commands (Windows, macOS, Linux)

COMMANDS = {

    "notepad": {"win": "notepad"},

    "calculator": {"win": "calc", "mac": "open -a Calculator", "linux": "gnome-calculator"},

    "vscode": {"win": r"C:\Users\%USERNAME%\AppData\Local\Programs\Microsoft VS Code\Code.exe",

               "mac": "open -a Visual\\ Studio\\ Code", "linux": "code"},

    "explorer": {"win": "explorer", "mac": "open", "linux": "xdg-open"},

}


# -------------------- Helpers --------------------

def speak(text, engine):

    """Speak text asynchronously so we don't block the main loop."""

    def _s():

        engine.say(text)

        engine.runAndWait()

    t = threading.Thread(target=_s, daemon=True)

    t.start()


def recognize_speech_from_mic(recognizer, microphone, timeout=5, phrase_time_limit=6):

    """Capture audio and return recognized text (or None)."""

    with microphone as source:

        recognizer.adjust_for_ambient_noise(source, duration=0.6)

        try:

            audio = recognizer.listen(source, timeout=timeout, phrase_time_limit=phrase_time_limit)

        except sr.WaitTimeoutError:

            return None

    try:

        # Using Google Web Speech API (requires internet) — good accuracy

        return recognizer.recognize_google(audio)

    except sr.RequestError:

        # API unreachable

        return "[error_api]"

    except sr.UnknownValueError:

        return None


def open_app(app_key):

    """Open an application based on COMMANDS map."""

    platform = os.name  # 'nt' on Windows, 'posix' on macOS/Linux

    is_windows = platform == "nt"

    cmd_map = COMMANDS.get(app_key.lower())

    if not cmd_map:

        return False, f"No mapping for app '{app_key}'."


    try:

        if is_windows:

            cmd = cmd_map.get("win")

            if not cmd:

                return False, "No Windows command available for this app."

            # Support environment vars in path

            cmd = os.path.expandvars(cmd)

            subprocess.Popen(cmd, shell=True)

        else:

            # macOS or Linux (posix)

            cmd = cmd_map.get("mac") or cmd_map.get("linux")

            if not cmd:

                return False, "No command available for this platform."

            subprocess.Popen(cmd, shell=True)

        return True, f"Opened {app_key}."

    except Exception as e:

        return False, str(e)


def open_folder(folder_name):

    """Open a common folder name like downloads, documents, desktop"""

    name = folder_name.strip().lower()

    user = os.path.expanduser("~")

    mapping = {

        "downloads": os.path.join(user, "Downloads"),

        "documents": os.path.join(user, "Documents"),

        "desktop": os.path.join(user, "Desktop"),

        "pictures": os.path.join(user, "Pictures"),

        "music": os.path.join(user, "Music"),

    }

    path = mapping.get(name) or os.path.join(user, name)

    if os.path.exists(path):

        if os.name == "nt":

            os.startfile(path)

        else:

            subprocess.Popen(f'xdg-open "{path}"', shell=True)

        return True, f"Opened folder {path}"

    return False, f"Folder {path} does not exist."


def search_files(query, root=SEARCH_ROOT, limit=10):

    """Simple filename search returning up to `limit` results."""

    results = []

    q = query.lower()

    for dirpath, dirs, files in os.walk(root):

        for f in files:

            if q in f.lower():

                results.append(os.path.join(dirpath, f))

                if len(results) >= limit:

                    return results

    return results


def play_random_music():

    """Play a random music file from MUSIC_FOLDER (if available)."""

    if not os.path.exists(MUSIC_FOLDER):

        return False, f"Music folder {MUSIC_FOLDER} not found."

    exts = (".mp3", ".wav", ".ogg", ".flac")

    files = [f for f in os.listdir(MUSIC_FOLDER) if f.lower().endswith(exts)]

    if not files:

        return False, "No music files found in your music folder."

    choice = os.path.join(MUSIC_FOLDER, files[0])  # pick the first for now

    try:

        if os.name == "nt":

            os.startfile(choice)

        else:

            subprocess.Popen(f'xdg-open "{choice}"', shell=True)

        return True, f"Playing {os.path.basename(choice)}"

    except Exception as e:

        return False, str(e)


def confirm_action(recognizer, microphone, engine, prompt="Are you sure? Say 'yes' to confirm"):

    speak(prompt, engine)

    text = recognize_speech_from_mic(recognizer, microphone, timeout=5, phrase_time_limit=4)

    if text:

        txt = text.lower()

        return "yes" in txt or "yeah" in txt or "yup" in txt

    return False


# -------------------- Main Assistant --------------------

def main():

    # Initialize recognizer and TTS

    recognizer = sr.Recognizer()

    microphone = sr.Microphone()

    engine = pyttsx3.init()

    engine.setProperty('rate', 160)


    speak("Hello — voice assistant activated. Say a command.", engine)

    print("Assistant is listening... (say 'help' for suggestions)")


    while True:

        print("\nListening...")

        text = recognize_speech_from_mic(recognizer, microphone)

        if text is None:

            print("No speech detected.")

            continue

        if text == "[error_api]":

            print("Speech API not reachable. Check internet or use offline recognizer.")

            speak("Sorry, speech service is unreachable.", engine)

            continue


        command = text.lower().strip()

        print("You said:", command)


        # Exit

        if any(w in command for w in ("exit", "quit", "stop", "shutdown assistant", "bye")):

            speak("Goodbye!", engine)

            print("Exiting assistant.")

            break


        # Help

        if "help" in command:

            help_text = ("You can say: open notepad, open calculator, open downloads, search file invoice, "

                         "play music, open website youtube, what's the time, shutdown system (requires confirmation).")

            speak(help_text, engine)

            print(help_text)

            continue


        # Time

        if "time" in command:

            now = datetime.now().strftime("%I:%M %p")

            speak(f"The time is {now}", engine)

            print("Time:", now)

            continue


        # Open website

        if command.startswith("open website") or command.startswith("open url") or command.startswith("open "):

            # e.g. "open website youtube" or "open youtube"

            parts = command.replace("open website", "").replace("open url", "").replace("open ", "").strip()

            site = parts.split()[0] if parts else None

            if site:

                url = site if site.startswith("http") else f"https://{site}.com"

                webbrowser.open(url)

                speak(f"Opening {site}", engine)

                print("Open URL:", url)

                continue


        # Open app mapping

        if command.startswith("open ") and not command.startswith("open website"):

            # e.g. "open notepad" or "open vscode"

            app = command.replace("open ", "").strip()

            success, msg = open_app(app)

            speak(msg if msg else ("Opened " + app), engine)

            print(msg)

            continue


        # Open folder

        if command.startswith("open folder") or command.startswith("open downloads") or command.startswith("open desktop"):

            folder_name = command.replace("open folder", "").replace("open ", "").strip()

            success, msg = open_folder(folder_name or "downloads")

            speak(msg, engine)

            print(msg)

            continue


        # Search files

        if command.startswith("search file") or command.startswith("find file") or command.startswith("search for file"):

            # e.g. "search file invoice 2024"

            query = command.split("file", 1)[-1].strip()

            if not query:

                speak("Please say the file name to search for.", engine)

                continue

            speak(f"Searching for files that match {query}", engine)

            print("Searching for:", query)

            results = search_files(query)

            if not results:

                speak("No files found.", engine)

                print("No results.")

            else:

                speak(f"I found {len(results)} files. First one is {os.path.basename(results[0])}", engine)

                print("\n".join(results[:10]))

            continue


        # Play music

        if "music" in command or command.startswith("play music"):

            ok, msg = play_random_music()

            speak(msg, engine) if ok else speak("Could not play music.", engine)

            print(msg)

            continue


        # Shutdown system (dangerous) — confirm first

        if "shutdown" in command or "restart system" in command:

            speak("You asked to perform a system operation. This requires confirmation.", engine)

            confirmed = confirm_action(recognizer, microphone, engine,

                                       prompt="Please say yes to confirm shutting down the system.")

            if confirmed:

                speak("Performing the operation now.", engine)

                print("Confirmed. Executing shutdown/restart.")

                if "shutdown" in command:

                    if os.name == "nt":

                        subprocess.Popen("shutdown /s /t 5", shell=True)

                    else:

                        subprocess.Popen("sudo shutdown -h now", shell=True)

                else:

                    # restart

                    if os.name == "nt":

                        subprocess.Popen("shutdown /r /t 5", shell=True)

                    else:

                        subprocess.Popen("sudo reboot", shell=True)

            else:

                speak("Operation cancelled.", engine)

            continue


        # Search web (fallback)

        if command.startswith("search ") or command.startswith("google "):

            query = command.replace("search", "").replace("google", "").strip()

            url = f"https://www.google.com/search?q={query.replace(' ', '+')}"

            webbrowser.open(url)

            speak(f"Searching the web for {query}", engine)

            print("Web search:", query)

            continue


        # Unknown command fallback

        speak("I did not understand that. Say help for suggestions.", engine)

        print("Unhandled command.")


if __name__ == "__main__":

    try:

        main()

    except KeyboardInterrupt:

        print("\nAssistant stopped by user.")

Virtual Phone Dialer

import tkinter as tk

from tkinter import messagebox

import sqlite3

from datetime import datetime


# ------------------ DATABASE ------------------ #


conn = sqlite3.connect('call_history.db')

cursor = conn.cursor()

cursor.execute('''

    CREATE TABLE IF NOT EXISTS calls (

        id INTEGER PRIMARY KEY AUTOINCREMENT,

        number TEXT NOT NULL,

        timestamp TEXT NOT NULL

    )

''')

conn.commit()



# ------------------ MAIN DIALER APP ------------------ #


class PhoneDialer:

    def __init__(self, root):

        self.root = root

        self.root.title("📱 Virtual Phone Dialer")

        self.number_var = tk.StringVar()


        self.create_widgets()


    def create_widgets(self):

        # Entry to show the number

        self.display = tk.Entry(self.root, textvariable=self.number_var, font=('Helvetica', 20), bd=10, justify='right')

        self.display.grid(row=0, column=0, columnspan=3, pady=10)


        # Keypad buttons

        btn_texts = [

            '1', '2', '3',

            '4', '5', '6',

            '7', '8', '9',

            '*', '0', '#'

        ]


        r = 1

        c = 0

        for text in btn_texts:

            tk.Button(self.root, text=text, font=('Helvetica', 18), width=5, height=2,

                      command=lambda t=text: self.append_number(t)).grid(row=r, column=c, padx=5, pady=5)

            c += 1

            if c > 2:

                c = 0

                r += 1


        # Call button

        tk.Button(self.root, text="📞 Call", bg="green", fg="white", font=('Helvetica', 18),

                  command=self.make_call).grid(row=5, column=0, pady=10, padx=5, sticky='we')


        # Clear button

        tk.Button(self.root, text="❌ Clear", bg="orange", fg="black", font=('Helvetica', 18),

                  command=self.clear).grid(row=5, column=1, pady=10, padx=5, sticky='we')


        # History button

        tk.Button(self.root, text="🕒 History", bg="blue", fg="white", font=('Helvetica', 18),

                  command=self.show_history).grid(row=5, column=2, pady=10, padx=5, sticky='we')


    def append_number(self, val):

        current = self.number_var.get()

        self.number_var.set(current + val)


    def clear(self):

        self.number_var.set("")


    def make_call(self):

        number = self.number_var.get()

        if number:

            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

            cursor.execute("INSERT INTO calls (number, timestamp) VALUES (?, ?)", (number, timestamp))

            conn.commit()

            messagebox.showinfo("Calling", f"Calling {number}...\n(Call logged)")

            self.clear()

        else:

            messagebox.showwarning("Empty", "Please enter a number")


    def show_history(self):

        history_win = tk.Toplevel(self.root)

        history_win.title("📜 Call History")


        tk.Label(history_win, text="Number", font=('Helvetica', 14, 'bold')).grid(row=0, column=0, padx=10, pady=5)

        tk.Label(history_win, text="Timestamp", font=('Helvetica', 14, 'bold')).grid(row=0, column=1, padx=10, pady=5)


        cursor.execute("SELECT number, timestamp FROM calls ORDER BY id DESC")

        rows = cursor.fetchall()


        for idx, (number, timestamp) in enumerate(rows):

            tk.Label(history_win, text=number, font=('Helvetica', 12)).grid(row=idx+1, column=0, padx=10, sticky='w')

            tk.Label(history_win, text=timestamp, font=('Helvetica', 12)).grid(row=idx+1, column=1, padx=10, sticky='w')



# ------------------ RUN APP ------------------ #


if __name__ == "__main__":

    root = tk.Tk()

    app = PhoneDialer(root)

    root.mainloop()


Text-to-World Map

pip install geopy folium nltk

import folium
from geopy.geocoders import Nominatim
import nltk
import re

from nltk import word_tokenize, pos_tag, ne_chunk
from nltk.tree import Tree

nltk.download("punkt")
nltk.download("averaged_perceptron_tagger")
nltk.download("maxent_ne_chunker")
nltk.download("words")


def extract_locations(text):
    """
    Extracts named entities (like countries/cities) using NLTK.
    """
    locations = set()
    tokens = word_tokenize(text)
    pos_tags = pos_tag(tokens)
    chunks = ne_chunk(pos_tags)

    for chunk in chunks:
        if isinstance(chunk, Tree) and chunk.label() == "GPE":
            name = " ".join(c[0] for c in chunk.leaves())
            locations.add(name)
    return list(locations)


def geocode_locations(location_names):
    """
    Uses geopy to convert place names into latitude and longitude.
    """
    geolocator = Nominatim(user_agent="world_map_app")
    geo_data = []

    for name in location_names:
        try:
            location = geolocator.geocode(name)
            if location:
                geo_data.append({
                    "name": name,
                    "lat": location.latitude,
                    "lon": location.longitude
                })
        except Exception as e:
            print(f"Geocoding error for {name}: {e}")

    return geo_data


def create_world_map(locations):
    """
    Create a world map using folium with markers on identified locations.
    """
    m = folium.Map(location=[20, 0], zoom_start=2)

    for loc in locations:
        folium.Marker(
            location=[loc["lat"], loc["lon"]],
            popup=loc["name"],
            icon=folium.Icon(color="blue", icon="info-sign")
        ).add_to(m)

    m.save("world_map.html")
    print("✅ Map saved to world_map.html")


# --------- MAIN FUNCTIONALITY ---------

if __name__ == "__main__":
    print("Paste your text below (press Enter twice to end input):")
    lines = []
    while True:
        line = input()
        if line.strip() == "":
            break
        lines.append(line)

    input_text = "\n".join(lines)

    places = extract_locations(input_text)
    print(f"📍 Places Found: {places}")

    coords = geocode_locations(places)
    create_world_map(coords)

Memory Trainer App

pip install playsound


import tkinter as tk
import random
import time
from threading import Thread
from playsound import playsound

# Constants
PATTERN_LENGTH = 5
DELAY_SECONDS = 3

class MemoryTrainer:
    def __init__(self, root):
        self.root = root
        self.root.title("🧠 Memory Trainer")
        self.root.geometry("400x300")
        self.pattern = []

        self.label = tk.Label(root, text="Click Start to Begin", font=("Arial", 16))
        self.label.pack(pady=30)

        self.start_button = tk.Button(root, text="Start", command=self.start_game, font=("Arial", 14))
        self.start_button.pack()

        self.input_entry = tk.Entry(root, font=("Arial", 14))
        self.submit_button = tk.Button(root, text="Submit", command=self.check_answer, font=("Arial", 12))

        self.feedback = tk.Label(root, text="", font=("Arial", 14), fg="blue")
        self.feedback.pack(pady=10)

    def start_game(self):
        self.pattern = [random.randint(0, 9) for _ in range(PATTERN_LENGTH)]
        self.label.config(text=f"Memorize: {' '.join(map(str, self.pattern))}")
        self.feedback.config(text="")
        self.start_button.config(state=tk.DISABLED)
        self.root.after(DELAY_SECONDS * 1000, self.show_input)

        # Optional sound cue
        Thread(target=lambda: playsound("ding.mp3")).start()  # Replace with a valid sound path

    def show_input(self):
        self.label.config(text="Now enter the pattern:")
        self.input_entry.pack()
        self.submit_button.pack()

    def check_answer(self):
        user_input = self.input_entry.get().strip().split()
        try:
            user_pattern = list(map(int, user_input))
            if user_pattern == self.pattern:
                self.feedback.config(text="✅ Correct!", fg="green")
            else:
                self.feedback.config(text=f"❌ Wrong! Pattern was: {' '.join(map(str, self.pattern))}", fg="red")
        except ValueError:
            self.feedback.config(text="⚠️ Please enter valid numbers.", fg="orange")

        self.input_entry.delete(0, tk.END)
        self.start_button.config(state=tk.NORMAL)
        self.input_entry.pack_forget()
        self.submit_button.pack_forget()

if __name__ == "__main__":
    root = tk.Tk()
    app = MemoryTrainer(root)
    root.mainloop()

Job Description Keyword Extractor

pip install pymupdf nltk wordcloud

import nltk
nltk.download('punkt')
nltk.download('stopwords')


import fitz  # PyMuPDF
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from wordcloud import WordCloud
import tkinter as tk
from tkinter import filedialog
import matplotlib.pyplot as plt
import string

def extract_text_from_pdf(pdf_path):
    doc = fitz.open(pdf_path)
    text = ""
    for page in doc:
        text += page.get_text()
    return text

def clean_and_tokenize(text):
    # Lowercase, remove punctuation
    text = text.lower().translate(str.maketrans("", "", string.punctuation))
    tokens = word_tokenize(text)

    # Remove stopwords & short words
    stop_words = set(stopwords.words("english"))
    keywords = [word for word in tokens if word not in stop_words and len(word) > 2]
    return keywords

def generate_wordcloud(keywords):
    word_freq = nltk.FreqDist(keywords)
    wordcloud = WordCloud(width=800, height=400, background_color='white').generate_from_frequencies(word_freq)

    plt.figure(figsize=(10, 5))
    plt.imshow(wordcloud, interpolation="bilinear")
    plt.axis("off")
    plt.title("Top Keywords in Job Description", fontsize=16)
    plt.show()

def main():
    # Open file dialog
    root = tk.Tk()
    root.withdraw()
    file_path = filedialog.askopenfilename(title="Select Job Description PDF", filetypes=[("PDF Files", "*.pdf")])

    if not file_path:
        print("No file selected.")
        return

    print("Processing...")
    text = extract_text_from_pdf(file_path)
    keywords = clean_and_tokenize(text)
    generate_wordcloud(keywords)

    print("\nTop 20 Keywords:")
    for word, freq in nltk.FreqDist(keywords).most_common(20):
        print(f"{word} - {freq}")

if __name__ == "__main__":
    main()

Custom Retro Snake Game with Skins

import pygame

import tkinter as tk

from tkinter import filedialog, simpledialog

from PIL import Image

import os

import random

import sys


# === Constants ===

TILE_SIZE = 20

DEFAULT_GRID = 20

FPS = 10


# === Globals (updated via GUI) ===

snake_head_img_path = "assets/default_head.png"

snake_body_img_path = "assets/default_body.png"

GRID_SIZE = DEFAULT_GRID



def load_skin(path, size):

    img = Image.open(path).resize((size, size)).convert("RGBA")

    return pygame.image.fromstring(img.tobytes(), img.size, img.mode)



def ask_user_inputs():

    global snake_head_img_path, snake_body_img_path, GRID_SIZE


    root = tk.Tk()

    root.withdraw()


    if filedialog.askyesno("Snake Skin", "Do you want to upload custom snake head image?"):

        snake_head_img_path = filedialog.askopenfilename(title="Select Head Image")


    if filedialog.askyesno("Snake Skin", "Do you want to upload custom snake body image?"):

        snake_body_img_path = filedialog.askopenfilename(title="Select Body Image")


    try:

        GRID_SIZE = int(simpledialog.askstring("Grid Size", "Enter grid size (e.g., 20 for 20x20):") or DEFAULT_GRID)

    except:

        GRID_SIZE = DEFAULT_GRID



def draw_grid(screen, color=(40, 40, 40)):

    for x in range(0, GRID_SIZE * TILE_SIZE, TILE_SIZE):

        for y in range(0, GRID_SIZE * TILE_SIZE, TILE_SIZE):

            rect = pygame.Rect(x, y, TILE_SIZE, TILE_SIZE)

            pygame.draw.rect(screen, color, rect, 1)



class Snake:

    def __init__(self):

        self.body = [(5, 5), (4, 5), (3, 5)]

        self.direction = (1, 0)

        self.grow = False


    def move(self):

        head = self.body[0]

        new_head = (head[0] + self.direction[0], head[1] + self.direction[1])

        self.body.insert(0, new_head)

        if not self.grow:

            self.body.pop()

        else:

            self.grow = False


    def change_direction(self, new_dir):

        # Prevent reversing

        if (new_dir[0] * -1, new_dir[1] * -1) != self.direction:

            self.direction = new_dir


    def draw(self, screen, head_img, body_img):

        for i, segment in enumerate(self.body):

            x, y = segment[0] * TILE_SIZE, segment[1] * TILE_SIZE

            if i == 0:

                screen.blit(head_img, (x, y))

            else:

                screen.blit(body_img, (x, y))


    def check_collision(self):

        head = self.body[0]

        return (

            head in self.body[1:] or

            head[0] < 0 or head[1] < 0 or

            head[0] >= GRID_SIZE or head[1] >= GRID_SIZE

        )



def generate_food(snake):

    while True:

        pos = (random.randint(0, GRID_SIZE - 1), random.randint(0, GRID_SIZE - 1))

        if pos not in snake.body:

            return pos



def main():

    ask_user_inputs()


    pygame.init()

    screen = pygame.display.set_mode((GRID_SIZE * TILE_SIZE, GRID_SIZE * TILE_SIZE))

    pygame.display.set_caption("🐍 Custom Snake Game")

    clock = pygame.time.Clock()


    head_img = load_skin(snake_head_img_path, TILE_SIZE)

    body_img = load_skin(snake_body_img_path, TILE_SIZE)


    snake = Snake()

    food = generate_food(snake)


    running = True

    while running:

        clock.tick(FPS)

        screen.fill((0, 0, 0))

        draw_grid(screen)


        for event in pygame.event.get():

            if event.type == pygame.QUIT:

                running = False

            elif event.type == pygame.KEYDOWN:

                if event.key == pygame.K_UP:

                    snake.change_direction((0, -1))

                elif event.key == pygame.K_DOWN:

                    snake.change_direction((0, 1))

                elif event.key == pygame.K_LEFT:

                    snake.change_direction((-1, 0))

                elif event.key == pygame.K_RIGHT:

                    snake.change_direction((1, 0))


        snake.move()


        if snake.body[0] == food:

            snake.grow = True

            food = generate_food(snake)


        if snake.check_collision():

            print("Game Over!")

            pygame.quit()

            sys.exit()


        snake.draw(screen, head_img, body_img)

        fx, fy = food[0] * TILE_SIZE, food[1] * TILE_SIZE

        pygame.draw.rect(screen, (255, 0, 0), (fx, fy, TILE_SIZE, TILE_SIZE))


        pygame.display.flip()



if __name__ == "__main__":

    main()