# ========================================== # app.py — FieldTech Pro | Streamlit App # ========================================== # Author: OpenAI GPT-5 # Date: 2026-05-09 # ========================================== import streamlit as st import pandas as pd import plotly.express as px import altair as alt from datetime import datetime, timedelta import os, json, uuid, time from io import BytesIO from PIL import Image import base64 from weasyprint import HTML # ========================================== # INITIAL SETUP & FOLDERS # ========================================== APP_NAME = "FieldTech Pro – International Service Technician Tracker" BASE_DIR = os.path.join(os.getcwd(), "FieldTechPro_data") os.makedirs(BASE_DIR, exist_ok=True) for sub in ["projects/active", "projects/finished", "media"]: os.makedirs(os.path.join(BASE_DIR, sub), exist_ok=True) # ========================================== # PAGE CONFIG & STYLES # ========================================== st.set_page_config(page_title=APP_NAME, layout="wide", page_icon="🧰") st.markdown( """ """, unsafe_allow_html=True, ) # ========================================== # HELPERS # ========================================== def gen_project_id() -> str: return f"FT-{datetime.now().strftime('%Y%m%d')}-{str(uuid.uuid4())[:4]}" def get_active_projects(): path = os.path.join(BASE_DIR, "projects/active") files = [f for f in os.listdir(path) if f.endswith(".json")] return [os.path.splitext(f)[0] for f in files] def load_project(project_id): pj = os.path.join(BASE_DIR, "projects/active", f"{project_id}.json") if not os.path.exists(pj): pj = os.path.join(BASE_DIR, "projects/finished", f"{project_id}.json") if not os.path.exists(pj): return None with open(pj, "r") as f: return json.load(f) def save_project(project): pid = project["project_id"] folder = "active" if project.get("status") != "completed" else "finished" path_json = os.path.join(BASE_DIR, f"projects/{folder}/{pid}.json") with open(path_json, "w") as f: json.dump(project, f, indent=4) def move_to_finished(project): project["status"] = "completed" save_project(project) active_path = os.path.join(BASE_DIR, "projects/active", f"{project['project_id']}.json") finished_path = os.path.join(BASE_DIR, "projects/finished", f"{project['project_id']}.json") if os.path.exists(active_path): os.rename(active_path, finished_path) def ensure_media_folder(project_id): path = os.path.join(BASE_DIR, "media", project_id) os.makedirs(path, exist_ok=True) return path def add_expense(project, category, amount, currency="USD"): exp = project.get("expenses", []) exp.append({"date": datetime.now().isoformat(), "category": category, "amount": float(amount), "currency": currency}) project["expenses"] = exp def fake_ocr_receipt(image): # Placeholder for Hugging Face OCR # In production, call Donut or TrOCR model here. return pd.DataFrame([ {"Date": datetime.now().strftime("%Y-%m-%d"), "Merchant": "ACME Tools", "Total": 245.50, "Currency": "USD", "Item": "Replacement Kit"} ]) def fake_video_transcribe(video_bytes): # Placeholder transcription (replace with actual model call) return "Technician performed diagnostics, replaced fuse, verified operation, and closed service ticket." # ========================================== # SIDEBAR NAVIGATION # ========================================== menu = st.sidebar.radio( "Navigation", ["🏠 Home","➕ New Project","📂 Existing Projects","🌍 Prospect","📑 Documentation","📊 Reports"] ) # ========================================== # HOME # ========================================== if menu == "🏠 Home": st.title("🏠 Dashboard") col1, col2, col3, col4 = st.columns(4) col1.markdown(f"
Active Trips
{len(get_active_projects())}
", unsafe_allow_html=True) col2.markdown("
Billable Hours (Week)
42.5
", unsafe_allow_html=True) col3.markdown("
Expenses Pending
$560
", unsafe_allow_html=True) col4.markdown("
Completed Projects
12
", unsafe_allow_html=True) # Charts st.subheader("Hours Trend") df_hours = pd.DataFrame({ "Day": [d.strftime("%a") for d in [datetime.now() - timedelta(days=i) for i in range(6,-1,-1)]], "Hours": [6,7,8,9,7,5,6] }) fig = px.line(df_hours, x="Day", y="Hours", markers=True) st.plotly_chart(fig, use_container_width=True) st.subheader("Expense Breakdown") exp_df = pd.DataFrame({"Category":["Travel","Meals","Tools","Hotels"],"Amount":[300,120,250,400]}) chart = alt.Chart(exp_df).mark_arc(innerRadius=50).encode(theta="Amount", color="Category") st.altair_chart(chart, use_container_width=True) st.markdown("
", unsafe_allow_html=True) st.button("➕ Start New Project") st.markdown("
", unsafe_allow_html=True) # ========================================== # NEW PROJECT # ========================================== elif menu == "➕ New Project": st.title("➕ New Project") if "current_project" not in st.session_state: st.session_state.current_project = {"project_id": gen_project_id(), "status": "active"} proj = st.session_state.current_project ensure_media_folder(proj["project_id"]) st.subheader("Project Header") c1, c2, c3, c4 = st.columns(4) proj["client"] = c1.text_input("Client Name", proj.get("client","")) proj["location"] = c2.text_input("Site/Location", proj.get("location","")) proj["country"] = c3.text_input("Country", proj.get("country","")) proj["technician"] = c4.text_input("Technician Name", proj.get("technician","")) proj["start_date"] = st.date_input("Start Date", proj.get("start_date", datetime.now().date())) proj["end_date"] = st.date_input("End Date", proj.get("end_date", datetime.now().date())) st.markdown("---") st.subheader("Trip Log & Travel") colA, colB = st.columns(2) proj["travel_type"] = colA.selectbox("Travel Type", ["Road","Air","Train","Mixed"], index=0) proj["distance_km"] = colB.number_input("Distance (km)", value=float(proj.get("distance_km",0.0))) proj["distance_miles"] = round(proj["distance_km"] * 0.621, 2) st.caption(f"≈ {proj['distance_miles']} miles") with st.expander("Labor Hours"): proj["task_category"] = st.selectbox("Task Category", ["Diagnostic","Repair","Testing","Training","Waiting"]) proj["hours_worked"] = st.number_input("Hours Worked", value=float(proj.get("hours_worked",0.0))) if proj["hours_worked"] > 8: st.warning("Overtime detected!") with st.expander("Hotel & Accommodation"): proj["hotel_rate"] = st.number_input("Nightly Rate (USD)", value=float(proj.get("hotel_rate",0.0))) proj["nights"] = st.number_input("Nights", value=int(proj.get("nights",0))) proj["hotel_total"] = proj["hotel_rate"] * proj["nights"] st.caption(f"Total: ${proj['hotel_total']:.2f}") with st.expander("Expenses"): cat = st.selectbox("Category", ["Travel","Meal","Tools","Other"]) amt = st.number_input("Amount", min_value=0.0) cur = st.text_input("Currency", "USD") if st.button("Add Expense"): add_expense(proj, cat, amt, cur) save_project(proj) if proj.get("expenses"): st.table(pd.DataFrame(proj["expenses"])) with st.expander("Media Capture"): img = st.camera_input("Take Photo") if img: img_path = os.path.join(ensure_media_folder(proj["project_id"]), f"photo_{int(time.time())}.jpg") Image.open(img).save(img_path) st.success("Photo saved.") vid = st.file_uploader("Upload Video", type=["mp4","mov"]) if vid: vid_path = os.path.join(ensure_media_folder(proj["project_id"]), vid.name) open(vid_path,"wb").write(vid.read()) st.success("Video uploaded.") proj["notes"] = st.text_area("Notes / Voice-to-text field") # Auto-Save if int(time.time()) % 30 == 0: save_project(proj) st.markdown("---") col_end1, col_end2 = st.columns(2) if col_end1.button("💾 Save & Continue Later"): save_project(proj) st.success("Project saved.") if col_end2.button("✅ Mark Project Complete"): move_to_finished(proj) st.success("Project marked complete and moved to finished folder.") # ========================================== # EXISTING PROJECTS # ========================================== elif menu == "📂 Existing Projects": st.title("📂 Existing Projects") active = get_active_projects() if active: sel = st.selectbox("Select Project", active) data = load_project(sel) st.json(data) else: st.info("No active projects found.") # ========================================== # PROSPECT # ========================================== elif menu == "🌍 Prospect": st.title("🌍 Prospect Capture") new_client = st.text_input("Prospective Client") loc = st.text_input("Location") service = st.text_area("Service Summary") if st.button("Save Prospect"): dfp = pd.DataFrame([{"Client":new_client, "Location":loc, "Service":service, "Date":datetime.now().isoformat()}]) dfp.to_csv(os.path.join(BASE_DIR,"prospects.csv"), mode="a", header=not os.path.exists(os.path.join(BASE_DIR,"prospects.csv")), index=False) st.success("Prospect saved.") # ========================================== # DOCUMENTATION (OCR + TRANSCRIPTION) # ========================================== elif menu == "📑 Documentation": st.title("📑 Documentation & Media") pid_list = get_active_projects() + [f for f in os.listdir(os.path.join(BASE_DIR,"projects/finished")) if f.endswith(".json")] pid_list = [os.path.splitext(f)[0] for f in pid_list] sel_project = st.selectbox("Select Project", pid_list) if sel_project: path_media = ensure_media_folder(sel_project) st.subheader("Media Gallery") imgs = [f for f in os.listdir(path_media) if f.lower().endswith(".jpg")] for img_path in imgs: st.image(os.path.join(path_media,img_path), width=250) st.subheader("Receipt OCR") receipt = st.file_uploader("Upload Receipt Image", type=["jpg","png"]) if receipt: st.image(receipt) data = fake_ocr_receipt(receipt) st.table(data) if st.button("Add Extracted Data to Expenses"): proj = load_project(sel_project) for _, r in data.iterrows(): add_expense(proj, "Receipt", r["Total"], r["Currency"]) save_project(proj) st.success("Extracted data added.") st.subheader("Video Transcription") video = st.file_uploader("Upload Video for Transcription", type=["mp4","mov"]) if video: text = fake_video_transcribe(video.read()) st.text_area("Transcribed Text", text) # ========================================== # REPORTS # ========================================== elif menu == "📊 Reports": st.title("📊 Reports") fins = [f for f in os.listdir(os.path.join(BASE_DIR,"projects/finished")) if f.endswith(".json")] if not fins: st.info("No completed projects yet.") else: sel = st.selectbox("Select Finished Project", [os.path.splitext(f)[0] for f in fins]) proj = load_project(sel) st.json(proj) if st.button("📄 Generate Full Service Report"): # create HTML report html = f"""

Service Report

{proj.get('client','')}

Project ID: {proj['project_id']} | {proj.get('location','')}

Summary

Expenses

{pd.DataFrame(proj.get('expenses',[])).to_html(index=False)}

Notes

{proj.get('notes','')}

""" pdf_bytes = HTML(string=html).write_pdf() st.download_button("Download Report PDF", pdf_bytes, file_name=f"{proj['project_id']}_report.pdf")