If you have ever said "I can't use AI, there is no internet in the field." or "I have some software ideas, but I don't know where to start.", I have the perfect answer for you: TKINTER!

NOTE

This article contains AI generated code, but the article was written, and the code was run, by a human. The headline image is AI generated but all screenshots are real.

Tkinter is a python library that allows you to create desktop applications with python for Windows, Mac, or Linux. Why is this relevant to AI? Tkinter is a no-frills workhorse that is very easy to learn and write. It is so easy to write a tkinter app, that any AI, even local models, can usually write a fully functional, bug free desktop app for you with one prompt.

Let's try an example generated with Llama 4 Maverick, a 17B parameter model that you can run on a laptop.

Prompt

Please create a tkinter app that appends equipment names to the 
start of selected files. The equipment names will be lines in a 
file called equipment.txt in the same directory as the script. 
The user will select files with a file-picker, select equipment 
name from a dropdown, and then click an "Apply" button.

Result

Skip this if you don't know python yet...

import tkinter as tk
from tkinter import filedialog
import os

def load_equipment_names(filename):
    """Load equipment names from a file."""
    try:
        with open(filename, 'r') as f:
            return [line.strip() for line in f.readlines()]
    except FileNotFoundError:
        return []

def select_files():
    """Open a file dialog to select files."""
    global file_paths
    file_paths = filedialog.askopenfilenames(title="Select Files")
    file_list_label.config(text=f"Selected {len(file_paths)} files")

def apply_renaming():
    """Rename selected files by prepending the selected equipment name."""
    global file_paths
    equipment_name = equipment_var.get()
    if equipment_name and file_paths:
        for file_path in file_paths:
            dir_name, file_name = os.path.split(file_path)
            new_file_name = f"{equipment_name} {file_name}"
            new_file_path = os.path.join(dir_name, new_file_name)
            os.rename(file_path, new_file_path)
        file_list_label.config(text="Renaming applied successfully!")
        file_paths = []

def main():
    global equipment_var, file_paths, file_list_label

    # Create the main window
    root = tk.Tk()
    root.title("Equipment Renamer")

    # Load equipment names from file
    equipment_names = load_equipment_names('equipment.txt')

    # Create equipment name variable and dropdown
    equipment_var = tk.StringVar(root)
    equipment_var.set(equipment_names[0] if equipment_names else '')
    equipment_label = tk.Label(root, text="Equipment Name:")
    equipment_label.grid(row=0, column=0, padx=5, pady=5)
    equipment_menu = tk.OptionMenu(root, equipment_var, *equipment_names)
    equipment_menu.grid(row=0, column=1, padx=5, pady=5)

    # Create file selection button and label
    file_paths = []
    file_button = tk.Button(root, text="Select Files", command=select_files)
    file_button.grid(row=1, column=0, columnspan=2, padx=5, pady=5)
    file_list_label = tk.Label(root, text="No files selected")
    file_list_label.grid(row=2, column=0, columnspan=2, padx=5, pady=5)

    # Create apply renaming button
    apply_button = tk.Button(root, text="Apply", command=apply_renaming)
    apply_button.grid(row=3, column=0, columnspan=2, padx=5, pady=5)

    # Start the main loop
    root.mainloop()

if __name__ == "__main__":
    main()

Does it Work?

Yes it does. Depending on how python is installed, you may be able to just click on the .py file we just created and you will see this tiny app open:

image

If clicking the .py file doesn't run the app, you can run it in Command Prompt using one of these commands:

>python <script name>.py
>python.exe <script name>.py
>python3 <script name>.py

Try typing "py" in your terminal and then clicking the TAB button to see what your python command is after installing it.

After you get the tkinter app to run, here is what happens when you click on the dropdown:

image

...and when you click the select files button, you get your system's native file picker.

image

...and after you click the "Apply" button, the selected file(s) will be renamed:

image

Now we have custom file-renaming software for building our turnover, and we didn't even need internet to create it or run it.

Let's get serious...

No more party tricks. Let's build an excel auditing tool. Excel files are the gold standard for databases in construction industry... maybe "gold" is the wrong word, but excel is the standard. Let's create a reusable auditing tool that compares two excel files, a weld log, and a P6 schedule export.

image

This type of audit would normally require sophisticated software, but tkinter is so simple that ChatGPT should be able to build it in one shot. A tall order for a local model, but that will likely change soon. Here is the prompt I sent ChatGPT:

Create a Python Tkinter app that audits two standardized Excel files: a P6-style schedule export and a weld log.

The app must have:
1. Two file picker buttons:
   - Select Schedule Excel File
   - Select Weld Log Excel File
2. One “Run Audit” button
3. A large scrollable text output area for results

Use pandas/openpyxl to read the files.

Hard-code these columns:
Schedule file:
- System
- Finish

Weld log:
- System
- NPS
- Date Welded

Audit checks:
1. List systems in weld log but missing from schedule.
2. List systems in schedule but missing from weld log.
3. Calculate average weld-diameter-inches per work day for the past 7 calendar days:
   NPS × completed weld count per day, using rows where Date Welded is populated.
4. Warn when welding for a system is not complete at least 3 days before that system’s schedule Finish date.
5. Estimate delay risk per system by comparing remaining weld-diameter-inches to the current average daily weld-diameter-inch rate.

Output all findings clearly in the Tkinter text area. Include counts, warnings, and a short plain-English summary. Handle missing files, missing columns, blank dates, and invalid Excel files gracefully.

And here is the code it generated:

import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import pandas as pd
from datetime import datetime, timedelta


SCHEDULE_COLUMNS = ["System", "Finish"]
WELD_COLUMNS = ["System", "NPS", "Date Welded"]


class WeldAuditApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Schedule vs Weld Log Audit")
        self.root.geometry("1000x700")

        self.schedule_file = None
        self.weld_file = None

        tk.Button(root, text="Select Schedule Excel File", command=self.select_schedule).pack(pady=5)
        tk.Button(root, text="Select Weld Log Excel File", command=self.select_weld_log).pack(pady=5)
        tk.Button(root, text="Run Audit", command=self.run_audit, font=("Arial", 11, "bold")).pack(pady=10)

        self.output = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=120, height=35)
        self.output.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

    def select_schedule(self):
        self.schedule_file = filedialog.askopenfilename(
            title="Select Schedule Excel File",
            filetypes=[("Excel Files", "*.xlsx *.xls")]
        )

    def select_weld_log(self):
        self.weld_file = filedialog.askopenfilename(
            title="Select Weld Log Excel File",
            filetypes=[("Excel Files", "*.xlsx *.xls")]
        )

    def write(self, text=""):
        self.output.insert(tk.END, text + "\n")

    def validate_columns(self, df, required_columns, file_label):
        missing = [col for col in required_columns if col not in df.columns]
        if missing:
            raise ValueError(f"{file_label} is missing required columns: {', '.join(missing)}")

    def run_audit(self):
        self.output.delete("1.0", tk.END)

        if not self.schedule_file or not self.weld_file:
            messagebox.showerror("Missing Files", "Please select both Excel files before running the audit.")
            return

        try:
            schedule = pd.read_excel(self.schedule_file, engine="openpyxl")
            welds = pd.read_excel(self.weld_file, engine="openpyxl")
        except Exception as e:
            messagebox.showerror("Invalid Excel File", f"Could not read one or both Excel files:\n\n{e}")
            return

        try:
            self.validate_columns(schedule, SCHEDULE_COLUMNS, "Schedule file")
            self.validate_columns(welds, WELD_COLUMNS, "Weld log")
        except ValueError as e:
            messagebox.showerror("Missing Columns", str(e))
            return

        try:
            schedule["System"] = schedule["System"].astype(str).str.strip()
            welds["System"] = welds["System"].astype(str).str.strip()

            schedule = schedule[schedule["System"].notna() & (schedule["System"] != "") & (schedule["System"] != "nan")]
            welds = welds[welds["System"].notna() & (welds["System"] != "") & (welds["System"] != "nan")]

            schedule["Finish"] = pd.to_datetime(schedule["Finish"], errors="coerce")
            welds["Date Welded"] = pd.to_datetime(welds["Date Welded"], errors="coerce")
            welds["NPS"] = pd.to_numeric(welds["NPS"], errors="coerce").fillna(0)

            completed_welds = welds[welds["Date Welded"].notna()].copy()

            schedule_systems = set(schedule["System"].dropna().unique())
            weld_systems = set(welds["System"].dropna().unique())

            missing_from_schedule = sorted(weld_systems - schedule_systems)
            missing_from_weld_log = sorted(schedule_systems - weld_systems)

            self.write("WELD LOG / SCHEDULE AUDIT")
            self.write("=" * 80)
            self.write(f"Schedule rows: {len(schedule)}")
            self.write(f"Weld log rows: {len(welds)}")
            self.write(f"Completed weld rows: {len(completed_welds)}")
            self.write("")

            self.write("1) Systems in weld log but missing from schedule")
            self.write(f"Count: {len(missing_from_schedule)}")
            for system in missing_from_schedule:
                self.write(f" - {system}")
            self.write("")

            self.write("2) Systems in schedule but missing from weld log")
            self.write(f"Count: {len(missing_from_weld_log)}")
            for system in missing_from_weld_log:
                self.write(f" - {system}")
            self.write("")

            today = pd.Timestamp(datetime.today().date())
            start_date = today - pd.Timedelta(days=6)

            recent = completed_welds[
                (completed_welds["Date Welded"].dt.normalize() >= start_date) &
                (completed_welds["Date Welded"].dt.normalize() <= today)
            ].copy()

            recent["WDI"] = recent["NPS"]
            daily_wdi = recent.groupby(recent["Date Welded"].dt.date)["WDI"].sum()

            total_recent_wdi = daily_wdi.sum()
            avg_daily_wdi = total_recent_wdi / 7 if total_recent_wdi > 0 else 0

            self.write("3) Average weld-diameter-inches per work day, past 7 calendar days")
            self.write(f"Date range: {start_date.date()} through {today.date()}")
            self.write(f"Total weld-diameter-inches: {total_recent_wdi:.2f}")
            self.write(f"Average per day: {avg_daily_wdi:.2f}")
            self.write("")
            self.write("Daily totals:")
            for i in range(7):
                day = (start_date + pd.Timedelta(days=i)).date()
                self.write(f" - {day}: {daily_wdi.get(day, 0):.2f}")
            self.write("")

            self.write("4) Completion warnings and delay risk by system")
            self.write("-" * 80)

            warnings = 0
            high_risk = 0

            all_systems = sorted(schedule_systems | weld_systems)

            for system in all_systems:
                system_schedule = schedule[schedule["System"] == system]
                system_welds = welds[welds["System"] == system]
                system_completed = system_welds[system_welds["Date Welded"].notna()]

                total_wdi = system_welds["NPS"].sum()
                completed_wdi = system_completed["NPS"].sum()
                remaining_wdi = max(total_wdi - completed_wdi, 0)

                finish_date = system_schedule["Finish"].min()

                if pd.isna(finish_date):
                    self.write(f"{system}: No valid Finish date found.")
                    continue

                required_done_date = finish_date.normalize() - pd.Timedelta(days=3)
                days_until_required_done = (required_done_date - today).days

                if remaining_wdi > 0 and today >= required_done_date:
                    warnings += 1
                    status = "WARNING: welding is not complete at least 3 days before Finish."
                else:
                    status = "OK"

                if avg_daily_wdi > 0:
                    estimated_days_needed = remaining_wdi / avg_daily_wdi
                else:
                    estimated_days_needed = float("inf") if remaining_wdi > 0 else 0

                if estimated_days_needed > max(days_until_required_done, 0) and remaining_wdi > 0:
                    risk = "HIGH DELAY RISK"
                    high_risk += 1
                elif remaining_wdi > 0:
                    risk = "Moderate / monitor"
                else:
                    risk = "Complete"

                self.write(f"System: {system}")
                self.write(f"  Finish date: {finish_date.date()}")
                self.write(f"  Required weld complete by: {required_done_date.date()}")
                self.write(f"  Total WDI: {total_wdi:.2f}")
                self.write(f"  Completed WDI: {completed_wdi:.2f}")
                self.write(f"  Remaining WDI: {remaining_wdi:.2f}")
                self.write(f"  Estimated days needed at current rate: "
                           f"{estimated_days_needed:.1f}" if estimated_days_needed != float("inf")
                           else "  Estimated days needed at current rate: No current production rate")
                self.write(f"  Status: {status}")
                self.write(f"  Delay risk: {risk}")
                self.write("")

            self.write("=" * 80)
            self.write("Plain-English Summary")
            self.write("=" * 80)
            self.write(f"{len(missing_from_schedule)} weld log systems are not found in the schedule.")
            self.write(f"{len(missing_from_weld_log)} schedule systems are not covered by the weld log.")
            self.write(f"The current 7-day average production rate is {avg_daily_wdi:.2f} weld-diameter-inches per day.")
            self.write(f"{warnings} systems have completion timing warnings.")
            self.write(f"{high_risk} systems appear to have high delay risk based on remaining WDI and current production rate.")

        except Exception as e:
            messagebox.showerror("Audit Error", f"The audit could not be completed:\n\n{e}")


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

After using pip to install some libraries, the results work perfectly. I select two files, hit "Run Audit", and the results print directly to the tkinter app.

image

Even though I needed internet to use ChatGPT, I can now use this auditing tool in the headache shack in a power house or on a greenfield site with no cell or internet service. At time of writing, the jobsite internet problem is on its way to being solved, but there are other benefits to using a reusable tkinter app:

1) Tkinter apps are deterministic, and will not contain hallucinations.

2) Errors can come from bugs, but these can be easily fixed because of how simple tkinter is to read/write.

3) Your tkinter code is part of your audit trail. Anyone can see how your audit works.

4) Reusable tools make efficient use AI tokens. Build it once, use it forever.

5) Non-technical users can operate a tkinter app. They just need to install python.

TLDR: tkinter apps cost almost nothing to write with AI. Building complex, job-specific software should no longer be considered 'wasteful' of 'out of reach'. Try building one today!

Python

tkinter

pandas

openpyxl See note about security!