<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kashifa Khursheed]]></title><description><![CDATA[Kashifa Khursheed]]></description><link>https://kashu94.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 29 Oct 2026 06:08:45 GMT</lastBuildDate><atom:link href="https://kashu94.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[What happens when a captain calls in sick at 1:30 am? I built a graph to find out!]]></title><description><![CDATA[How I used a FalkorDB graph to find legal cover when a captain calls in sick, explain every decision, and let AI agents share memory and coordinate.]]></description><link>https://kashu94.hashnode.dev/what-happens-when-a-captain-calls-in-sick-at-1-30-am-i-built-a-graph-to-find-out</link><guid isPermaLink="true">https://kashu94.hashnode.dev/what-happens-when-a-captain-calls-in-sick-at-1-30-am-i-built-a-graph-to-find-out</guid><category><![CDATA[falkordb]]></category><category><![CDATA[graph database]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[hackathon]]></category><category><![CDATA[WeMakeDevs]]></category><dc:creator><![CDATA[Kashifa Khursheed]]></dc:creator><pubDate>Sun, 18 Oct 2026 15:18:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/202a111f-67c0-4470-9c6d-0cacd1346b5d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Someone in crew control now has to answer a lot of questions very quickly. Which flights are left without a captain? Who could replace them? Has that person rested long enough? Are their licence and medical still valid? Are they even based at the right airport? What does each choice cost? And who needs to be told?</p>
<p>None of these answers sit in one place. They depend on how things are connected: crew to trips, trips to flights, flights to aircraft, people to rules. That's why I built CrewOps on FalkorDB for the Graph Hacks hackathon. It keeps the whole operation in a graph and answers these questions by following the connections.</p>
<h2>The setup</h2>
<p>The data is a made-up week for three fictional airlines: 150 crew, 147 flights, six aircraft, reserve crew on call, certificates, 28 days of duty history, boarding gates and the people on duty at each station. Loaded into FalkorDB, that's about 6,000 nodes, and it loads in under a second.</p>
<p>There are five kinds of users:</p>
<ul>
<li><p>a <strong>reporter</strong>, who logs what went wrong</p>
</li>
<li><p>a <strong>controller</strong>, who plans the cover (there are three desks)</p>
</li>
<li><p>an <strong>approver</strong>, who has to be a different person from the controller</p>
</li>
<li><p><strong>crew</strong>, who only see their own schedule</p>
</li>
<li><p>an <strong>auditor</strong>, who can read everything but change nothing</p>
</li>
</ul>
<h2>The graph</h2>
<p>Here's the core of the model:</p>
<pre><code class="language-plaintext">(:Crew)-[:ASSIGNED]-&gt;(:Pairing)-[:HAS_DAY]-&gt;(:DutyDay)-[:LEG]-&gt;(:Flight)
(:Flight)-[:ON]-&gt;(:Aircraft)-[:OPERATED_BY]-&gt;(:Airline)
(:Flight)-[:NEXT_LEG]-&gt;(:Flight)          // the aircraft's day, flight by flight
(:Crew)-[:HOLDS]-&gt;(:Certification)
(:Crew)-[:ON_CALL]-&gt;(:Station)            // reserve duty
</code></pre>
<p>A "pairing" is airline speak for a trip: one or more days of flying that a crew does together.</p>
<p>The part I'm happiest with is that decisions live in the graph too, not just data. When a disruption comes in, the analysis writes its results back as nodes and edges:</p>
<pre><code class="language-plaintext">(:Disruption)-[:GROUNDS]-&gt;(:Crew)
(:Disruption)-[:HAS_GAP]-&gt;(:Gap)-[:OPTION {rank, cost}]-&gt;(:Crew)
(:Gap)-[:RULED_OUT]-&gt;(:Exclusion)-[:BREACHES]-&gt;(:Rule)
(:Plan)-[:APPROVED_BY]-&gt;(:User)
</code></pre>
<p>So the next person, the next agent, or the auditor all read the same picture.</p>
<h2>Finding cover is just a walk through the graph</h2>
<p>Who could possibly fly this seat? Only crew from the airline that operates the aircraft, at the right rank, who aren't already on the trip. In Cypher that's:</p>
<pre><code class="language-cypher">MATCH (p:Pairing {id: $pairing})-[:FLOWN_BY]-&gt;(:Aircraft)-[:OPERATED_BY]-&gt;(al:Airline)
MATCH (c:Crew)-[:EMPLOYED_BY]-&gt;(al)
WHERE c.rank = $rank AND NOT (c)-[:ASSIGNED]-&gt;(p)
RETURN c.id
</code></pre>
<p>Then one more query pulls everything the legality rules need for every candidate at once: their aircraft ratings, certificates, other duties, the last 28 days of duty hours, reserve windows, and whether anyone has reported them unavailable. Seven rules run on that, covering things like the 13-hour duty limit (which shrinks with each extra flight), 60 hours in any 7 days, and 12 hours of rest.</p>
<p>For our sick captain, 9 captains are considered and 4 are legal. The cheapest is recommended, a reserve captain at INR 23,090. Cancelling the trip would cost about 65 times more.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/70bf98b2-7726-449d-b685-83897b9edac1.png" alt="The controller can pick any legal option, hold the departure, or cancel. Cancelling asks for confirmation." style="display:block;margin:0 auto" />

<p>What I like most is the "why not?" part. If a controller asks why a certain captain wasn't picked, the answer is a short walk: <code>Gap -&gt; RULED_OUT -&gt; Exclusion -&gt; BREACHES -&gt; Rule</code>. For one captain it comes back as: <em>would exceed 60 hours in 7 days by 1h20m on 13 Oct</em>. No guessing, just the graph.</p>
<h2>Delays travel along the aircraft</h2>
<p>Delays were fun to model. When a flight runs late, the aircraft is late for the rest of its day, and so is the crew on it. With variable-length paths that's a few lines:</p>
<pre><code class="language-cypher">MATCH (f:Flight {id: $flight})
MATCH path = (f)-[:NEXT_LEG*0..12]-&gt;(g:Flight)
RETURN g.id, g.dep_ts, length(path) AS hop
ORDER BY hop
</code></pre>
<p>Each delayed flight then climbs up to its crew's duty day to check the limit. In my test case, a 90-minute delay stretches a four-flight day to 12.75 hours against a 12-hour limit, so the last flight has to come off that crew.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/1017474b-d850-4724-a4ed-0192f4925dac.png" alt="A 90-minute delay carries through four flights and breaks the crew's duty limit." style="display:block;margin:0 auto" />

<h2>Two people, one write</h2>
<p>In crew control you don't want software quietly changing the roster. So in CrewOps the agents only recommend. A controller submits a plan, and a different person has to approve it.</p>
<p>When they approve, the app checks legality again and recomputes a fingerprint of the plan and the roster it was made against. If anything changed in between, it refuses. Then the whole change happens in a single Cypher query: the crew swap, any cancelled flights, any moved gates or delayed departures. It all happens or none of it does.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/c0ab28d8-52fc-43bc-b52e-71e5bc78d5f8.png" alt="The decision trail and the disruption's neighbourhood in the graph, read straight from FalkorDB." style="display:block;margin:0 auto" />

<h2>Agents that share a graph instead of talking to each other</h2>
<p>There are a few agents behind the app. A triage agent picks up new reports, works out what they affect, and writes the options back. A notifier tells the right people. An assistant answers questions.</p>
<p>They never call each other. They coordinate through the graph. A triage agent claims a disruption like this:</p>
<pre><code class="language-cypher">MATCH (d:Disruption {status: 'REPORTED'})
WITH d ORDER BY d.reported_ts LIMIT 1
SET d.status = 'ANALYSING', d.claimed_by = $agent
</code></pre>
<p>FalkorDB runs write queries on a graph one at a time, so two agents can never grab the same disruption. I tested this with four agents racing over six disruptions, and each one was claimed exactly once.</p>
<h2>Memory is a graph too</h2>
<p>Every user gets their own memory graph, in the same FalkorDB instance as the main one. It stores their conversations, the crew and trips they talked about, and their preferences.</p>
<p>That's what makes follow-ups work. If I ask "Who covers C-1042 on 13 Oct?" and then "Is she legal for it?", the assistant walks back through my memory graph to find who "she" and "it" were. When I come back the next day, it remembers what I asked last time.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/a2688a6c-75a8-47cb-ae53-75ed451f5b4a.png" alt="The assistant picks graph tools, and every answer shows the Cypher queries behind it." style="display:block;margin:0 auto" />

<p>The assistant uses free open models through Groq to decide which graph tools to call. The numbers always come from the graph, never from the model doing maths. If the model is busy or down, a simple rule-based planner answers instead, so the demo never breaks.</p>
<h2>A few other things</h2>
<ul>
<li><p><strong>Graph algorithms as tools.</strong> PageRank and betweenness on the route network show that Bangalore is the hub everything depends on. Shortest paths suggest how to move crew between airports.</p>
</li>
<li><p><strong>Crew schedules.</strong> Desk roles can see every crew member's week across all three airlines. Crew only see their own.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6ad4dc5b213fe338c6e9d7cd/02dccb09-ce28-455c-be24-262d3f567ade.png" alt="Every crew member's week, across all three airlines." style="display:block;margin:0 auto" />

<h2>What I learned</h2>
<p><strong>Put decisions in the graph, not just data.</strong> This was the biggest lesson. Once options, exclusions, plans and approvals were nodes and edges, the hard features became easy. Explanations, the audit trail, agent coordination and memory all turned into simple traversals.</p>
<p><strong>Getting the rules right takes patience.</strong> Duty limits use calendar days, the duty limit shrinks with every extra flight, and a delay today can make someone illegal for a trip two days later. I worked out six scenarios by hand and kept fixing things until the graph matched every one of them exactly.</p>
<p><strong>Free models need some care.</strong> Groq's free tier limits tokens per minute, and my first version blew through it in two questions. Sending only the tools the model actually needs, and switching to another model when one is busy, fixed it.</p>
<p><strong>A few FalkorDB notes.</strong> It's fast, and having many graphs in one instance is great for per-user memory. I did hit a couple of things: <code>EXISTS { }</code> subqueries aren't supported (OPTIONAL MATCH and count() worked instead), and betweenness is sampled, so I set a fixed seed to get the same result every time.</p>
<h2>Try it</h2>
<ul>
<li><p>Live demo: <a href="https://crewops-falkordb.onrender.com">crewops-falkordb.onrender.com</a></p>
</li>
<li><p>Code: <a href="https://github.com/kashu94/crewops-falkordb">github.com/kashu94/crewops-falkordb</a></p>
</li>
</ul>
<p>Sign in as Imran (a reporter) and report a sick crew member, switch to Ananya (a controller) to pick cover, then switch to Meera to approve it. Or open the assistant and ask "Who covers C-1042 on 13 Oct?".</p>
<p>All 123 Cypher queries are listed in the repo under <code>docs/CYPHER.md</code>, if you'd like to see how it all works.</p>
<p>Thanks for reading. I built this for Graph Hacks by WeMakeDevs and FalkorDB.</p>
]]></content:encoded></item></channel></rss>