| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Local Food AI - Capstone Technical Presentation</title>
- <!-- Google Fonts -->
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
- <style>
- :root {
- --bg-color: #0b0c10;
- --slide-bg: rgba(21, 26, 35, 0.7);
- --border-color: rgba(255, 255, 255, 0.08);
- --primary-accent: #58a6ff;
- --secondary-accent: #bc8cff;
- --text-primary: #e6edf3;
- --text-secondary: #8b949e;
- --text-muted: #484f58;
- --code-bg: #161b22;
- --font-display: 'Outfit', sans-serif;
- --font-sans: 'Inter', sans-serif;
- --font-mono: 'Fira Code', monospace;
- --glow-color: rgba(88, 166, 255, 0.15);
- }
- * {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- }
- body {
- background-color: var(--bg-color);
- background-image:
- radial-gradient(at 0% 0%, rgba(88, 166, 255, 0.08) 0, transparent 50%),
- radial-gradient(at 100% 100%, rgba(188, 140, 255, 0.08) 0, transparent 50%);
- color: var(--text-primary);
- font-family: var(--font-sans);
- overflow: hidden;
- height: 100vh;
- width: 100vw;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
- /* Slide Container */
- .presentation-container {
- position: relative;
- width: 90%;
- max-width: 1100px;
- height: 80vh;
- min-height: 550px;
- perspective: 1500px;
- }
- .slide-deck {
- position: relative;
- width: 100%;
- height: 100%;
- transform-style: preserve-3d;
- }
- /* Individual Slides */
- .slide {
- position: absolute;
- width: 100%;
- height: 100%;
- background: var(--slide-bg);
- border: 1px solid var(--border-color);
- border-radius: 16px;
- padding: 3.5rem;
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), inset 0 1px 1px rgba(255, 255, 255, 0.1);
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- opacity: 0;
- visibility: hidden;
- transform: scale(0.9) translateZ(-100px);
- transition: all 0.6s cubic-bezier(0.25, 0.8, 0.25, 1);
- pointer-events: none;
- }
- /* Slide States for Animations */
- .slide.active {
- opacity: 1;
- visibility: visible;
- transform: scale(1) translateZ(0);
- pointer-events: auto;
- z-index: 10;
- }
- .slide.prev {
- opacity: 0;
- visibility: hidden;
- transform: scale(0.9) translateX(-100%) rotateY(15deg);
- z-index: 5;
- }
- .slide.next {
- opacity: 0;
- visibility: hidden;
- transform: scale(0.9) translateX(100%) rotateY(-15deg);
- z-index: 5;
- }
- /* Content Formatting */
- .slide-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1px dashed var(--border-color);
- padding-bottom: 1rem;
- margin-bottom: 1.5rem;
- }
- .slide-header .tag {
- font-family: var(--font-mono);
- font-size: 0.8rem;
- color: var(--secondary-accent);
- text-transform: uppercase;
- letter-spacing: 2px;
- }
- .slide-header .title-badge {
- font-size: 0.75rem;
- padding: 0.25rem 0.75rem;
- border-radius: 20px;
- background: rgba(88, 166, 255, 0.1);
- border: 1px solid rgba(88, 166, 255, 0.2);
- color: var(--primary-accent);
- font-family: var(--font-mono);
- }
- .slide-body {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
- .slide-title {
- font-family: var(--font-display);
- font-size: 2.2rem;
- font-weight: 700;
- line-height: 1.2;
- margin-bottom: 1.5rem;
- background: linear-gradient(135deg, var(--primary-accent), var(--secondary-accent));
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
- .slide-footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-top: 1px solid var(--border-color);
- padding-top: 1.2rem;
- margin-top: 1.5rem;
- font-size: 0.8rem;
- color: var(--text-secondary);
- }
- /* Grid Layouts inside slides */
- .slide-grid-2 {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 2rem;
- align-items: start;
- }
- .slide-grid-3 {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- gap: 1.5rem;
- }
- /* Lists and Cards */
- ul {
- list-style: none;
- }
- li {
- position: relative;
- padding-left: 1.5rem;
- margin-bottom: 0.8rem;
- font-size: 1.05rem;
- line-height: 1.5;
- color: var(--text-primary);
- }
- li::before {
- content: "→";
- position: absolute;
- left: 0;
- color: var(--primary-accent);
- font-weight: bold;
- }
- li strong {
- color: #fff;
- font-weight: 600;
- }
- .card {
- background: rgba(255, 255, 255, 0.02);
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 8px;
- padding: 1.2rem;
- transition: all 0.3s ease;
- }
- .card:hover {
- background: rgba(255, 255, 255, 0.04);
- border-color: rgba(88, 166, 255, 0.2);
- box-shadow: 0 4px 20px var(--glow-color);
- transform: translateY(-2px);
- }
- .card-title {
- font-family: var(--font-display);
- font-size: 1.15rem;
- font-weight: 600;
- color: #fff;
- margin-bottom: 0.6rem;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- }
- .card-title .icon {
- color: var(--primary-accent);
- }
- .card-desc {
- font-size: 0.95rem;
- color: var(--text-secondary);
- line-height: 1.4;
- }
- /* Title Slide Layout */
- .title-slide {
- justify-content: center;
- align-items: center;
- text-align: center;
- }
- .title-slide .slide-title {
- font-size: 3.2rem;
- margin-bottom: 1.5rem;
- }
- .title-slide .subtitle {
- font-size: 1.3rem;
- color: var(--text-secondary);
- margin-bottom: 2rem;
- max-width: 700px;
- }
- .meta-grid {
- display: grid;
- grid-template-columns: repeat(3, 160px);
- gap: 1.5rem;
- margin-top: 2rem;
- border-top: 1px solid var(--border-color);
- padding-top: 2rem;
- }
- .meta-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .meta-label {
- font-size: 0.75rem;
- text-transform: uppercase;
- letter-spacing: 1px;
- color: var(--text-secondary);
- margin-bottom: 0.4rem;
- }
- .meta-value {
- font-size: 0.95rem;
- font-weight: 500;
- color: #fff;
- }
- /* Code Block Styling */
- pre {
- background: var(--code-bg);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 1rem;
- overflow: auto;
- max-height: 250px;
- text-align: left;
- }
- code {
- font-family: var(--font-mono);
- font-size: 0.9rem;
- color: #ff7b72;
- }
- pre code {
- color: #e6edf3;
- }
- .highlight-text {
- color: var(--primary-accent);
- font-weight: 500;
- }
- /* SVG Schematic Layout */
- .architecture-svg {
- width: 100%;
- height: 260px;
- background: rgba(0, 0, 0, 0.2);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 0.5rem;
- }
- /* Navigation Controls */
- .controls {
- position: absolute;
- bottom: 2.5vh;
- display: flex;
- align-items: center;
- gap: 2rem;
- z-index: 100;
- }
- .nav-btn {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid var(--border-color);
- border-radius: 50%;
- width: 50px;
- height: 50px;
- color: var(--text-primary);
- cursor: pointer;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 1.2rem;
- transition: all 0.2s ease;
- }
- .nav-btn:hover:not(:disabled) {
- background: rgba(88, 166, 255, 0.2);
- border-color: var(--primary-accent);
- box-shadow: 0 0 15px var(--glow-color);
- color: #fff;
- transform: scale(1.05);
- }
- .nav-btn:disabled {
- opacity: 0.3;
- cursor: not-allowed;
- }
- .progress-bar-container {
- width: 250px;
- height: 6px;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 3px;
- overflow: hidden;
- }
- .progress-bar {
- height: 100%;
- width: 0%;
- background: linear-gradient(90deg, var(--primary-accent), var(--secondary-accent));
- border-radius: 3px;
- transition: width 0.4s ease;
- }
- .slide-counter {
- font-family: var(--font-mono);
- font-size: 0.85rem;
- color: var(--text-secondary);
- min-width: 100px;
- text-align: center;
- }
- /* Dynamic SVG diagram styles */
- .svg-box { fill: #161b22; stroke: var(--border-color); stroke-width: 1.5; rx: 6; }
- .svg-box:hover { fill: #21262d; stroke: var(--primary-accent); }
- .svg-text { fill: #e6edf3; font-family: var(--font-sans); font-size: 12px; text-anchor: middle; font-weight: 500; }
- .svg-text-sub { fill: var(--text-secondary); font-family: var(--font-mono); font-size: 10px; text-anchor: middle; }
- .svg-line { stroke: var(--text-secondary); stroke-width: 1.5; stroke-dasharray: 4; fill: none; }
- .svg-line-active { stroke: var(--primary-accent); stroke-width: 2; fill: none; }
- </style>
- </head>
- <body>
- <div class="presentation-container">
- <div class="slide-deck">
- <!-- Slide 1: Title -->
- <div class="slide title-slide active">
- <div class="slide-header">
- <span class="tag">Local Food AI</span>
- <span class="title-badge">CAPSTONE PROJECT</span>
- </div>
- <div class="slide-body">
- <h1 class="slide-title">Local Food AI Platform</h1>
- <p class="subtitle">Complete Technical Architecture, Development Lifecycle, and Security Governance Overview</p>
- <div class="meta-grid">
- <div class="meta-item">
- <span class="meta-label">Audience</span>
- <span class="meta-value">IT & Technical Team</span>
- </div>
- <div class="meta-item">
- <span class="meta-label">Lead Engineer</span>
- <span class="meta-value">Francois Lange</span>
- </div>
- <div class="meta-item">
- <span class="meta-label">Version ID</span>
- <span class="meta-value">2a8ed056 (main)</span>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Department of Domain-specific Project 1 (DSP1) & PM1</span>
- <span>1 / 11</span>
- </div>
- </div>
- <!-- Slide 2: Core IT Requirements -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">System Design</span>
- <span class="title-badge">IT REQUIREMENTS</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Core Requirements Analysis & Assessment</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>100% Privacy & Data Sovereignty</strong>: Absolute enforcement of an air-gapped system. Patient parameters, query strings, and custom food plans must never escape the local subnet.</li>
- <li><strong>Sub-second Database Response</strong>: Fast SQL lookup queries against the 24GB OpenFoodFacts dataset (with over 100+ raw schema parameters).</li>
- <li><strong>Offline LLM Inference Capabilities</strong>: Local AI reasoning model supporting clinical tool-calling without internet-based APIs.</li>
- </ul>
- </div>
- <div class="slide-grid-2" style="grid-template-columns: 1fr; gap: 1rem;">
- <div class="card">
- <div class="card-title"><span class="icon">🛡️</span> HIPAA Guard</div>
- <div class="card-desc">No external API leaks. Prompt parameters remain in internal RAM pools.</div>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">📈</span> High Efficiency</div>
- <div class="card-desc">Sub-second database responses via optimized views and B-Tree indexing.</div>
- </div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>2 / 11</span>
- </div>
- </div>
- <!-- Slide 3: Multi-Container Architecture -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Architecture Diagram</span>
- <span class="title-badge">LOCAL INFRASTRUCTURE</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Distributed Local Component Communication</h2>
- <div class="slide-grid-2" style="grid-template-columns: 1.3fr 0.7fr; gap: 1.5rem; align-items: center;">
- <svg class="architecture-svg" viewBox="0 0 500 250">
- <!-- Client -->
- <rect class="svg-box" x="10" y="90" width="100" height="40" style="fill:rgba(88, 166, 255, 0.1); stroke: var(--primary-accent);"/>
- <text class="svg-text" x="60" y="110">User Browser</text>
- <text class="svg-text-sub" x="60" y="122">HTTP Port 80</text>
-
- <!-- Proxy & App -->
- <rect class="svg-box" x="150" y="30" width="100" height="40"/>
- <text class="svg-text" x="200" y="50">Nginx Gateway</text>
- <text class="svg-text-sub" x="200" y="62">Port 80</text>
-
- <rect class="svg-box" x="150" y="150" width="100" height="40"/>
- <text class="svg-text" x="200" y="170">Streamlit UI</text>
- <text class="svg-text-sub" x="200" y="182">Port 8502</text>
-
- <!-- Backends -->
- <rect class="svg-box" x="350" y="15" width="110" height="40"/>
- <text class="svg-text" x="405" y="35">Ollama Daemon</text>
- <text class="svg-text-sub" x="405" y="47">Port 11434 (Llama3.2)</text>
-
- <rect class="svg-box" x="350" y="90" width="110" height="40"/>
- <text class="svg-text" x="405" y="110">MySQL Cluster</text>
- <text class="svg-text-sub" x="405" y="122">Port 3306 (Partitions)</text>
- <rect class="svg-box" x="350" y="165" width="110" height="40"/>
- <text class="svg-text" x="405" y="185">Zabbix Server</text>
- <text class="svg-text-sub" x="405" y="197">Port 10051 (SNMPv3)</text>
-
- <!-- Lines -->
- <path class="svg-line-active" d="M 110 110 L 150 50" />
- <path class="svg-line-active" d="M 200 70 L 200 150" />
- <path class="svg-line" d="M 250 160 L 350 35" />
- <path class="svg-line-active" d="M 250 170 L 350 110" />
- <path class="svg-line" d="M 250 180 L 350 185" />
- </svg>
- <div>
- <ul>
- <li><strong>Nginx Proxy</strong> routes all browser traffic to Streamlit.</li>
- <li><strong>Streamlit</strong> issues optimized view queries directly to <strong>MySQL</strong>.</li>
- <li>RAG flows communicate locally with <strong>Ollama</strong>.</li>
- <li><strong>Zabbix</strong> monitors all health points locally via secure SNMPv3 channels.</li>
- </ul>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>3 / 11</span>
- </div>
- </div>
- <!-- Slide 4: Database Design -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Database Performance</span>
- <span class="title-badge">VERTICAL PARTITIONING</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Database Design: Grouped Partitioning</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>Vertically Partitioned Schema</strong>: To split the massive OpenFoodFacts attributes and avoid InnoDB row size limits, the data is partitioned across 5 tables:
- <span class="highlight-text">core, allergens, macros, vitamins, minerals</span>.
- </li>
- <li><strong>Unified SQL View</strong>: A single joined view presents records to the Streamlit app seamlessly.</li>
- <li><strong>FULLTEXT Search Indexes</strong>: Added to the `products_core` title column to bypass standard slow string matches.</li>
- </ul>
- </div>
- <div class="card">
- <div style="font-family: var(--font-mono); font-size: 1.3rem; margin-bottom: 1rem; color: var(--primary-accent); text-align: center; border-bottom: 1px dashed var(--border-color); padding-bottom: 0.5rem;">
- Performance Comparison
- </div>
- <div style="display: flex; justify-content: space-around; align-items: center; margin-top: 1.5rem;">
- <div style="text-align: center;">
- <div style="color: var(--text-secondary); font-size: 0.8rem; text-transform: uppercase;">Standard JOIN</div>
- <div style="font-size: 1.8rem; color: #ff7b72; font-weight: 700;">> 4.20s</div>
- </div>
- <div style="font-size: 1.5rem; color: var(--text-secondary);">→</div>
- <div style="text-align: center;">
- <div style="color: var(--primary-accent); font-size: 0.8rem; text-transform: uppercase;">Optimized Index</div>
- <div style="font-size: 2.2rem; color: #56ff56; font-weight: 700;">< 0.04s</div>
- </div>
- </div>
- <div style="font-size: 0.8rem; text-align: center; color: var(--text-secondary); margin-top: 1rem;">
- Sub-second response times achieved via strategic index bindings!
- </div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>4 / 11</span>
- </div>
- </div>
- <!-- Slide 5: LLM and RAG -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Artificial Intelligence</span>
- <span class="title-badge">LOCAL LLM / RAG</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Local LLM Choice & RAG Tool-Calling</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>Quantized `llama3.2:3b` Model</strong>: Runs completely locally under Ollama daemon. Consumes only 2.2 GB of RAM, fitting within the 16 GB hardware constraints.</li>
- <li><strong>Chain-of-Thought (CoT)</strong>: System prompts enforce XML-wrapped thinking tags (`<thought>...</thought>`) to explain dietary constraints.</li>
- <li><strong>Autonomous Tool Calling</strong>: Enables the LLM to trigger two local tools:
- <br>1. `search_nutrition_db` (direct SQL macro retrieval)
- <br>2. `local_web_search` (covert SearXNG search proxy)
- </li>
- </ul>
- </div>
- <pre><code># Ollama Tool Binding Example in app.py
- response = ollama.chat(
- model='llama3.2:3b',
- messages=st.session_state.messages,
- tools=[search_nutrition_db, local_web_search]
- )
- # Returns structured arguments to
- # query vertical database views!</code></pre>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>5 / 11</span>
- </div>
- </div>
- <!-- Slide 6: Antigravity Subagents -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Development Operations</span>
- <span class="title-badge">ANTIGRAVITY AGENTS</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">AI Agent Orchestration: Antigravity Subagents</h2>
- <div class="slide-grid-3">
- <div class="card">
- <div class="card-title"><span class="icon">🎓</span> Expert Coach</div>
- <div class="card-desc">Reviewed vertical partition schemas, verified dynamic formats, and checked code complexity indices.</div>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">📝</span> Doc Writer</div>
- <div class="card-desc">Maintained operational guides and manuals in `/docs`, syncing them with codebase modifications.</div>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">🖇️</span> Git Commit</div>
- <div class="card-desc">Linked local commits to Gogs and triggered automatic updates to the Taiga task board.</div>
- </div>
- </div>
- <div class="slide-grid-2" style="margin-top: 1rem; grid-template-columns: 1fr 1fr;">
- <div class="card">
- <div class="card-title"><span class="icon">🛠️</span> SQL Optimizer</div>
- <div class="card-desc">Created database indices, vertical division schemas, and optimized JOIN layouts on unified views.</div>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">🔍</span> Code Reviewer</div>
- <div class="card-desc">Analyzed python scripts (`app.py`, `ingest_csv.py`) to resolve encoding issues and runtime errors.</div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>6 / 11</span>
- </div>
- </div>
- <!-- Slide 7: Agent Permissions -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Security Governance</span>
- <span class="title-badge">POLP BOUNDARIES</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Agent Permissions & Security Boundaries</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>Principle of Least Privilege (PoLP)</strong>: Configured tight boundaries to ensure the AI agent could not access sensitive host directory structures or execute unchecked network calls.</li>
- <li><strong>Path Lock constraints</strong>: Restricted file read and write operations exclusively to the local workspace folder.</li>
- <li><strong>Shell commands limits</strong>: Prevented shell command executes. Limited commands exclusively to `git`, `python`, `chmod`, `docker-compose`, and `Get-Content`.</li>
- </ul>
- </div>
- <div class="card" style="background: rgba(255, 123, 114, 0.05); border-color: rgba(255, 123, 114, 0.2);">
- <div class="card-title" style="color: #ff7b72;"><span class="icon">⚠️</span> Network Guardrails</div>
- <div class="card-desc" style="color: var(--text-secondary); margin-bottom: 0.5rem;">
- Network reads and executions were restricted solely to:
- </div>
- <div style="font-family: var(--font-mono); font-size: 0.85rem; color: #fff;">
- - 192.168.130.170 (Docker host)
- <br>- 192.168.130.161 (Taiga API)
- </div>
- <div style="margin-top: 0.8rem; font-size: 0.8rem; color: var(--text-secondary);">
- Zero external DNS queries or telemetry leaks allowed!
- </div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>7 / 11</span>
- </div>
- </div>
- <!-- Slide 8: Technical Struggles -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Lessons Learned</span>
- <span class="title-badge">REFLECTIONS</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Reflections: Engineering Struggles & Solutions</h2>
- <div class="slide-grid-3">
- <div class="card">
- <div class="card-title" style="font-size:1rem; color:#ff7b72;">1. Regex Greediness</div>
- <div class="card-desc" style="font-size:0.85rem;">
- <strong>Struggle:</strong> The smudge regex was too greedy and corrupted python literals across lines.
- <br><strong>Fix:</strong> Line-restricted the pattern (`[^\r\n$]+\$`) and split literals in the script.
- </div>
- </div>
- <div class="card">
- <div class="card-title" style="font-size:1rem; color:#ff7b72;">2. Filter Self-Delete</div>
- <div class="card-desc" style="font-size:0.85rem;">
- <strong>Struggle:</strong> Git checkout deleted the smudge script first, causing subsequent smudges to crash.
- <br><strong>Fix:</strong> Sequential checkout checking out the filter script before other files.
- </div>
- </div>
- <div class="card">
- <div class="card-title" style="font-size:1rem; color:#ff7b72;">3. Accent Encoding</div>
- <div class="card-desc" style="font-size:0.85rem;">
- <strong>Struggle:</strong> French accents in git metadata logs broke code writers under Windows codepages.
- <br><strong>Fix:</strong> Sanitizers processing python strings with `errors='replace'` writing to UTF-8.
- </div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>8 / 11</span>
- </div>
- </div>
- <!-- Slide 9: Observability -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">DevOps Telemetry</span>
- <span class="title-badge">ZABBIX & SNMPv3</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Enterprise Observability & Real-Time Alerts</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>Secure SNMPv3 Architecture</strong>: Set up encrypted monitoring with username `securityUser` utilizing SHA verification and AES encryption keys.</li>
- <li><strong>Dynamic Telemetry Daemon</strong>: `zabbix_telemetry.py` polls system status indices and forwards database states.</li>
- <li><strong>Application Traps (`snmp_notifier.py`)</strong>: Directly triggers SNMP traps on major events:
- <br>- Failed credential attempts
- <br>- Password reset actions
- <br>- Database index delays
- </li>
- </ul>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">💬</span> Active Webhook Routing</div>
- <div class="card-desc" style="margin-bottom: 0.8rem;">
- Zabbix alerts parse the trap and automatically forward notification packets to Teams and Discord webhooks:
- </div>
- <pre style="max-height: 120px;"><code>{
- "event": "SNMPv3 Trap Alert",
- "node": "WSL-Workstation",
- "status": "PROBLEM",
- "desc": "High CPU utilization detected"
- }</code></pre>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>9 / 11</span>
- </div>
- </div>
- <!-- Slide 10: Privacy Verification -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Security Audit</span>
- <span class="title-badge">DATA PRIVACY VERIFIED</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Data Privacy Audit: 100% Air-Gapped</h2>
- <div class="slide-grid-2">
- <div>
- <ul>
- <li><strong>Access Logs Verification</strong>: Audited Nginx reverse proxy configurations to confirm all user requests originate inside the local subnet range.</li>
- <li><strong>Container Network Separation</strong>: Database has no public port mappings. Runs in an isolated Docker bridge container network.</li>
- <li><strong>SearXNG Scraper Proxy</strong>: Strip tracking tags and user cookies prior to querying external domains.</li>
- </ul>
- </div>
- <div class="card" style="background: rgba(86, 255, 86, 0.03); border-color: rgba(86, 255, 86, 0.2);">
- <div class="card-title" style="color: #56ff56;"><span class="icon">🔒</span> TCPDump Network Sniffing Audit</div>
- <div class="card-desc" style="color: var(--text-primary); margin-bottom: 0.5rem;">
- Ran packet sniffing audits during intensive chat inference RAG queries:
- </div>
- <pre style="max-height: 90px; background: #000; border-color: #333;"><code style="color: #56ff56;">$ tcpdump -i eth0 dst port not 80 \
- and dst port not 22 and dst port not 161
- # Result: ZERO packets sent outbound to external IPs!</code></pre>
- <div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 0.5rem;">
- Audited verification confirms no data escapes the host server!
- </div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>10 / 11</span>
- </div>
- </div>
- <!-- Slide 11: Day 2 Operations -->
- <div class="slide">
- <div class="slide-header">
- <span class="tag">Production Maintenance</span>
- <span class="title-badge">FUTURE PLANNING</span>
- </div>
- <div class="slide-body">
- <h2 class="slide-title">Future Work & Day 2 Operations</h2>
- <div class="slide-grid-2">
- <div class="card">
- <div class="card-title"><span class="icon">🔑</span> SSL/TLS Encryption</div>
- <div class="card-desc">Upgrade the Nginx configuration to support HTTPS (Port 443) using the organization's root security authority to encrypt local workstation networks.</div>
- </div>
- <div class="card">
- <div class="card-title"><span class="icon">🧭</span> Database Migrations</div>
- <div class="card-desc">Rely strictly on Alembic scripts to roll out subsequent tables, preventing manual schema modifications from breaking view joins.</div>
- </div>
- <div class="card" style="margin-top: 1rem;">
- <div class="card-title"><span class="icon">⏱️</span> Rate Limiting</div>
- <div class="card-desc">Implement a sliding-window throttling algorithm in `app.py` to prevent potential clinician local server resource exhaustion.</div>
- </div>
- <div class="card" style="margin-top: 1rem;">
- <div class="card-title"><span class="icon">🧪</span> UAT Iteration Loops</div>
- <div class="card-desc">Incorporate clinical dietitian feedback to adjust nutritional threshold recommendations and warning tags.</div>
- </div>
- </div>
- </div>
- <div class="slide-footer">
- <span>Local Food AI - Technical Presentation</span>
- <span>11 / 11</span>
- </div>
- </div>
- </div>
- </div>
- <!-- Navigation Controls -->
- <div class="controls">
- <button class="nav-btn" id="prevBtn" aria-label="Previous slide">←</button>
- <div class="progress-bar-container">
- <div class="progress-bar" id="progress"></div>
- </div>
- <span class="slide-counter" id="counter">Slide 1 of 11</span>
- <button class="nav-btn" id="nextBtn" aria-label="Next slide">→</button>
- </div>
- <script>
- let currentSlide = 0;
- const slides = document.querySelectorAll('.slide');
- const totalSlides = slides.length;
- const prevBtn = document.getElementById('prevBtn');
- const nextBtn = document.getElementById('nextBtn');
- const progress = document.getElementById('progress');
- const counter = document.getElementById('counter');
- function updateSlides() {
- slides.forEach((slide, idx) => {
- slide.classList.remove('active', 'prev', 'next');
- if (idx === currentSlide) {
- slide.classList.add('active');
- } else if (idx < currentSlide) {
- slide.classList.add('prev');
- } else {
- slide.classList.add('next');
- }
- });
- // Update progress bar
- const pct = ((currentSlide + 1) / totalSlides) * 100;
- progress.style.width = `${pct}%`;
- // Update slide counter
- counter.innerText = `Slide ${currentSlide + 1} of ${totalSlides}`;
- // Disable buttons at bounds
- prevBtn.disabled = currentSlide === 0;
- nextBtn.disabled = currentSlide === totalSlides - 1;
- }
- prevBtn.addEventListener('click', () => {
- if (currentSlide > 0) {
- currentSlide--;
- updateSlides();
- }
- });
- nextBtn.addEventListener('click', () => {
- if (currentSlide < totalSlides - 1) {
- currentSlide++;
- updateSlides();
- }
- });
- // Key bindings
- document.addEventListener('keydown', (e) => {
- if (e.key === 'ArrowRight' || e.key === ' ') {
- if (currentSlide < totalSlides - 1) {
- currentSlide++;
- updateSlides();
- }
- } else if (e.key === 'ArrowLeft') {
- if (currentSlide > 0) {
- currentSlide--;
- updateSlides();
- }
- }
- });
- updateSlides();
- </script>
- </body>
- </html>
|