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:

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:

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

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

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.

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.
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!
Links
openpyxl See note about security!