889 lines
73 KiB
HTML
889 lines
73 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<style>
|
||
:root { color-scheme: light dark; }
|
||
html, body { margin: 0; }
|
||
img { max-width: 100%; }
|
||
[hidden] { display: none !important; }
|
||
</style>
|
||
<title>Doormile Route Intelligence</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700&family=Source+Sans+3:ital,wght@0,400;0,600;1,400&family=JetBrains+Mono:wght@400;600&display=swap">
|
||
<style>
|
||
:root {
|
||
--paper:#F6F4F5; --surface:#FFFFFF; --surface-2:#EFEBED;
|
||
--ink:#17141A; --ink-2:#453E48; --muted:#6E6670;
|
||
--line:#DCD5D8; --line-2:#C6BCC0;
|
||
--accent:#C01227; --accent-ink:#C01227; --accent-soft:#F6E2E5;
|
||
--alt:#10646F; --alt-soft:#DDEEF0;
|
||
--gold:#8A6206; --gold-soft:#F7ECD4;
|
||
--shadow: 0 1px 2px rgba(23,20,26,.06), 0 8px 24px -16px rgba(23,20,26,.35);
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--paper:#121014; --surface:#1A171C; --surface-2:#221E24;
|
||
--ink:#EFEAEC; --ink-2:#C4BBC0; --muted:#968D94;
|
||
--line:#322C34; --line-2:#443C46;
|
||
--accent:#FF6076; --accent-ink:#FF8090; --accent-soft:#3A1B22;
|
||
--alt:#4FC7D4; --alt-soft:#10333A;
|
||
--gold:#E8B84B; --gold-soft:#3A2E10;
|
||
--shadow: 0 1px 2px rgba(0,0,0,.5), 0 8px 24px -16px rgba(0,0,0,.9);
|
||
}
|
||
}
|
||
:root[data-theme="dark"] {
|
||
--paper:#121014; --surface:#1A171C; --surface-2:#221E24;
|
||
--ink:#EFEAEC; --ink-2:#C4BBC0; --muted:#968D94;
|
||
--line:#322C34; --line-2:#443C46;
|
||
--accent:#FF6076; --accent-ink:#FF8090; --accent-soft:#3A1B22;
|
||
--alt:#4FC7D4; --alt-soft:#10333A;
|
||
--gold:#E8B84B; --gold-soft:#3A2E10;
|
||
--shadow: 0 1px 2px rgba(0,0,0,.5), 0 8px 24px -16px rgba(0,0,0,.9);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
background: var(--paper); color: var(--ink);
|
||
font-family: "Source Sans 3", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||
font-size: 16.5px; line-height: 1.62; margin: 0; padding: 0 24px 96px;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
.wrap { max-width: 1180px; margin: 0 auto; }
|
||
.measure { max-width: 68ch; }
|
||
|
||
header.masthead { padding: 64px 0 40px; border-bottom: 2px solid var(--ink); margin-bottom: 40px; }
|
||
.eyebrow {
|
||
font-family: "JetBrains Mono", ui-monospace, Menlo, monospace;
|
||
font-size: 11.5px; letter-spacing: .14em; text-transform: uppercase;
|
||
color: var(--accent-ink); margin: 0 0 18px;
|
||
}
|
||
h1 {
|
||
font-family: Archivo, "Helvetica Neue", Arial, sans-serif; font-weight: 700;
|
||
font-size: clamp(2.3rem, 5.4vw, 3.6rem); line-height: 1.03;
|
||
letter-spacing: -.03em; text-wrap: balance; margin: 0 0 20px;
|
||
}
|
||
.standfirst { font-size: 1.16rem; color: var(--ink-2); max-width: 62ch; margin: 0; }
|
||
.standfirst strong { color: var(--ink); font-weight: 600; }
|
||
|
||
nav.toc {
|
||
display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 52px;
|
||
}
|
||
nav.toc a {
|
||
font-family: "JetBrains Mono", monospace; font-size: 11.5px; letter-spacing: .05em;
|
||
text-decoration: none; color: var(--ink-2); background: var(--surface);
|
||
border: 1px solid var(--line); border-radius: 3px; padding: 6px 11px;
|
||
}
|
||
nav.toc a:hover, nav.toc a:focus-visible { border-color: var(--accent); color: var(--accent-ink); }
|
||
nav.toc a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||
|
||
section { margin: 0 0 76px; scroll-margin-top: 20px; }
|
||
h2 {
|
||
font-family: Archivo, "Helvetica Neue", Arial, sans-serif; font-weight: 600;
|
||
font-size: 1.62rem; letter-spacing: -.017em; line-height: 1.2;
|
||
text-wrap: balance; margin: 0 0 6px;
|
||
}
|
||
h3 {
|
||
font-family: Archivo, "Helvetica Neue", Arial, sans-serif; font-weight: 600;
|
||
font-size: 1.1rem; letter-spacing: -.01em; margin: 34px 0 8px;
|
||
}
|
||
.sec-head {
|
||
display: flex; align-items: baseline; gap: 16px;
|
||
border-top: 1px solid var(--line); padding-top: 16px; margin-bottom: 18px;
|
||
}
|
||
.sec-num {
|
||
font-family: "JetBrains Mono", monospace; font-size: 12px; font-weight: 600;
|
||
color: var(--accent-ink); padding-top: 6px; flex: none;
|
||
}
|
||
.sec-head .tag {
|
||
font-family: "JetBrains Mono", monospace; font-size: 10.5px; letter-spacing: .1em;
|
||
text-transform: uppercase; padding: 3px 9px; border-radius: 3px; font-weight: 600; flex: none;
|
||
}
|
||
.t-first { background: var(--accent-soft); color: var(--accent-ink); }
|
||
.t-mid { background: var(--gold-soft); color: var(--gold); }
|
||
.t-last { background: var(--alt-soft); color: var(--alt); }
|
||
.t-all { background: var(--surface-2); color: var(--ink-2); }
|
||
|
||
p { margin: 0 0 15px; }
|
||
code, .mono { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-size: .86em; }
|
||
p code, li code, td code {
|
||
background: var(--surface-2); border: 1px solid var(--line);
|
||
border-radius: 3px; padding: .07em .34em; color: var(--ink);
|
||
}
|
||
|
||
figure {
|
||
margin: 26px 0 32px; background: var(--surface); border: 1px solid var(--line);
|
||
border-radius: 4px; box-shadow: var(--shadow); overflow: hidden;
|
||
}
|
||
.fig-scroll { overflow-x: auto; padding: 22px 22px 6px; }
|
||
figure svg { display: block; max-width: 100%; height: auto; min-width: 700px; margin: 0 auto; }
|
||
figcaption {
|
||
border-top: 1px solid var(--line); background: var(--surface-2);
|
||
padding: 12px 22px 14px; font-size: 14px; color: var(--ink-2); line-height: 1.5;
|
||
}
|
||
figcaption b {
|
||
font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: .1em;
|
||
text-transform: uppercase; color: var(--accent-ink); display: block;
|
||
margin-bottom: 4px; font-weight: 600;
|
||
}
|
||
|
||
.table-scroll { overflow-x: auto; margin: 24px 0; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); }
|
||
table { border-collapse: collapse; width: 100%; font-size: 14.6px; min-width: 660px; }
|
||
th, td { text-align: left; padding: 11px 16px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
||
thead th {
|
||
font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: .1em;
|
||
text-transform: uppercase; color: var(--muted); font-weight: 600; background: var(--surface-2);
|
||
}
|
||
tbody tr:last-child td { border-bottom: 0; }
|
||
td.num { font-family: "JetBrains Mono", monospace; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
||
|
||
.callout {
|
||
border-left: 3px solid var(--accent); background: var(--accent-soft);
|
||
padding: 16px 20px; border-radius: 0 4px 4px 0; margin: 24px 0; font-size: 15.4px;
|
||
}
|
||
.callout.teal { border-left-color: var(--alt); background: var(--alt-soft); }
|
||
.callout.teal .label { color: var(--alt); }
|
||
.callout.gold { border-left-color: var(--gold); background: var(--gold-soft); }
|
||
.callout.gold .label { color: var(--gold); }
|
||
.callout p:last-child { margin-bottom: 0; }
|
||
.callout .label {
|
||
font-family: "JetBrains Mono", monospace; font-size: 11px; letter-spacing: .1em;
|
||
text-transform: uppercase; font-weight: 600; color: var(--accent-ink);
|
||
display: block; margin-bottom: 5px;
|
||
}
|
||
|
||
ul.plain { padding-left: 1.15em; margin: 12px 0 18px; max-width: 68ch; }
|
||
ul.plain li { margin-bottom: 8px; }
|
||
|
||
footer { border-top: 1px solid var(--line); padding-top: 20px; color: var(--muted); font-size: 13.6px; }
|
||
|
||
@media (max-width: 640px) {
|
||
body { padding: 0 16px 64px; font-size: 16px; }
|
||
header.masthead { padding: 44px 0 30px; }
|
||
}
|
||
</style>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<header class="masthead">
|
||
<p class="eyebrow">Companion to Doormile Parcel Flow</p>
|
||
<h1>Doormile Route Intelligence</h1>
|
||
<p class="standfirst">Each leg of the journey drawn on its own — <strong>first</strong>, <strong>mid</strong>, <strong>last</strong> — with its real states, endpoints and failure branches. Then all three as one chain. Then where an AI-optimised design would actually put its intelligence, which is not where Doormile puts it now.</p>
|
||
</header>
|
||
|
||
<nav class="toc">
|
||
<a href="#first">01 · First mile</a>
|
||
<a href="#mid">02 · Mid mile</a>
|
||
<a href="#last">03 · Last mile</a>
|
||
<a href="#whole">04 · The whole flow</a>
|
||
<a href="#ordering">05 · The ordering mistake</a>
|
||
<a href="#clocks">06 · Three clocks</a>
|
||
<a href="#build">07 · Build order</a>
|
||
</nav>
|
||
|
||
<!-- ================= 01 FIRST MILE ================= -->
|
||
<section id="first">
|
||
<div class="sec-head"><span class="sec-num">01</span><span class="tag t-first">First mile</span><div><h2>Shipper to origin hub</h2></div></div>
|
||
|
||
<p class="measure">Everything from a booking existing to a parcel being in a rider's hands. The happy path runs down the centre; every exception branch on the right is one the backend actually implements.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 960" role="img" aria-label="First mile flow. A booking is created and queued; geo-search finds candidate milers or escalates when none are in radius; the AI layer scores them within five seconds or falls back to greedy nearest; the assignment commits in a transaction and a push notification goes out; the rider accepts or rejects, a rejection reassigning back to the search; on accept the rider proceeds to pickup scheduled, arrived at pickup, and pickup complete, which converts the booking into a consignment collected by miler.">
|
||
<defs>
|
||
<marker id="f1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
<marker id="f1a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/></marker>
|
||
</defs>
|
||
<g font-family="'Source Sans 3', sans-serif" color="var(--line-2)">
|
||
|
||
<text x="300" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--muted)">HAPPY PATH</text>
|
||
<text x="880" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--accent-ink)">EXCEPTIONS</text>
|
||
<line x1="660" y1="34" x2="660" y2="930" stroke="var(--line)" stroke-width="1" stroke-dasharray="3 4"/>
|
||
|
||
<!-- 1 -->
|
||
<rect x="140" y="40" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="64" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Booking created</text>
|
||
<text x="300" y="83" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Created | Pending_Pickup</text>
|
||
<line x1="300" y1="98" x2="300" y2="126" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 2 -->
|
||
<rect x="140" y="128" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="152" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">NATS queue → TryAssignOnce</text>
|
||
<text x="300" y="171" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">60 s worker budget</text>
|
||
<line x1="300" y1="186" x2="300" y2="214" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 3 decision -->
|
||
<rect x="140" y="216" width="320" height="58" rx="29" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="240" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">GEOSEARCH — any candidates?</text>
|
||
<text x="300" y="259" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">geoRadiusKm 10 · geoMaxCount 10</text>
|
||
|
||
<path d="M460,245 H700" fill="none" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#f1a)"/>
|
||
<text x="580" y="238" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">none in radius</text>
|
||
<rect x="704" y="218" width="372" height="54" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="890" y="240" text-anchor="middle" font-size="12" font-weight="600" fill="var(--accent-ink)">Escalated → manual assignment</text>
|
||
<text x="890" y="258" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--accent-ink)">/admin/bookings/:id/assign-miler</text>
|
||
|
||
<line x1="300" y1="274" x2="300" y2="302" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 4 -->
|
||
<rect x="140" y="304" width="320" height="58" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="300" y="328" text-anchor="middle" font-size="13" font-weight="600" fill="var(--accent-ink)">AI layer scores candidates</text>
|
||
<text x="300" y="347" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--accent-ink)">routemate.workolik.com · 5 s cap</text>
|
||
|
||
<path d="M460,333 H700" fill="none" stroke="var(--accent)" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#f1a)"/>
|
||
<text x="580" y="326" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">timeout</text>
|
||
<rect x="704" y="306" width="372" height="54" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="890" y="328" text-anchor="middle" font-size="12" font-weight="600" fill="var(--ink)">Greedy nearest miler</text>
|
||
<text x="890" y="346" text-anchor="middle" font-size="10.5" fill="var(--muted)">deterministic fallback, never blocks</text>
|
||
|
||
<line x1="300" y1="362" x2="300" y2="390" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 5 -->
|
||
<rect x="140" y="392" width="320" height="76" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="416" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">commitAssignment — one transaction</text>
|
||
<text x="300" y="435" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">booking → Miler_Assigned</text>
|
||
<text x="300" y="451" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">assignment → Assigned · miler → Assigned</text>
|
||
|
||
<path d="M460,430 H700" fill="none" stroke="var(--muted)" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#f1)"/>
|
||
<rect x="704" y="404" width="372" height="52" rx="3" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="890" y="426" text-anchor="middle" font-size="12" fill="var(--ink)">AgentDecision row written</text>
|
||
<text x="890" y="444" text-anchor="middle" font-size="10.5" fill="var(--muted)">reasoning + candidate count, for audit</text>
|
||
|
||
<line x1="300" y1="468" x2="300" y2="496" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 6 -->
|
||
<rect x="140" y="498" width="320" height="52" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="529" text-anchor="middle" font-size="13" fill="var(--ink)">push notification to rider</text>
|
||
<line x1="300" y1="550" x2="300" y2="578" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 7 decision -->
|
||
<rect x="140" y="580" width="320" height="52" rx="26" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="611" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">rider accepts?</text>
|
||
|
||
<path d="M460,606 H700" fill="none" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#f1a)"/>
|
||
<text x="580" y="599" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">reject</text>
|
||
<rect x="704" y="580" width="372" height="52" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="890" y="602" text-anchor="middle" font-size="12" font-weight="600" fill="var(--accent-ink)">Rejected → Reassigned</text>
|
||
<text x="890" y="620" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">back into the search</text>
|
||
<path d="M1076,606 H1100 V245 H1080" fill="none" stroke="var(--accent)" stroke-width="1.3" stroke-dasharray="5 4" marker-end="url(#f1a)"/>
|
||
|
||
<line x1="300" y1="632" x2="300" y2="660" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 8 -->
|
||
<rect x="140" y="662" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="686" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Accept</text>
|
||
<text x="300" y="705" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Pickup_Scheduled · miler On_Pickup</text>
|
||
<line x1="300" y1="720" x2="300" y2="748" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 9 -->
|
||
<rect x="140" y="750" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="774" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Reached doorstep</text>
|
||
<text x="300" y="793" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Arrived_At_Pickup · arrivedat + GPS</text>
|
||
<line x1="300" y1="808" x2="300" y2="836" stroke="currentColor" stroke-width="1.4" marker-end="url(#f1)"/>
|
||
|
||
<!-- 10 -->
|
||
<rect x="140" y="838" width="320" height="76" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="2"/>
|
||
<text x="300" y="862" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Pickup complete</text>
|
||
<text x="300" y="881" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Picked_Up → Converted_To_Consignment</text>
|
||
<text x="300" y="897" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">COD collected · Idempotency-Key</text>
|
||
|
||
<path d="M460,876 H700" fill="none" stroke="var(--muted)" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#f1)"/>
|
||
<rect x="704" y="838" width="372" height="76" rx="3" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="890" y="862" text-anchor="middle" font-size="12" font-weight="600" fill="var(--ink)">Consignment issued</text>
|
||
<text x="890" y="881" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Collected_By_Miler · trackingno</text>
|
||
<text x="890" y="897" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">+ 6-digit OTP to receiver</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 1 — first mile</b>Every branch here is real: escalation on an empty radius, the five-second greedy fallback, rejection looping back into the search, and the idempotency key that stops a retried <code>pickup-complete</code> collecting COD twice.</figcaption>
|
||
</figure>
|
||
|
||
<p class="measure">Two details worth holding onto. <code>ALREADY_PICKED_UP</code> and <code>INVALID_STATE</code> are stable machine-readable codes the rider app branches on, so the state machine is enforced server-side rather than trusted from the client. And <code>commitAssignment</code> writes booking, assignment and miler availability in a single transaction — there is no window where a rider is holding a job the booking does not know about.</p>
|
||
</section>
|
||
|
||
<!-- ================= 02 MID MILE ================= -->
|
||
<section id="mid">
|
||
<div class="sec-head"><span class="sec-num">02</span><span class="tag t-mid">Mid mile</span><div><h2>Hub to hub</h2></div></div>
|
||
|
||
<p class="measure">The line-haul leg. This is the only one with no automation at all — every decision below is made by a person at a screen.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 900" role="img" aria-label="Mid mile flow. The consignment is inbound-scanned at the origin hub with a condition recorded and a shelf assigned, becoming inwarded at hub; damaged condition diverts to a hold. If the destination hub equals the current hub the parcel skips straight to last mile. Otherwise a tripsheet is created as draft, items are scanned and loaded with discrepancies flagged, a vehicle and driver are attached to make it ready, dispatch puts it in transit, and arrival unloads it. If the destination is still not reached the parcel is re-inwarded and joins the next tripsheet.">
|
||
<defs>
|
||
<marker id="f2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
<marker id="f2g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--gold)"/></marker>
|
||
<marker id="f2t" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--alt)"/></marker>
|
||
</defs>
|
||
<g font-family="'Source Sans 3', sans-serif" color="var(--line-2)">
|
||
|
||
<text x="300" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--muted)">LINE-HAUL PATH</text>
|
||
<text x="880" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--gold)">BRANCHES</text>
|
||
<line x1="660" y1="34" x2="660" y2="870" stroke="var(--line)" stroke-width="1" stroke-dasharray="3 4"/>
|
||
|
||
<!-- 1 -->
|
||
<rect x="140" y="40" width="320" height="52" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="71" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Parcel arrives at origin hub</text>
|
||
<line x1="300" y1="92" x2="300" y2="120" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 2 -->
|
||
<rect x="140" y="122" width="320" height="76" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="146" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Inbound barcode scan</text>
|
||
<text x="300" y="165" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">shelf assigned · Condition recorded</text>
|
||
<text x="300" y="181" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">→ Inwarded_at_Hub</text>
|
||
|
||
<path d="M460,160 H700" fill="none" stroke="var(--gold)" stroke-width="1.4" marker-end="url(#f2g)"/>
|
||
<text x="580" y="153" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--gold)">Condition ≠ Good</text>
|
||
<rect x="704" y="134" width="372" height="52" rx="3" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="890" y="156" text-anchor="middle" font-size="12" font-weight="600" fill="var(--gold)">Damaged — held at hub</text>
|
||
<text x="890" y="174" text-anchor="middle" font-size="10.5" fill="var(--gold)">exception queue, not loaded</text>
|
||
|
||
<line x1="300" y1="198" x2="300" y2="226" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 3 decision -->
|
||
<rect x="140" y="228" width="320" height="58" rx="29" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="252" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">destination hub = this hub?</text>
|
||
<text x="300" y="271" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">currenthubid vs destinationhubid</text>
|
||
|
||
<path d="M460,257 H700" fill="none" stroke="var(--alt)" stroke-width="1.4" marker-end="url(#f2t)"/>
|
||
<text x="580" y="250" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--alt)">yes</text>
|
||
<rect x="704" y="230" width="372" height="54" rx="3" fill="var(--alt-soft)" stroke="var(--alt)"/>
|
||
<text x="890" y="252" text-anchor="middle" font-size="12" font-weight="600" fill="var(--alt)">mid mile skipped</text>
|
||
<text x="890" y="270" text-anchor="middle" font-size="10.5" fill="var(--alt)">straight to last-mile assignment</text>
|
||
|
||
<line x1="300" y1="286" x2="300" y2="314" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
<text x="316" y="304" font-size="10.5" font-weight="600" fill="var(--muted)">no</text>
|
||
|
||
<!-- 4 -->
|
||
<rect x="140" y="316" width="320" height="76" rx="3" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="300" y="340" text-anchor="middle" font-size="13" font-weight="600" fill="var(--gold)">Tripsheet created by hand</text>
|
||
<text x="300" y="359" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--gold)">status Draft · source + destination hub</text>
|
||
<text x="300" y="375" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--gold)">Batchkind: local | transfer</text>
|
||
<line x1="300" y1="392" x2="300" y2="420" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 5 -->
|
||
<rect x="140" y="422" width="320" height="76" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="446" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Items scanned onto the sheet</text>
|
||
<text x="300" y="465" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">item Pending → Loaded</text>
|
||
<text x="300" y="481" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">consignment → Tripsheet_Loaded</text>
|
||
|
||
<path d="M460,460 H700" fill="none" stroke="var(--gold)" stroke-width="1.4" marker-end="url(#f2g)"/>
|
||
<text x="580" y="453" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--gold)">mismatch</text>
|
||
<rect x="704" y="434" width="372" height="52" rx="3" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="890" y="456" text-anchor="middle" font-size="12" font-weight="600" fill="var(--gold)">Discrepancy</text>
|
||
<text x="890" y="474" text-anchor="middle" font-size="10.5" fill="var(--gold)">scanned but not on the manifest, or missing</text>
|
||
|
||
<line x1="300" y1="498" x2="300" y2="526" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 6 -->
|
||
<rect x="140" y="528" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="552" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Vehicle + driver attached</text>
|
||
<text x="300" y="571" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">status → Ready</text>
|
||
<line x1="300" y1="586" x2="300" y2="614" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 7 -->
|
||
<rect x="140" y="616" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="640" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Dispatch</text>
|
||
<text x="300" y="659" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Dispatched · dispatchtime · → In_Transit</text>
|
||
|
||
<path d="M460,645 H700" fill="none" stroke="var(--gold)" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#f2g)"/>
|
||
<rect x="704" y="618" width="372" height="54" rx="3" fill="var(--surface)" stroke="var(--gold)" stroke-dasharray="4 3"/>
|
||
<text x="890" y="640" text-anchor="middle" font-size="12" font-weight="600" fill="var(--gold)">hold-vs-go decided by guess</text>
|
||
<text x="890" y="658" text-anchor="middle" font-size="10.5" fill="var(--gold)">depart at 80% fill, or wait for 95%?</text>
|
||
|
||
<line x1="300" y1="674" x2="300" y2="702" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 8 -->
|
||
<rect x="140" y="704" width="320" height="76" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="728" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Arrival scan & offload</text>
|
||
<text x="300" y="747" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Arrived · arrivaltime · items Unloaded</text>
|
||
<text x="300" y="763" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">currenthubid updated</text>
|
||
<line x1="300" y1="780" x2="300" y2="808" stroke="currentColor" stroke-width="1.4" marker-end="url(#f2)"/>
|
||
|
||
<!-- 9 decision -->
|
||
<rect x="140" y="810" width="320" height="58" rx="29" fill="var(--surface)" stroke="var(--ink)" stroke-width="2"/>
|
||
<text x="300" y="834" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">final destination reached?</text>
|
||
<text x="300" y="853" text-anchor="middle" font-size="10.5" fill="var(--muted)">yes → last mile</text>
|
||
|
||
<path d="M140,839 H80 V353 H136" fill="none" stroke="var(--gold)" stroke-width="1.4" stroke-dasharray="5 4" marker-end="url(#f2g)"/>
|
||
<text x="66" y="600" font-size="10.5" font-weight="600" fill="var(--gold)" transform="rotate(-90 66 600)">no — re-inward, next leg</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 2 — mid mile</b>Note the loop on the left: a parcel can ride more than one tripsheet. Nothing decides which parcels board which truck, when it should leave, or whether the lane should exist — those are the three open decisions on this leg.</figcaption>
|
||
</figure>
|
||
|
||
<div class="callout gold">
|
||
<span class="label">The unpriced decision</span>
|
||
<p>Dispatch timing is the central mid-mile trade: the marginal cost of running an extra vehicle against the SLA-breach risk of the parcels you hold back. Both sides are computable — you already store <code>sladueat</code> on every consignment. Today it is a dispatcher's instinct.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 03 LAST MILE ================= -->
|
||
<section id="last">
|
||
<div class="sec-head"><span class="sec-num">03</span><span class="tag t-last">Last mile</span><div><h2>Destination hub to consignee</h2></div></div>
|
||
|
||
<p class="measure">The only leg with real road-network routing — and the only one with a retry loop, because a delivery can fail in a way a pickup cannot.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 900" role="img" aria-label="Last mile flow. A rider is assigned by one of three routes: manual, synchronous auto-assign, or greedy batch assign. Stops are then sequenced against a Valhalla road network. The rider starts delivery, moving the consignment to out for delivery, reaches the consignee, and either verifies the six-digit OTP and files a delivery proof to reach delivered, or skips, incrementing the attempt count and returning to the queue. Once attempts are exhausted the consignment goes to RTO initiated and then returned to sender.">
|
||
<defs>
|
||
<marker id="f3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
<marker id="f3a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/></marker>
|
||
<marker id="f3t" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--alt)"/></marker>
|
||
</defs>
|
||
<g font-family="'Source Sans 3', sans-serif" color="var(--line-2)">
|
||
|
||
<text x="300" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--muted)">DELIVERY PATH</text>
|
||
<text x="880" y="20" text-anchor="middle" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".1em" fill="var(--accent-ink)">FAILURE PATH</text>
|
||
<line x1="660" y1="34" x2="660" y2="870" stroke="var(--line)" stroke-width="1" stroke-dasharray="3 4"/>
|
||
|
||
<!-- 1: three assignment doors -->
|
||
<rect x="140" y="40" width="100" height="46" rx="3" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="190" y="61" text-anchor="middle" font-size="11" fill="var(--ink)">manual</text>
|
||
<text x="190" y="76" text-anchor="middle" font-size="9.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">assign-miler</text>
|
||
<rect x="250" y="40" width="100" height="46" rx="3" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="300" y="61" text-anchor="middle" font-size="11" fill="var(--ink)">sync AI</text>
|
||
<text x="300" y="76" text-anchor="middle" font-size="9.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">auto-assign</text>
|
||
<rect x="360" y="40" width="100" height="46" rx="3" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="410" y="61" text-anchor="middle" font-size="11" fill="var(--ink)">greedy</text>
|
||
<text x="410" y="76" text-anchor="middle" font-size="9.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">batch-assign</text>
|
||
|
||
<path d="M190,86 V104 H300" fill="none" stroke="currentColor" stroke-width="1.3"/>
|
||
<path d="M410,86 V104 H300" fill="none" stroke="currentColor" stroke-width="1.3"/>
|
||
<line x1="300" y1="86" x2="300" y2="104" stroke="currentColor" stroke-width="1.3"/>
|
||
<line x1="300" y1="104" x2="300" y2="126" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 2 -->
|
||
<rect x="140" y="128" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="152" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Rider assigned</text>
|
||
<text x="300" y="171" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">assignment Assigned · miler On_Delivery</text>
|
||
<line x1="300" y1="186" x2="300" y2="214" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 3 -->
|
||
<rect x="140" y="216" width="320" height="76" rx="3" fill="var(--alt-soft)" stroke="var(--alt)"/>
|
||
<text x="300" y="240" text-anchor="middle" font-size="13" font-weight="600" fill="var(--alt)">SequenceMilerStops → Valhalla</text>
|
||
<text x="300" y="259" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)">writes step · previouskms · cumulativeeta</text>
|
||
<text x="300" y="275" text-anchor="middle" font-size="10.5" fill="var(--alt)">async, best-effort, needs ≥ 2 stops</text>
|
||
|
||
<path d="M460,254 H700" fill="none" stroke="var(--alt)" stroke-width="1.3" stroke-dasharray="4 3" marker-end="url(#f3t)"/>
|
||
<rect x="704" y="228" width="372" height="52" rx="3" fill="var(--surface)" stroke="var(--alt)" stroke-dasharray="4 3"/>
|
||
<text x="890" y="250" text-anchor="middle" font-size="12" fill="var(--alt)">unconfigured → stops stay unsequenced</text>
|
||
<text x="890" y="268" text-anchor="middle" font-size="10.5" fill="var(--muted)">assignment never fails because of this</text>
|
||
|
||
<line x1="300" y1="292" x2="300" y2="320" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 4 -->
|
||
<rect x="140" y="322" width="320" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="346" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Start delivery</text>
|
||
<text x="300" y="365" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">→ Out_for_Delivery</text>
|
||
<line x1="300" y1="380" x2="300" y2="408" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 5 -->
|
||
<rect x="140" y="410" width="320" height="52" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="300" y="441" text-anchor="middle" font-size="13" fill="var(--ink)">rider reaches the consignee</text>
|
||
<line x1="300" y1="462" x2="300" y2="490" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 6 decision -->
|
||
<rect x="140" y="492" width="320" height="58" rx="29" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="516" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">receiver present, OTP correct?</text>
|
||
<text x="300" y="535" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">OTP_REQUIRED | OTP_INVALID</text>
|
||
|
||
<path d="M460,521 H700" fill="none" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#f3a)"/>
|
||
<text x="580" y="514" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">no</text>
|
||
<rect x="704" y="494" width="372" height="58" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="890" y="518" text-anchor="middle" font-size="12" font-weight="600" fill="var(--accent-ink)">Skip — failed attempt</text>
|
||
<text x="890" y="537" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--accent-ink)">Attemptcount + 1 · reason logged</text>
|
||
|
||
<!-- retry loop -->
|
||
<path d="M1076,523 H1100 V152 H1080" fill="none" stroke="var(--accent)" stroke-width="1.3" stroke-dasharray="5 4" marker-end="url(#f3a)"/>
|
||
<text x="1114" y="330" font-size="10.5" font-weight="600" fill="var(--accent-ink)" transform="rotate(90 1114 330)">re-queue for next attempt</text>
|
||
|
||
<line x1="300" y1="550" x2="300" y2="578" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
<text x="316" y="570" font-size="10.5" font-weight="600" fill="var(--muted)">yes</text>
|
||
|
||
<!-- 7 -->
|
||
<rect x="140" y="580" width="320" height="94" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="300" y="604" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">Deliver</text>
|
||
<text x="300" y="623" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">OTP verified · Idempotency-Key</text>
|
||
<text x="300" y="639" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">signature + photo via presigned URL</text>
|
||
<text x="300" y="659" text-anchor="middle" font-size="10.5" fill="var(--muted)">DeliveryProof row written</text>
|
||
|
||
<!-- attempts exhausted -->
|
||
<path d="M890,552 V596 H1000" fill="none" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#f3a)"/>
|
||
<text x="778" y="578" font-size="10.5" font-weight="600" fill="var(--accent-ink)">attempts exhausted</text>
|
||
<rect x="704" y="612" width="372" height="52" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="890" y="634" text-anchor="middle" font-size="12" font-weight="600" fill="var(--accent-ink)">RTO_Initiated</text>
|
||
<text x="890" y="652" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--accent-ink)">Returnreason · Parentconsignmentid</text>
|
||
<line x1="890" y1="664" x2="890" y2="692" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#f3a)"/>
|
||
<rect x="704" y="694" width="372" height="52" rx="3" fill="var(--surface-2)" stroke="var(--accent)" stroke-width="1.5"/>
|
||
<text x="890" y="716" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--accent-ink)">Returned_to_Sender</text>
|
||
<text x="890" y="734" text-anchor="middle" font-size="10.5" fill="var(--muted)">the return leg is itself a full journey</text>
|
||
|
||
<line x1="300" y1="674" x2="300" y2="702" stroke="currentColor" stroke-width="1.4" marker-end="url(#f3)"/>
|
||
|
||
<!-- 8 -->
|
||
<rect x="140" y="704" width="320" height="76" rx="3" fill="var(--alt-soft)" stroke="var(--alt)" stroke-width="2"/>
|
||
<text x="300" y="730" text-anchor="middle" font-size="14" font-weight="600" fill="var(--alt)">Delivered</text>
|
||
<text x="300" y="750" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)">Codcollected reconciled</text>
|
||
<text x="300" y="766" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)">Billingstatus → Billed</text>
|
||
|
||
<!-- other terminals -->
|
||
<rect x="140" y="812" width="320" height="52" rx="3" fill="var(--surface)" stroke="var(--line-2)" stroke-dasharray="4 3"/>
|
||
<text x="300" y="834" text-anchor="middle" font-size="11.5" font-weight="600" fill="var(--muted)">other terminal states</text>
|
||
<text x="300" y="852" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">Missing · Damaged · Cancelled</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 3 — last mile</b><code>Attemptcount</code> and the skip reason are already being recorded on every failure. That is a labelled training set for first-attempt success prediction, sitting unused.</figcaption>
|
||
</figure>
|
||
|
||
<div class="callout teal">
|
||
<span class="label">Where the money leaks</span>
|
||
<p>Every trip around that retry loop is a second full delivery run for one parcel. First-attempt delivery rate is the dominant cost line in Indian last-mile, and the loop above is currently entered on discovery rather than predicted in advance.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 04 WHOLE ================= -->
|
||
<section id="whole">
|
||
<div class="sec-head"><span class="sec-num">04</span><span class="tag t-all">All three</span><div><h2>The whole flow</h2></div></div>
|
||
|
||
<p class="measure">Every status a parcel can hold, in the order it holds them, colour-coded by leg. Read it as a snake: left to right, drop, right to left, drop, left to right.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 620" role="img" aria-label="The complete status chain as a snake. Row one, first mile, left to right: created or pending pickup, miler assigned, pickup scheduled, arrived at pickup, picked up, converted to consignment. Row two, mid mile, right to left: collected by miler, inwarded at hub, tripsheet loaded, in transit, with a loop back to inwarded at hub for multi-leg journeys. Row three, last mile, left to right: out for delivery then delivered. A hyperlocal bypass curve jumps from converted to consignment straight to out for delivery. Failure terminals branch off to RTO initiated and returned to sender, with missing, damaged and cancelled shown separately.">
|
||
<defs>
|
||
<marker id="w1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
<marker id="w1t" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--alt)"/></marker>
|
||
<marker id="w1a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/></marker>
|
||
<marker id="w1g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--gold)"/></marker>
|
||
</defs>
|
||
<g font-family="'JetBrains Mono', ui-monospace, monospace" color="var(--line-2)">
|
||
|
||
<!-- ROW 1 : first mile -->
|
||
<text x="20" y="58" font-size="10.5" letter-spacing=".1em" fill="var(--accent-ink)" font-weight="600">FIRST MILE · booking</text>
|
||
<rect x="20" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="97" y="90" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Created |</text>
|
||
<text x="97" y="104" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Pending_Pickup</text>
|
||
<rect x="205" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="282" y="97" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Miler_Assigned</text>
|
||
<rect x="390" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="467" y="97" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Pickup_Scheduled</text>
|
||
<rect x="575" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="652" y="97" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Arrived_At_Pickup</text>
|
||
<rect x="760" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="837" y="97" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Picked_Up</text>
|
||
<rect x="945" y="70" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.8"/>
|
||
<text x="1022" y="90" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Converted_To_</text>
|
||
<text x="1022" y="104" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">Consignment</text>
|
||
|
||
<line x1="179" y1="92" x2="201" y2="92" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="364" y1="92" x2="386" y2="92" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="549" y1="92" x2="571" y2="92" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="734" y1="92" x2="756" y2="92" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="919" y1="92" x2="941" y2="92" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
|
||
<!-- drop right -->
|
||
<path d="M1022,114 V230" fill="none" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
|
||
<!-- ROW 2 : mid mile -->
|
||
<text x="945" y="212" text-anchor="end" font-size="10.5" letter-spacing=".1em" fill="var(--gold)" font-weight="600">MID MILE · consignment</text>
|
||
<rect x="945" y="232" width="155" height="44" rx="6" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="1022" y="252" text-anchor="middle" font-size="10.5" fill="var(--gold)">Collected_By_</text>
|
||
<text x="1022" y="266" text-anchor="middle" font-size="10.5" fill="var(--gold)">Miler</text>
|
||
<rect x="760" y="232" width="155" height="44" rx="6" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="837" y="259" text-anchor="middle" font-size="10.5" fill="var(--gold)">Inwarded_at_Hub</text>
|
||
<rect x="575" y="232" width="155" height="44" rx="6" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="652" y="259" text-anchor="middle" font-size="10.5" fill="var(--gold)">Tripsheet_Loaded</text>
|
||
<rect x="390" y="232" width="155" height="44" rx="6" fill="var(--gold-soft)" stroke="var(--gold)"/>
|
||
<text x="467" y="259" text-anchor="middle" font-size="10.5" fill="var(--gold)">In_Transit</text>
|
||
|
||
<line x1="941" y1="254" x2="919" y2="254" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="756" y1="254" x2="734" y2="254" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<line x1="571" y1="254" x2="549" y2="254" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
|
||
<!-- multi-leg loop -->
|
||
<path d="M467,232 V196 H837 V228" fill="none" stroke="var(--gold)" stroke-width="1.3" stroke-dasharray="5 4" marker-end="url(#w1g)"/>
|
||
<rect x="560" y="182" width="184" height="22" rx="11" fill="var(--surface)" stroke="var(--gold)"/>
|
||
<text x="652" y="197" text-anchor="middle" font-size="10" fill="var(--gold)">next hub · multi-leg</text>
|
||
|
||
<!-- drop left -->
|
||
<path d="M467,276 V392" fill="none" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
|
||
<!-- hyperlocal bypass -->
|
||
<path d="M1022,114 C 1110,150 1110,400 560,400 H500" fill="none" stroke="var(--alt)" stroke-width="1.6" stroke-dasharray="6 4" marker-end="url(#w1t)"/>
|
||
<rect x="742" y="386" width="292" height="24" rx="12" fill="var(--alt-soft)" stroke="var(--alt)"/>
|
||
<text x="888" y="402" text-anchor="middle" font-size="10" fill="var(--alt)">hyperlocal — pincode prefix match</text>
|
||
|
||
<!-- ROW 3 : last mile -->
|
||
<text x="20" y="378" font-size="10.5" letter-spacing=".1em" fill="var(--alt)" font-weight="600">LAST MILE</text>
|
||
<rect x="390" y="394" width="155" height="44" rx="6" fill="var(--alt-soft)" stroke="var(--alt)"/>
|
||
<text x="467" y="421" text-anchor="middle" font-size="10.5" fill="var(--alt)">Out_for_Delivery</text>
|
||
<line x1="549" y1="416" x2="571" y2="416" stroke="currentColor" stroke-width="1.4" marker-end="url(#w1)"/>
|
||
<rect x="575" y="394" width="155" height="44" rx="6" fill="var(--alt-soft)" stroke="var(--alt)" stroke-width="2.4"/>
|
||
<text x="652" y="421" text-anchor="middle" font-size="11" font-weight="600" fill="var(--alt)">Delivered</text>
|
||
|
||
<!-- failure branch -->
|
||
<path d="M467,438 V486 H756" fill="none" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#w1a)"/>
|
||
<text x="560" y="478" font-size="10" fill="var(--accent-ink)" font-weight="600">attempts exhausted</text>
|
||
<rect x="760" y="464" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="837" y="491" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">RTO_Initiated</text>
|
||
<line x1="919" y1="486" x2="941" y2="486" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#w1a)"/>
|
||
<rect x="945" y="464" width="155" height="44" rx="6" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="2.4"/>
|
||
<text x="1022" y="484" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">Returned_to_</text>
|
||
<text x="1022" y="498" text-anchor="middle" font-size="10.5" font-weight="600" fill="var(--accent-ink)">Sender</text>
|
||
|
||
<!-- skip loop -->
|
||
<path d="M467,438 C 360,470 360,400 386,416" fill="none" stroke="var(--accent)" stroke-width="1.3" stroke-dasharray="5 4" marker-end="url(#w1a)"/>
|
||
<text x="240" y="452" font-size="10" fill="var(--accent-ink)" font-weight="600">skip → Attemptcount + 1</text>
|
||
|
||
<!-- exception terminals -->
|
||
<line x1="20" y1="546" x2="1100" y2="546" stroke="var(--line)" stroke-width="1"/>
|
||
<text x="20" y="576" font-size="10.5" letter-spacing=".1em" fill="var(--muted)" font-weight="600">EXCEPTION TERMINALS · reachable from any leg</text>
|
||
<rect x="440" y="560" width="120" height="30" rx="6" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="500" y="580" text-anchor="middle" font-size="10.5" fill="var(--ink)">Missing</text>
|
||
<rect x="576" y="560" width="120" height="30" rx="6" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="636" y="580" text-anchor="middle" font-size="10.5" fill="var(--ink)">Damaged</text>
|
||
<rect x="712" y="560" width="120" height="30" rx="6" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="772" y="580" text-anchor="middle" font-size="10.5" fill="var(--ink)">Cancelled</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 4 — the complete chain</b>Two entities, one chain: the first row is a <code>PickupBooking</code>, everything after <code>Converted_To_Consignment</code> is a <code>Consignment</code>. The teal bypass and the gold loop are the two places a parcel's path stops being linear.</figcaption>
|
||
</figure>
|
||
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead><tr><th>Leg</th><th>Entity</th><th>Ends when</th><th>Loops</th><th>Optimised today by</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>First</td><td><code>PickupBooking</code></td><td>parcel in rider's hands</td><td>reject → reassign</td><td>per-parcel geo-search + LLM pick</td></tr>
|
||
<tr><td>Mid</td><td><code>Consignment</code></td><td>at destination hub</td><td>multi-leg re-inward</td><td>nothing — hand-built tripsheets</td></tr>
|
||
<tr><td>Last</td><td><code>Consignment</code></td><td>OTP verified, proof filed</td><td>skip → next attempt</td><td>Valhalla sequencing, post-assignment</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 05 ORDERING ================= -->
|
||
<section id="ordering">
|
||
<div class="sec-head"><span class="sec-num">05</span><div><h2>The ordering mistake</h2></div></div>
|
||
|
||
<p class="measure">This is the most consequential thing in the codebase, and it is a sequencing question about the software rather than the parcels. Doormile <strong>assigns first and sequences second</strong>. Those are not two problems — they are one problem, and solving them in series throws away most of the available gain.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 430" role="img" aria-label="Top: today's pipeline runs one parcel at a time through geo-search, an LLM choice, a committed assignment, and only then road sequencing of whatever stops that rider was given. Bottom: the proposed pipeline accumulates a batch of parcels and available riders with their constraints, and a single vehicle-routing solver assigns and sequences together, leaving the LLM to handle exceptions only.">
|
||
<defs>
|
||
<marker id="b" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
<marker id="bt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="var(--alt)"/></marker>
|
||
</defs>
|
||
<g font-family="'Source Sans 3', sans-serif" color="var(--line-2)">
|
||
<text x="0" y="22" font-family="'JetBrains Mono', monospace" font-size="11" font-weight="600" letter-spacing="1.1" fill="var(--accent-ink)">TODAY · ASSIGN, THEN SEQUENCE</text>
|
||
|
||
<rect x="0" y="46" width="150" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="75" y="72" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--ink)">one parcel</text>
|
||
<text x="75" y="89" text-anchor="middle" font-size="10.5" fill="var(--muted)">as it arrives</text>
|
||
<line x1="154" y1="75" x2="192" y2="75" stroke="currentColor" stroke-width="1.4" marker-end="url(#b)"/>
|
||
|
||
<rect x="196" y="46" width="184" height="58" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="288" y="70" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--ink)">GEOSEARCH</text>
|
||
<text x="288" y="88" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">10 km · top 10</text>
|
||
<line x1="384" y1="75" x2="422" y2="75" stroke="currentColor" stroke-width="1.4" marker-end="url(#b)"/>
|
||
|
||
<rect x="426" y="46" width="184" height="58" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="518" y="70" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--accent-ink)">LLM picks one</text>
|
||
<text x="518" y="88" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">5 s cap</text>
|
||
<line x1="614" y1="75" x2="652" y2="75" stroke="currentColor" stroke-width="1.4" marker-end="url(#b)"/>
|
||
|
||
<rect x="656" y="46" width="160" height="58" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="736" y="70" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--ink)">assignment</text>
|
||
<text x="736" y="88" text-anchor="middle" font-size="10.5" fill="var(--muted)">committed</text>
|
||
<line x1="820" y1="75" x2="858" y2="75" stroke="currentColor" stroke-width="1.4" marker-end="url(#b)"/>
|
||
|
||
<rect x="862" y="46" width="214" height="58" rx="3" fill="var(--surface)" stroke="var(--line-2)" stroke-dasharray="4 3"/>
|
||
<text x="969" y="70" text-anchor="middle" font-size="12.5" font-weight="600" fill="var(--ink)">Valhalla sequences</text>
|
||
<text x="969" y="88" text-anchor="middle" font-size="10.5" fill="var(--muted)">that rider's stops, async</text>
|
||
|
||
<path d="M969,108 V138 H518 V108" fill="none" stroke="var(--accent)" stroke-width="1.3" stroke-dasharray="5 4"/>
|
||
<rect x="600" y="126" width="330" height="24" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="765" y="143" text-anchor="middle" font-size="11" font-weight="600" fill="var(--accent-ink)">can only reorder a set it did not get to choose</text>
|
||
|
||
<line x1="0" y1="196" x2="1120" y2="196" stroke="var(--line)" stroke-width="1"/>
|
||
|
||
<text x="0" y="232" font-family="'JetBrains Mono', monospace" font-size="11" font-weight="600" letter-spacing="1.1" fill="var(--alt)">PROPOSED · ONE JOINT SOLVE</text>
|
||
|
||
<rect x="0" y="256" width="200" height="42" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="100" y="283" text-anchor="middle" font-size="12" fill="var(--ink)">N parcels · batch window</text>
|
||
<rect x="0" y="306" width="200" height="42" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="100" y="333" text-anchor="middle" font-size="12" fill="var(--ink)">M riders · live capacity</text>
|
||
<rect x="0" y="356" width="200" height="42" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<text x="100" y="376" text-anchor="middle" font-size="12" fill="var(--ink)">constraints</text>
|
||
<text x="100" y="391" text-anchor="middle" font-size="10" fill="var(--muted)">slots · capacity · skills</text>
|
||
|
||
<path d="M204,277 H244 V327" fill="none" stroke="var(--alt)" stroke-width="1.4"/>
|
||
<path d="M204,327 H244" fill="none" stroke="var(--alt)" stroke-width="1.4"/>
|
||
<path d="M204,377 H244 V327" fill="none" stroke="var(--alt)" stroke-width="1.4"/>
|
||
<line x1="244" y1="327" x2="288" y2="327" stroke="var(--alt)" stroke-width="1.5" marker-end="url(#bt)"/>
|
||
|
||
<rect x="292" y="272" width="270" height="110" rx="3" fill="var(--alt-soft)" stroke="var(--alt)" stroke-width="1.6"/>
|
||
<text x="427" y="308" text-anchor="middle" font-size="14" font-weight="600" fill="var(--alt)">VRP solver</text>
|
||
<text x="427" y="329" text-anchor="middle" font-size="11.5" fill="var(--alt)">assignment and order</text>
|
||
<text x="427" y="346" text-anchor="middle" font-size="11.5" fill="var(--alt)">decided together</text>
|
||
<text x="427" y="367" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)">OR-Tools / VROOM · ms</text>
|
||
|
||
<line x1="566" y1="327" x2="606" y2="327" stroke="var(--alt)" stroke-width="1.5" marker-end="url(#bt)"/>
|
||
|
||
<rect x="610" y="272" width="200" height="110" rx="3" fill="var(--surface-2)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="710" y="316" text-anchor="middle" font-size="13" font-weight="600" fill="var(--ink)">complete routes</text>
|
||
<text x="710" y="336" text-anchor="middle" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--muted)">rider · step · ETA</text>
|
||
|
||
<line x1="814" y1="327" x2="854" y2="327" stroke="currentColor" stroke-width="1.4" marker-end="url(#b)"/>
|
||
|
||
<rect x="858" y="272" width="218" height="110" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<text x="967" y="306" text-anchor="middle" font-size="13" font-weight="600" fill="var(--accent-ink)">LLM</text>
|
||
<text x="967" y="327" text-anchor="middle" font-size="11.5" fill="var(--accent-ink)">exceptions only</text>
|
||
<text x="967" y="345" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">overflow · absence ·</text>
|
||
<text x="967" y="361" text-anchor="middle" font-size="10.5" fill="var(--accent-ink)">bad address · breakdown</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 5 — the fix is an ordering change</b>The Valhalla client already exists and already speaks Doormile's vocabulary. Moving it in front of assignment, and feeding it the whole open set rather than one rider's inherited stops, is a smaller change than it sounds.</figcaption>
|
||
</figure>
|
||
|
||
<div class="callout">
|
||
<span class="label">Why this matters more than model quality</span>
|
||
<p>Greedy nearest-neighbour assignment is a known-bad heuristic for vehicle routing — on realistic instances it lands well short of what a proper solver finds on the same data, and no amount of smarter per-parcel scoring recovers the gap, because the loss comes from committing to each parcel before seeing the rest. A better model choosing one rider at a time is still choosing one rider at a time.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 06 CLOCKS ================= -->
|
||
<section id="clocks">
|
||
<div class="sec-head"><span class="sec-num">06</span><div><h2>Put the intelligence where it belongs</h2></div></div>
|
||
|
||
<p class="measure">The expert move is to stop treating "AI" as one layer. Logistics decisions live on three clocks, and each clock wants a different technique. Doormile currently has a language model on the second clock, which is the one place it fits worst.</p>
|
||
|
||
<figure>
|
||
<div class="fig-scroll">
|
||
<svg viewBox="0 0 1120 480" role="img" aria-label="Three stacked horizons. Strategic, monthly: beat and territory design, hub and lane network, solved by districting and facility location. Tactical, per wave: load planning, cutoffs, joint assignment and sequencing, solved by a mixed-integer or vehicle-routing solver. Operational, per second: exceptions such as overflow, absence, bad addresses, handled by a language model. A feedback arrow runs from completed-route data back up into all three layers.">
|
||
<defs>
|
||
<marker id="c" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="currentColor"/></marker>
|
||
</defs>
|
||
<g font-family="'Source Sans 3', sans-serif" color="var(--line-2)">
|
||
<text x="0" y="24" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".08em" fill="var(--muted)">HORIZON</text>
|
||
<text x="230" y="24" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".08em" fill="var(--muted)">DECIDES</text>
|
||
<text x="600" y="24" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".08em" fill="var(--muted)">TECHNIQUE</text>
|
||
<text x="960" y="24" font-family="'JetBrains Mono', monospace" font-size="10.5" letter-spacing=".08em" fill="var(--muted)">CADENCE</text>
|
||
|
||
<rect x="0" y="40" width="1000" height="94" rx="3" fill="var(--surface-2)" stroke="var(--line-2)"/>
|
||
<rect x="0" y="40" width="5" height="94" fill="var(--alt)"/>
|
||
<text x="24" y="76" font-size="14" font-weight="600" fill="var(--ink)">Strategic</text>
|
||
<text x="24" y="99" font-size="11" fill="var(--muted)">the shape of the network</text>
|
||
<text x="230" y="72" font-size="12.5" fill="var(--ink)">beat & territory design · hub siting</text>
|
||
<text x="230" y="92" font-size="12.5" fill="var(--ink)">which lanes exist at all</text>
|
||
<text x="230" y="112" font-size="12.5" fill="var(--ink)">rider headcount per zone</text>
|
||
<text x="600" y="82" font-size="12.5" fill="var(--alt)">districting · facility location</text>
|
||
<text x="600" y="102" font-size="12.5" fill="var(--alt)">demand forecasting</text>
|
||
<rect x="940" y="74" width="130" height="26" rx="13" fill="var(--surface)" stroke="var(--line-2)"/>
|
||
<text x="1005" y="92" text-anchor="middle" font-size="11" font-family="'JetBrains Mono', monospace" fill="var(--ink)">monthly</text>
|
||
|
||
<rect x="0" y="146" width="1000" height="114" rx="3" fill="var(--alt-soft)" stroke="var(--alt)" stroke-width="1.6"/>
|
||
<rect x="0" y="146" width="5" height="114" fill="var(--alt)"/>
|
||
<text x="24" y="184" font-size="14" font-weight="600" fill="var(--ink)">Tactical</text>
|
||
<text x="24" y="207" font-size="11" fill="var(--muted)">the plan for this wave</text>
|
||
<text x="24" y="230" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)">← the LLM sits</text>
|
||
<text x="24" y="245" font-size="10.5" font-family="'JetBrains Mono', monospace" fill="var(--alt)"> here today</text>
|
||
<text x="230" y="180" font-size="12.5" fill="var(--ink)">joint assignment + sequencing</text>
|
||
<text x="230" y="200" font-size="12.5" fill="var(--ink)">tripsheet load planning</text>
|
||
<text x="230" y="220" font-size="12.5" fill="var(--ink)">hold-vs-go on every departure</text>
|
||
<text x="230" y="240" font-size="12.5" fill="var(--ink)">rider-to-beat for the day</text>
|
||
<text x="600" y="190" font-size="12.5" font-weight="600" fill="var(--alt)">MILP / VRP solver</text>
|
||
<text x="600" y="210" font-size="12.5" fill="var(--alt)">OR-Tools, VROOM, Valhalla matrix</text>
|
||
<text x="600" y="230" font-size="11.5" fill="var(--muted)">deterministic · explainable · milliseconds</text>
|
||
<rect x="940" y="190" width="130" height="26" rx="13" fill="var(--surface)" stroke="var(--alt)"/>
|
||
<text x="1005" y="208" text-anchor="middle" font-size="11" font-family="'JetBrains Mono', monospace" fill="var(--alt)">per wave</text>
|
||
|
||
<rect x="0" y="272" width="1000" height="94" rx="3" fill="var(--accent-soft)" stroke="var(--accent)"/>
|
||
<rect x="0" y="272" width="5" height="94" fill="var(--accent)"/>
|
||
<text x="24" y="308" font-size="14" font-weight="600" fill="var(--ink)">Operational</text>
|
||
<text x="24" y="331" font-size="11" fill="var(--muted)">what just went wrong</text>
|
||
<text x="230" y="304" font-size="12.5" fill="var(--ink)">unresolvable address · receiver unreachable</text>
|
||
<text x="230" y="324" font-size="12.5" fill="var(--ink)">rider absent · vehicle down · overflow parcel</text>
|
||
<text x="230" y="344" font-size="12.5" fill="var(--ink)">operator asks a question in plain English</text>
|
||
<text x="600" y="314" font-size="12.5" font-weight="600" fill="var(--accent-ink)">LLM + rules</text>
|
||
<text x="600" y="334" font-size="11.5" fill="var(--accent-ink)">judgment over messy, unstructured input</text>
|
||
<rect x="940" y="306" width="130" height="26" rx="13" fill="var(--surface)" stroke="var(--accent)"/>
|
||
<text x="1005" y="324" text-anchor="middle" font-size="11" font-family="'JetBrains Mono', monospace" fill="var(--accent-ink)">per second</text>
|
||
|
||
<rect x="0" y="404" width="1000" height="58" rx="3" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
|
||
<text x="24" y="428" font-size="13" font-weight="600" fill="var(--ink)">Completed-route exhaust</text>
|
||
<text x="24" y="448" font-size="11.5" fill="var(--muted)">AgentDecision · breadcrumbs · arrivedat + GPS · Attemptcount · DeliveryProof geolocation</text>
|
||
<text x="700" y="440" font-size="12" font-weight="600" fill="var(--ink)">→ learned dwell, travel time, failure risk</text>
|
||
|
||
<path d="M1040,433 H1090 V60 H1006" fill="none" stroke="currentColor" stroke-width="1.5" marker-end="url(#c)"/>
|
||
<path d="M1090,203 H1006" fill="none" stroke="currentColor" stroke-width="1.5" marker-end="url(#c)"/>
|
||
<path d="M1090,319 H1006" fill="none" stroke="currentColor" stroke-width="1.5" marker-end="url(#c)"/>
|
||
<text x="1104" y="250" font-size="11" font-weight="600" fill="var(--ink)" transform="rotate(90 1104 250)">trains every layer</text>
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<figcaption><b>Figure 6 — three clocks, three techniques</b>Language models are strong on unstructured judgment and weak on combinatorial search. Solvers are the reverse. The current design has them swapped.</figcaption>
|
||
</figure>
|
||
|
||
<h3>Per leg, what to build</h3>
|
||
|
||
<ul class="plain">
|
||
<li><b>First — batch the window, solve once.</b> Accumulate 10–15 minutes of open pickups and solve assignment and sequence together. Make the candidate set adaptive: widen the radius until <em>k</em> genuine candidates are found, instead of a fixed 10 km that returns ten near-identical riders at noon and nothing at 6 AM. Learn dwell time per pickup point — every <code>arrivedat</code> → <code>pickup-complete</code> pair is already a labelled example.</li>
|
||
<li><b>Mid — auto-build the tripsheet.</b> Parcel-to-truck given capacity, destination mix and cut-off is bin-packing with a clean objective. Then price hold-vs-go against <code>sladueat</code>, and consolidate thin lanes through a transfer hub rather than running half-empty direct trips — <code>Batchkind</code>'s <code>local</code>/<code>transfer</code> split already anticipates this.</li>
|
||
<li><b>Last — predict first-attempt success and sequence on it.</b> <code>Attemptcount</code> and skip reasons are your training labels. Then resolve addresses with the pgvector you already run: embedding delivery addresses and snapping them to confirmed <code>DeliveryProof</code> coordinates turns your delivered history into a private geocoder that beats any general one inside your own zones.</li>
|
||
</ul>
|
||
|
||
<div class="callout teal">
|
||
<span class="label">Replace this function first</span>
|
||
<p><code>calculateETA</code> is <code>(distance / 20) × 60 + 10</code> — a flat 20 km/h against straight-line distance, plus a fixed ten-minute buffer. It sets rider expectations, customer-facing ETAs and support load. Road time from the Valhalla matrix you already pay for, multiplied by a time-of-day factor and added to learned dwell, is a same-day change with effect on every leg.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ================= 07 BUILD ================= -->
|
||
<section id="build">
|
||
<div class="sec-head"><span class="sec-num">07</span><div><h2>Build order</h2></div></div>
|
||
|
||
<p class="measure">Sequenced by ratio of effect to effort, not by ambition. The first two need no new infrastructure at all.</p>
|
||
|
||
<div class="table-scroll">
|
||
<table>
|
||
<thead><tr><th>#</th><th>Move</th><th>Leg</th><th>Depends on</th><th>Why it's here</th></tr></thead>
|
||
<tbody>
|
||
<tr><td class="num">1</td><td>Score <code>AgentDecision</code> against outcomes</td><td>all</td><td>nothing — data exists</td><td>You cannot currently tell whether AI dispatch beats greedy-nearest. Until you can, every other change is unmeasurable.</td></tr>
|
||
<tr><td class="num">2</td><td>Real ETA from road time + learned dwell</td><td>all</td><td>Valhalla matrix</td><td>Replaces a flat 20 km/h constant. Touches rider trust, customer ETA and support volume at once.</td></tr>
|
||
<tr><td class="num">3</td><td>Adaptive candidate set</td><td>first</td><td>nothing</td><td>Removes both failure modes of the fixed 10 km / top 10 window.</td></tr>
|
||
<tr><td class="num">4</td><td>Batched joint assign + sequence</td><td>first, last</td><td>solver, batch window</td><td>The ordering fix in §05. Largest routing gain available.</td></tr>
|
||
<tr><td class="num">5</td><td>Address resolution on pgvector</td><td>last</td><td>delivered history</td><td>Turns your own proof-of-delivery coordinates into a private geocoder for your zones.</td></tr>
|
||
<tr><td class="num">6</td><td>First-attempt success model</td><td>last</td><td>#5, <code>Attemptcount</code> history</td><td>Attacks the dominant cost line in last mile.</td></tr>
|
||
<tr><td class="num">7</td><td>Automated tripsheet load planning</td><td>mid</td><td>#2, volume forecast</td><td>The only leg with no optimisation at all today.</td></tr>
|
||
<tr><td class="num">8</td><td>Hold-vs-go and dynamic cut-offs</td><td>mid</td><td>#7, <code>sladueat</code></td><td>Converts a dispatcher's guess into a priced decision.</td></tr>
|
||
<tr><td class="num">9</td><td>Beat districting</td><td>first, last</td><td>#4, a Beat entity</td><td>The strategic layer. Only worth it once daily routing is solid.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<span class="label">The milk-round dividend</span>
|
||
<p>Every item above gets cheaper under a round model rather than more expensive. When stops are stable, the heavy optimisation moves to beat-design time — monthly, offline, with as much compute as you like — and the daily solve shrinks to the deltas: today's new stops, today's skips, today's absences. Real-time combinatorial search over the whole city, every wave, is a cost you only pay because the beats do not exist yet.</p>
|
||
</div>
|
||
</section>
|
||
|
||
<footer>
|
||
Read against <code>constants/constants.go</code>, <code>internal/assignment/</code>, <code>internal/routing/optimizer.go</code>, <code>models/audit.go</code>, <code>models/transit.go</code> and <code>controllers/adminController.go</code> in the nearle-de workspace. Status strings, constants and formulas are quoted as they appear there.
|
||
</footer>
|
||
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|