<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ahmed Maaloul · Research</title><description>Research and engineering notes on retrieval, evaluation, and the systems that keep AI accurate in production.</description><link>https://ahmedmaaloul.com/</link><language>en</language><item><title>A leak that scores perfect calibration: measuring same-day leakage in delay prediction</title><link>https://ahmedmaaloul.com/research/leakage-perfect-calibration-delay-prediction/</link><guid isPermaLink="true">https://ahmedmaaloul.com/research/leakage-perfect-calibration-delay-prediction/</guid><description>Computing &quot;how bad is today at this airport&quot; over the whole day rather than the hours that had elapsed inflates PR-AUC by 21% and drives expected calibration error to 0.00008, which any results table prints as 0.000. Good calibration is not evidence of a sound pipeline.</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Published flight-delay models are routinely evaluated on a random split, with
historical features computed over the whole dataset. Both are leaks. While
building &lt;a href=&quot;https://github.com/ahmedmaaloul/transportpredictor&quot;&gt;TransportPredictor&lt;/a&gt;
I measured what the second one is worth, and found that its clearest symptom is
a metric usually read as reassurance.&lt;/p&gt;
&lt;h2 id=&quot;setup-and-why-the-flights-are-american&quot;&gt;Setup, and why the flights are American&lt;/h2&gt;
&lt;p&gt;The scarce thing in European aviation data is not volume, it is the target. No
source publishes per-flight actual departure times for European airports on terms
this project accepts, which means no account, no API key, no quota and no
retention cap. The US Department of Transportation publishes exactly that, in the
public domain, for every domestic flight.&lt;/p&gt;
&lt;p&gt;So the method is built where the ground truth exists at scale. &lt;strong&gt;Method transfers
between regions. A fitted model does not, and nothing in this piece describes
Frankfurt or any European airport.&lt;/strong&gt; Leakage is a property of how a feature is
computed against a clock, not of a country, which is why this particular question
can be answered on the corpus that can answer it.&lt;/p&gt;
&lt;p&gt;5,834,764 US domestic flights departing 15 large hubs, January 2023 to December
2024. The target is a departure 15 minutes late or more; the test base rate is
18.7%. The prediction is made &lt;strong&gt;two hours before the scheduled departure&lt;/strong&gt;, so a
feature is admissible only if it was observable at that instant. The split is
chronological with no overlap: the last two months are held out.&lt;/p&gt;
&lt;h2 id=&quot;two-versions-of-one-feature&quot;&gt;Two versions of one feature&lt;/h2&gt;
&lt;p&gt;The strongest single predictor is the delay rate observed at the departure
airport so far that day. There are two ways to compute it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;as-of&lt;/strong&gt;: only flights that had actually departed by the prediction instant;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;whole day&lt;/strong&gt;: a group-by over the calendar day, including the hours that had
not happened yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second is not a strawman. It is what a group-by gives you by default.&lt;/p&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Encoding&lt;/th&gt;&lt;th&gt;PR-AUC&lt;/th&gt;&lt;th&gt;Brier skill&lt;/th&gt;&lt;th&gt;Calibration error&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;as-of&lt;/td&gt;&lt;td&gt;0.287&lt;/td&gt;&lt;td&gt;+0.015&lt;/td&gt;&lt;td&gt;0.050&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;whole day&lt;/td&gt;&lt;td&gt;0.348&lt;/td&gt;&lt;td&gt;+0.072&lt;/td&gt;&lt;td&gt;&lt;strong&gt;0.00008&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The leak is worth +21.3% PR-AUC and nearly quintuples Brier skill.&lt;/p&gt;
&lt;h2 id=&quot;the-number-that-matters-is-the-last-one&quot;&gt;The number that matters is the last one&lt;/h2&gt;
&lt;p&gt;The leaky feature’s expected calibration error is 0.00008. Printed to the three
decimals a results table normally carries, that reads 0.000, and six of its nine
populated reliability bins have a gap of exactly zero. The honest encoding scores
0.050, about six hundred and fifty times worse.&lt;/p&gt;
&lt;p&gt;A model that has seen the answer is calibrated on the data it saw, because within
each bin its prediction and the outcome are the same quantity. Calibration is
normally reported as evidence that a pipeline is sound. Here it is the clearest
available evidence that it is not.&lt;/p&gt;
&lt;h2 id=&quot;why-a-coarse-check-misses-it&quot;&gt;Why a coarse check misses it&lt;/h2&gt;
&lt;p&gt;The same test on a route-level delay rate moves PR-AUC from 0.219 to 0.223, a
gain of 1.7%. Route delay rates are stable over months, so seeing the future
barely improves the estimate. Anyone who tests for leakage only on a coarse,
slow-moving grouping will conclude their pipeline is clean.&lt;/p&gt;
&lt;h2 id=&quot;what-this-does-not-claim&quot;&gt;What this does not claim&lt;/h2&gt;
&lt;p&gt;Nothing here says the as-of model is good. At 0.287 against a base rate of 0.187
it is a modest signal, which is what two hours of advance notice buys. The claim is
only about the size of the gap between the two encodings, and about where it
hides.&lt;/p&gt;
&lt;h2 id=&quot;the-rest-of-the-work&quot;&gt;The rest of the work&lt;/h2&gt;
&lt;p&gt;This measurement came out of a larger project, and the rest of it is published as a
dashboard. It keeps the same boundary and labels it on every page: Frankfurt and 31 other
European airports are covered by EUROCONTROL’s published daily series, while the per-flight
model is the American one described above. So the dashboard carries Frankfurt’s daily
punctuality since 2022, those 32 airports ranked before and after the network-wide day effect
is removed, and a model card that includes a numbered list of what the model does not tell
you, starting with the fact that it describes no European airport.&lt;/p&gt;
&lt;p&gt;Everything behind it is open bulk data. No account, no API key, no quota, and every figure
traceable to the commit that produced it.&lt;/p&gt;
&lt;p&gt;→ &lt;strong&gt;&lt;a href=&quot;https://ahmedmaaloul.github.io/transportpredictor/&quot;&gt;Open the TransportPredictor dashboard&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</content:encoded><category>leakage</category><category>evaluation</category><category>aviation</category></item><item><title>A containment metric with no lower bound: auditing retrieval scoring in GraphRAG</title><link>https://ahmedmaaloul.com/research/containment-metric-no-lower-bound/</link><guid isPermaLink="true">https://ahmedmaaloul.com/research/containment-metric-no-lower-bound/</guid><description>A null retriever that returns only entity names scores 100% fact recall under containment scoring. Why that metric has no floor, and a channel-gated fix that costs honest systems nothing.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Retrieval-augmented systems built on knowledge graphs are frequently evaluated
with a &lt;em&gt;containment&lt;/em&gt; rule: a gold passage counts as retrieved once the context
contains the surface form of an entity extracted from it. While building
&lt;a href=&quot;https://github.com/ahmedmaaloul/synapse&quot;&gt;Synapse&lt;/a&gt;, I found that this rule has a
property nobody wants in a metric: &lt;strong&gt;it has no lower bound&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-null-control-result&quot;&gt;The null-control result&lt;/h2&gt;
&lt;p&gt;The cleanest way to show it is a retriever that cannot possibly be doing its
job. On a 14-question internal multi-hop benchmark, I scored a &lt;em&gt;null retriever&lt;/em&gt;
that returns nothing but the graph’s alphabetical entity vocabulary. No
descriptions, no edges, no prose, and no ability to answer any question.&lt;/p&gt;
&lt;p&gt;Under containment scoring, it gets &lt;strong&gt;100% fact recall&lt;/strong&gt;. Under a prose-grounded
rule, which only credits a fact when the context carries actual evidence for it,
the same retriever gets &lt;strong&gt;0%&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;it-replicates-and-it-is-not-one-systems-bug&quot;&gt;It replicates, and it is not one system’s bug&lt;/h2&gt;
&lt;p&gt;The same mechanism shows up on HotpotQA (n = 50). Its Both-gold rule credits a
question only when both of its gold paragraphs are retrieved; a graph-only
retriever scores 100% on it under containment and 0% under prose-grounding, with
93% of its credited hits coming from entity-name blocks rather than retrieved
evidence.&lt;/p&gt;
&lt;p&gt;To rule out an implementation quirk, I ran the same null control against a
second, independently authored GraphRAG system, LightRAG. Same picture: a
zero-evidence retriever scores 100% under containment and 0% under
prose-grounding. The inflation is a property of the metric, not of any one
codebase.&lt;/p&gt;
&lt;h2 id=&quot;what-survives-honest-scoring&quot;&gt;What survives honest scoring&lt;/h2&gt;
&lt;p&gt;Under prose-grounded scoring the knowledge graph’s advantage is real but
budget-neutral: a plain passage baseline matches it while reading 45% of the
context. That is a more modest claim than most GraphRAG evaluations make, and I
think it is the accurate one for setups like this.&lt;/p&gt;
&lt;h2 id=&quot;the-fix&quot;&gt;The fix&lt;/h2&gt;
&lt;p&gt;The paper proposes channel-gated scoring: credit an entity only when it arrives
through a channel that can carry evidence, which costs prose-carrying systems
nothing and removes the free floor entirely. The null-control protocol is
released with the code, so you can run the same audit against your own
retrieval eval before trusting its numbers.&lt;/p&gt;
&lt;p&gt;If you evaluate a GraphRAG system with recall-style metrics, running a null
control is the cheapest sanity check available: if a retriever that returns
only entity names can score, the metric is measuring vocabulary, not retrieval.&lt;/p&gt;</content:encoded><category>graphrag</category><category>evaluation</category><category>retrieval</category></item><item><title>Measuring GraphRAG retrieval in Synapse</title><link>https://ahmedmaaloul.com/research/measuring-graphrag-retrieval-synapse/</link><guid isPermaLink="true">https://ahmedmaaloul.com/research/measuring-graphrag-retrieval-synapse/</guid><description>Eight paraphrased queries against a seeded Neo4j fixture: Hit@1 88%, MRR 0.92. What Synapse&apos;s eval harness measures, and what it deliberately does not claim.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most RAG projects ship without measuring retrieval at all. Synapse ships with a
small harness, &lt;code&gt;backend/eval/&lt;/code&gt;, that turns “it feels grounded” into numbers you
can track across changes. This note documents what it measures and how to read
the results.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;The harness seeds a fixture graph into Neo4j (15 entities, 10 relationships),
runs the production retrieval path (&lt;code&gt;retrieve_subgraph&lt;/code&gt;) for each query, and
scores the entities it returns. Embeddings are computed locally with
&lt;code&gt;fastembed&lt;/code&gt;, so the whole thing runs offline:&lt;/p&gt;
&lt;pre class=&quot;astro-code site&quot; style=&quot;background-color:var(--shiki-background);color:var(--shiki-foreground); overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:var(--shiki-token-function)&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-string)&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-string)&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-string)&quot;&gt; -d&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-string)&quot;&gt; neo4j&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:var(--shiki-token-function)&quot;&gt;make&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-string)&quot;&gt; eval&lt;/span&gt;&lt;span style=&quot;color:var(--shiki-token-comment)&quot;&gt;        # writes backend/eval/results.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The 8 queries are deliberately paraphrased so they share no lexical overlap
with the entity names. “What technology stores data as connected nodes and
edges?” has to reach Neo4j through embedding similarity; keyword search would
miss most of the set. That is the property being tested: hybrid retrieval
(vector and full-text seeds, then 1-hop graph expansion) should survive
paraphrase.&lt;/p&gt;
&lt;h2 id=&quot;results&quot;&gt;Results&lt;/h2&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;/th&gt;&lt;th&gt;Score&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Hit@1&lt;/td&gt;&lt;td&gt;88%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Recall@8&lt;/td&gt;&lt;td&gt;100%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Precision@8&lt;/td&gt;&lt;td&gt;17%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MRR&lt;/td&gt;&lt;td&gt;0.917&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Hit@1 and MRR are the quality signal: for 7 of 8 queries the top-ranked entity
is a correct one, and every query has all of its expected entities somewhere in
the top 8. MRR is the mean reciprocal rank, the average of one divided by the
position of the first correct entity, so 0.917 says the first hit is almost
always the top one.&lt;/p&gt;
&lt;p&gt;Precision@8 looks alarming until you notice it is low by construction. Most
queries have only one or two relevant entities, and the retriever returns 8
candidates because the UI highlights a neighborhood, not a single node.
Returning fewer candidates would raise precision and make the product worse.&lt;/p&gt;
&lt;h2 id=&quot;what-this-does-not-claim&quot;&gt;What this does not claim&lt;/h2&gt;
&lt;p&gt;This is a fixture-sized eval: 8 queries over a 15-entity graph. It catches
regressions in the retrieval path and it validates that paraphrase survives the
trip through embeddings, which is what I need from CI. It says nothing about
retrieval quality on large graphs, noisy extractions, or adversarial queries.&lt;/p&gt;
&lt;p&gt;It also inherits a subtler limitation of entity-level scoring itself, which
turned into its own investigation: a scoring rule that credits entity names can
be gamed by a retriever that returns nothing else. That result, a null-control
audit of containment scoring with a replication on HotpotQA and LightRAG, is
written up separately in
&lt;a href=&quot;https://ahmedmaaloul.com/research/containment-metric-no-lower-bound/&quot;&gt;the paper&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>graphrag</category><category>evaluation</category><category>neo4j</category></item></channel></rss>