I built Skylark_Operations, a Python repository on my GitHub, as a conversational AI agent for four drone-operations responsibilities: roster management, assignment tracking, drone inventory, and conflict detection. The project started in 2026-07 and was last updated in 2026-07. Google Sheets is its operational database.
My architecture routes chat through Streamlit in app.py to Gemini gemini-2.5-flash, then through tool dispatch in tool_schemas.py and tools.py to the Pilots, Drones, and Missions worksheets via db_utils.py. Gemini uses native function calling with google-genai manual, non-automatic function calling; the tool layer receives typed arguments rather than parsing model-generated JSON text.
I chose native function calling because raw JSON output can break when the model adds prose or Markdown fences, while post-hoc keyword matching to correct arguments duplicates the model’s decision and is fragile. I keep tool declarations in tool_schemas.py as plain {name, description, parameters} dictionaries without an SDK import, so a provider change should be localized to app.py. Google Sheets is a hard requirement in the brief; its trade-offs are recorded in DECISION_LOG.md.
| Worksheet | Columns |
|---|---|
| Pilots | pilot_id, name, skills, certifications, location, status, current_assignment, day_rate, available_from |
| Drones | drone_id, model, capabilities, weather_rating, status, current_assignment, location, maintenance_due |
| Missions | mission_id, client_name, location, required_skills, required_certifications, start_date, end_date, priority, budget, weather_forecast, status |
The worksheets match the sample CSVs in data/. I store skills, capabilities, certifications, and required_* list fields as comma-separated values in one cell, such as mapping,survey; _split_list() in tools.py parses them. The LLM selects a tool and supplies arguments, while deterministic Python functions perform data access and operations; the LLM does not directly access the data.
get_pilot_rosterqueries pilots by skill, certification, location, and availability;get_drone_fleetqueries drones by capability, location, weather, and availability;get_missionslooks up missions by status or priority.- Pilot and drone matching, plus
check_conflicts, compare project date ranges against each resource’s current assignment using the same overlap check.
The captured project information does not specify the Google Sheets trade-offs, why over-budget pilots are flagged rather than excluded, or the exact semantics of availability, date-overlap boundaries, “near maintenance,” and config.weather_ok. It reports no operational test results, performance measurements, or sample-data counts.
- A pilot without a required certification is hard-excluded from matches and explicitly flagged by
check_conflicts. An available pilot over the mission budget remains a candidate but is flagged, not excluded; the README points toDECISION_LOG.mdfor the rationale, which I do not have here. - The checks cover a drone in maintenance while assigned, a drone not rated for forecast weather via
config.weather_ok, and a pilot or drone located somewhere different from the mission or from each other. calculate_pilot_costcalculates a pilot’s mission cost and checks the budget.match_pilots_to_missionandmatch_drones_to_missionreturn candidate resources with flags; drone matching also checks date overlap.check_conflictsscans a mission’s assigned pilot and drone.update_pilot_statusandupdate_drone_statuswrite to their respective sheets.flag_maintenanceidentifies drones in or near maintenance.coordinate_urgent_reassignmentranks replacement candidates when a resource suddenly becomes unavailable.
To run Skylark_Operations locally, I install the requirements and start the Streamlit app. I put GEMINI_API_KEY, SHEET_URL, and GOOGLE_SERVICE_ACCOUNT_JSON in .streamlit/secrets.toml; the service-account JSON is a single-line string.
pip install -r requirements.txt
streamlit run app.py
# .streamlit/secrets.toml
GEMINI_API_KEY = "your_gemini_api_key"
SHEET_URL = "https://docs.google.com/spreadsheets/d/XXXX/edit"
GOOGLE_SERVICE_ACCOUNT_JSON = '{"type": "service_account", ...}'
I can point the app at an existing Google Sheet without running a setup script. It must have tabs named exactly Pilots, Drones, and Missions; I share it with the service account’s client_email with Editor access and set SHEET_URL. db_utils.py reads the tabs on each request. Status writes locate the status column by its header, so extra columns and a different column order are acceptable. The hard requirements are those three tab names and a column literally named status on both Pilots and Drones.
- If I add missing feature-dependent columns, their position does not matter:
Pilots.day_rateis a number used for cost calculation and budget-overrun checks;Drones.weather_ratingacceptsUnrated,IP43,IP54, orIP55for weather-risk checks;Missions.budgetis a number used for budget-overrun checks;Missions.weather_forecastacceptsSunny,Cloudy,Windy, orRainyfor weather-risk checks; andMissions.statusacceptsPlanned,Active, orCompletedforget_missionsfiltering.
To create the sheet from sample CSVs in data/, I create a Google Cloud service account, enable the Sheets and Drive APIs, and download its JSON key. I then run setup_sheets.py with the credentials, optionally supplying an existing sheet URL:
python setup_sheets.py --creds path/to/service_account.json
python setup_sheets.py --creds path/to/service_account.json --sheet-url "https://docs.google.com/..."
I can deploy by pushing the repository to GitHub, creating a Streamlit Community Cloud app pointing at app.py, and adding the three secrets in its Secrets panel. Railway, Render, or another host that runs Streamlit also works.
- Each pilot and drone has one
current_assignment, not an assignment history table. The app checks overlap against that currently assigned mission; it does not model a resource juggling three or more simultaneous bookings. This limitation applies to pilots and drones; the Decision Log discusses a possible v2 approach. - I treat mission budget as a ceiling on pilot cost alone; drone and equipment costs are not included in the sample data.
- The read cache,
db_utils._fetch_data_cached, has a 5-second TTL, is per-process, speeds up repeat reads within one Streamlit session, and is cleared on every write. It does not make concurrent writes across coordinators or sessions safe; the Decision Log discusses Google Sheets over a real DB. - The app has no authentication or access control, so anyone with the hosted link can write pilot or drone status changes.
- I name the Missions tab’s ID and certification columns exactly
mission_idandrequired_certifications, becausetools.pyreads those names.