瀏覽代碼

[#1] Commit current state before purging sensitive history

Lange François 2 周之前
父節點
當前提交
27504a592f
共有 76 個文件被更改,包括 2899 次插入31 次删除
  1. 二進制
      37b11a8e-c3a8-4d02-a3f5-db36b8767771.png
  2. 二進制
      41535d4b-b926-4645-a5d9-86205955427c.png
  3. 二進制
      4d00b963-7be0-43b2-bfcf-2c2d005d15b8.png
  4. 二進制
      5dc8b34f-dd7f-4bdd-b4fd-52623ae8cf05.png
  5. 二進制
      5f1439a5-a5c6-40f7-b32e-2562510e9c63.png
  6. 二進制
      754f8f4d-a42b-4590-99f1-912a554fcc6d.png
  7. 二進制
      Project.pdf
  8. 二進制
      Retro Planning.pdf
  9. 二進制
      ad2619d5-f12b-4841-b28c-56a8fc78f56e.png
  10. 二進制
      b2b1454f-7c4a-41ed-ab20-1ab3ca7899f7.png
  11. 二進制
      delivery.zip
  12. 二進制
      docs/Backup_Procedure.pdf
  13. 二進制
      docs/Data_Ingestion.pdf
  14. 69 0
      docs/Env_Configuration.md
  15. 二進制
      docs/Env_Configuration.pdf
  16. 二進制
      docs/Final_Report.pdf
  17. 二進制
      docs/Header_Footer_Antigravity.pdf
  18. 二進制
      docs/Header_Footer_Gemini.pdf
  19. 1 1
      docs/Installation_Guide.md
  20. 二進制
      docs/Installation_Guide.pdf
  21. 1 1
      docs/Operator_Installation_Guide.md
  22. 二進制
      docs/Operator_Installation_Guide.pdf
  23. 906 0
      docs/Presentation_Technical.html
  24. 149 0
      docs/Presentation_Technical.md
  25. 二進制
      docs/Presentation_Technical.pdf
  26. 782 0
      docs/Presentation_User.html
  27. 106 0
      docs/Presentation_User.md
  28. 二進制
      docs/Presentation_User.pdf
  29. 二進制
      docs/Recommendations.pdf
  30. 二進制
      docs/Scrum_Artifacts.pdf
  31. 二進制
      docs/Scrum_Daily.pdf
  32. 二進制
      docs/Scrum_Plan.pdf
  33. 二進制
      docs/Scrum_Retro.pdf
  34. 二進制
      docs/Scrum_Review.pdf
  35. 二進制
      docs/Scrum_Wiki.pdf
  36. 二進制
      docs/Start_Stop_Procedures.pdf
  37. 二進制
      docs/Technical_Document.pdf
  38. 二進制
      docs/Test_Cases_Sprint8.pdf
  39. 二進制
      docs/URL_Formats.pdf
  40. 21 2
      docs/Uninstall_Guide.md
  41. 二進制
      docs/Uninstall_Guide.pdf
  42. 1 1
      docs/User_Description.md
  43. 二進制
      docs/User_Description.pdf
  44. 二進制
      docs/User_Guide.pdf
  45. 二進制
      docs/WSL_Deployment.pdf
  46. 3 0
      docs/Wiki_Home.md
  47. 二進制
      docs/Wiki_Home.pdf
  48. 二進制
      docs/architecture.pdf
  49. 二進制
      docs/disaster_recovery_plan.pdf
  50. 1 1
      docs/distributed_deployment.md
  51. 二進制
      docs/distributed_deployment.pdf
  52. 1 1
      docs/docker_connection.md
  53. 二進制
      docs/docker_connection.pdf
  54. 5 5
      docs/project_report.md
  55. 二進制
      docs/project_report.pdf
  56. 二進制
      docs/retro_planning.pdf
  57. 1 1
      docs/taiga_audit_report.md
  58. 二進制
      docs/taiga_audit_report.pdf
  59. 二進制
      docs/zabbix_monitoring.pdf
  60. 97 5
      generate_docs.py
  61. 12 12
      manage_services.sh
  62. 22 0
      scratch/debug_pdf.py
  63. 84 0
      scratch/debug_pre_issue.py
  64. 23 0
      scratch/find_am_png.py
  65. 25 0
      scratch/find_am_png_everywhere.py
  66. 19 0
      scratch/find_pdf_slashes.py
  67. 24 0
      scratch/inspect_pdf_pages.py
  68. 34 0
      scratch/test_clean_save.py
  69. 120 0
      scratch/test_css_and_headers.py
  70. 56 0
      scratch/test_header_effect.py
  71. 73 0
      scratch/test_header_footer.py
  72. 100 0
      scratch/test_pre.py
  73. 89 0
      scratch/test_pre_length.py
  74. 11 0
      scratch/test_text.py
  75. 62 0
      scratch/uninstall_reinstall_server.py
  76. 1 1
      scripts/generate_project_report.py

二進制
37b11a8e-c3a8-4d02-a3f5-db36b8767771.png


二進制
41535d4b-b926-4645-a5d9-86205955427c.png


二進制
4d00b963-7be0-43b2-bfcf-2c2d005d15b8.png


二進制
5dc8b34f-dd7f-4bdd-b4fd-52623ae8cf05.png


二進制
5f1439a5-a5c6-40f7-b32e-2562510e9c63.png


二進制
754f8f4d-a42b-4590-99f1-912a554fcc6d.png


二進制
Project.pdf


二進制
Retro Planning.pdf


二進制
ad2619d5-f12b-4841-b28c-56a8fc78f56e.png


二進制
b2b1454f-7c4a-41ed-ab20-1ab3ca7899f7.png


二進制
delivery.zip


二進制
docs/Backup_Procedure.pdf


二進制
docs/Data_Ingestion.pdf


+ 69 - 0
docs/Env_Configuration.md

@@ -0,0 +1,69 @@
+The current version is #ident "@(#)$Format:LocalFoodAI_lanfr144:Env_Configuration.md:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
+
+
+# Local Food AI - Environment Variables Configuration Runbook
+
+This runbook catalogs every configuration key in the `.env` file, explaining its functional purpose, dependency context, and providing fillable fields for custom deployment setups.
+
+---
+
+## 1. Network & System Mode Configuration
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `NETWORK_MODE` | Controls external request checks. Set to `server` to enable telemetry alerts and database triggers, or `local` for offline development/standalone fallback. | `server` | `____________________` |
+| `LLM_MODEL` | The local Large Language Model loaded into Ollama. Default is `llama3.2:3b` for quantized local inference. | `llama3.2:3b` | `____________________` |
+
+---
+
+## 2. Database Credentials (MySQL)
+
+These credentials configure standard user authentication permissions on the vertical partitions.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `MYSQL_ROOT_PASSWORD` | MySQL root administrative account access password. | `your_mysql_root_pass` | `____________________` |
+| `DB_READER_PASS` | Password for the low-privilege `food_reader` database account. | `your_db_reader_pass` | `____________________` |
+| `DB_LOADER_PASS` | Password for the `food_loader` account executing bulk CSV imports. | `your_db_loader_pass` | `____________________` |
+| `DB_APP_AUTH_PASS` | Password for the `food_app_auth` credential checker role. | `your_db_auth_pass` | `____________________` |
+| `MYSQL_ZABBIX_PASSWORD` | Password for the Zabbix DB user connection profile. | `your_mysql_zabbix_pass` | `____________________` |
+
+---
+
+## 3. Zabbix & SNMPv3 Configuration
+
+Configures the monitoring server APIs and SNMPv3 security parameters.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `ZABBIX_URL` | Endpoint URL for Zabbix API script queries. | `http://your_zabbix_host:8081` | `____________________` |
+| `ZABBIX_USER` | Admin dashboard username for API provisioning. | `your_zabbix_user` | `____________________` |
+| `ZABBIX_PASS` | Zabbix admin dashboard password. | `your_zabbix_pass` | `____________________` |
+| `ZABBIX_SNMP_USER` | Secure SNMPv3 routing user identifier. | `your_snmp_user` | `____________________` |
+| `ZABBIX_SNMP_AUTHKEY` | SNMPv3 SHA authentication key (minimum 8 characters). | `your_snmp_authkey` | `____________________` |
+| `ZABBIX_SNMP_PRIVKEY` | SNMPv3 AES encryption key (minimum 8 characters). | `your_snmp_privkey` | `____________________` |
+| `DISCORD_WEBHOOK` | External webhook URL for automated alerts. | `https://discord.com/api/webhooks/your_webhook_id` | `____________________` |
+
+---
+
+## 4. Alert Mail Gateways & PM Integration
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `EMAIL_USER` | SMTP server username for password recovery alerts. | `your_email_user@example.com` | `____________________` |
+| `EMAIL_PASS` | SMTP server authentication password. | `your_email_password` | `____________________` |
+| `TAIGA_URL` | Endpoint path for the Taiga Agile Kanban board server. | `http://your_taiga_host/taiga` | `____________________` |
+| `TAIGA_USER` | API authentication account username. | `your_taiga_user` | `____________________` |
+| `TAIGA_PASS` | Password for the API sync user. | `your_taiga_pass` | `____________________` |
+
+---
+
+## 5. VM Server Remote Deployments
+
+Credentials required to manage SSH keys and remote container processes from the client.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `SERVER_HOST` | Remote server IP address (Server A hypervisor). | `your_server_ip` | `____________________` |
+| `SERVER_USER` | SSH login username for command line deployments. | `your_ssh_user` | `____________________` |
+| `SERVER_PASS` | SSH login password for VM operations. | `your_ssh_pass` | `____________________` |

二進制
docs/Env_Configuration.pdf


二進制
docs/Final_Report.pdf


二進制
docs/Header_Footer_Antigravity.pdf


二進制
docs/Header_Footer_Gemini.pdf


+ 1 - 1
docs/Installation_Guide.md

@@ -159,7 +159,7 @@ chmod +x data_sync.sh backup_db.sh manage_services.sh   scripts/manage_models.sh
 
 Follow the standard runbook to initialize credentials and launch services:
 ```bash
-# 1. Create a local [.env file](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/.env) based on step 3 guidelines
+# 1. Create a local [.env file](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/.env) based on the [Environment Variables Configuration Runbook](Env_Configuration.md) guidelines
 # 2. Run the service manager to spin up containers
 ./manage_services.sh start
 ```

二進制
docs/Installation_Guide.pdf


+ 1 - 1
docs/Operator_Installation_Guide.md

@@ -29,7 +29,7 @@ To maximize CPU/GPU efficiency and secure database read/writes, services are dis
 | :--- | :--- | :--- |
 | **streamlit-app (app.py)** | Local WSL2 (Windows) | Low-latency rendering and direct client access |
 | **mysql (Database Node)** | Hyper-V VM (Server A) | Persistent enterprise-grade disk storage |
-| **ollama (NLP Qwen2.5:7b Engine)** | VirtualBox VM (Server B) | Dedicated CPU/GPU virtualization allocation |
+| **ollama (NLP llama3.2:3b Engine)** | VirtualBox VM (Server B) | Dedicated CPU/GPU virtualization allocation |
 | **zabbix-server & web (Monitoring)** | Hyper-V VM (Server A) | Centralized SNMPv3 alert processing and logs |
 | **searxng (Meta-Search Gateway)** | Local WSL2 (Windows) | Dynamic browser-level loopbacks |
 

二進制
docs/Operator_Installation_Guide.pdf


+ 906 - 0
docs/Presentation_Technical.html

@@ -0,0 +1,906 @@
+<!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;">&gt; 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;">&lt; 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>

+ 149 - 0
docs/Presentation_Technical.md

@@ -0,0 +1,149 @@
+The current version is $Format:LocalFoodAI_lanfr144:Presentation_Technical.md:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$
+
+
+# Local Food AI - Capstone Technical Presentation
+
+This presentation slides outline the technical architecture, development lifecycle, DevOps telemetry, and security governance of the **Local Food AI** clinical dietitian platform.
+
+---
+
+## Slide 1: Executive Project Context & Technical Requirements
+
+The **Local Food AI** system is a strictly local, privacy-first dietetics and nutrition analyzer. 
+
+### Core IT Requirements:
+* **100% Privacy & Data Sovereignty**: Under no circumstances can patient clinical details, medical histories, or dietitian search queries egress to cloud servers.
+* **Low Latency Database Lookups**: Sub-second search capabilities against the massive 24GB OpenFoodFacts dataset.
+* **Lightweight Offline Inference**: Deploy a fully functional, localized Large Language Model (LLM) engine capable of clinical RAG validation without external API dependency.
+* **DevOps Observability**: Standard SNMPv3 metrics and real-time alerts integrated into Zabbix server.
+
+---
+
+## Slide 2: Platform Architecture & Technology Stack
+
+The platform is containerized as an 8-service local Docker Compose stack running within a private, air-gapped Ubuntu network boundary.
+
+```mermaid
+flowchart TD
+    Client["Client Browser"] -->|Port 80 HTTP| Nginx["Nginx Reverse Proxy"]
+    Nginx -->|Port 8502 Proxy Pass| Streamlit["Streamlit Web Interface"]
+    Streamlit -->|Port 3306 SQL| MySQL["MySQL DB Node"]
+    Streamlit -->|Port 11434 API| Ollama["Ollama Engine (Llama3.2:3b)"]
+    Streamlit -->|Port 8085 Search| SearXNG["SearXNG Proxy"]
+    MySQL -->|Port 161 SNMPv3 Traps| Zabbix["Zabbix Server"]
+    SearXNG -->|Anonymous Web Scrape| Dust["Dust Search"]
+```
+
+---
+
+## Slide 3: Database Design: Grouped Vertical Partitioning
+
+Loading the OpenFoodFacts dataset requires bypassing InnoDB's hard limit of 1017 columns and optimizing query latency:
+
+### Partitioning Model:
+* The schema is vertically split into 5 physical tables to avoid table row size overflows:
+  1. `products_core` (Barcode, product name, brand, energy, Nutri-Score, and FULLTEXT indices)
+  2. `products_allergens` (Gluten, nuts, milk, mustard, etc.)
+  3. `products_macros` (Proteins, carbohydrates, fats, sugars, fibers, salt)
+  4. `products_vitamins` (Vitamins A, B, C, D, E, etc.)
+  5. `products_minerals` (Calcium, iron, sodium, potassium, etc.)
+* **The Unified View**: A database VIEW named `products` executes high-performance `LEFT JOIN` operations across the vertical partitions.
+* **Latency Optimization**: Adding custom B-TREE indexes on core search fields reduced lookup times from >4.2s to **<0.04s** (99% reduction).
+
+---
+
+## Slide 4: Local LLM Engine & RAG Configuration
+
+We upgraded the AI capability to deliver complex medical reasoning without cloud latency.
+
+### LLM Specifications:
+* **Active Model**: **`llama3.2:3b`** (quantized 3-Billion parameter instruction-following model) hosted locally via Ollama.
+* **Memory Footprint**: Fits within a ~4.7 GB RAM envelope, running smoothly on the host workstation.
+* **Structured Reasoning**: Configured with a system prompt that forces XML Chain-of-Thought (CoT) processing.
+* **Tool Calling Capabilities**:
+  * `search_nutrition_db`: Direct local MySQL extraction for exact nutrient records.
+  * `local_web_search`: Direct local SearXNG metasearch lookup if the ingredient database lacks local entries.
+
+---
+
+## Slide 5: Antigravity AI Subagents & Task Domains
+
+The development lifecycle utilized specialized Antigravity models to manage distinct segments of the codebase:
+
+* **Expert Coach**: Audited codebase structure, enforced vertical database schema partitioning, and validated format header syntax rules.
+* **Doc Writer**: Kept all documentation, deployment runbooks, and user guides in the `/docs` folder synchronized with source code changes.
+* **Git Commit Governance**: Connected Gogs commits directly to Taiga task boards via custom Webhook APIs.
+* **SQL Optimizer**: Designed index strategies, FULLTEXT subqueries, and views to handle large datasets.
+* **Code Review**: Analyzed PRs and code modifications inside `app.py` and backend scripts to spot syntax errors.
+
+---
+
+## Slide 6: Agent Security & Least Privilege Configuration
+
+To protect the host development environment, the Antigravity agent was run under strict sandboxing limits:
+
+* **File Permissions**: Restricted `read_file` and `write_file` paths exclusively to the workspace folder:
+  `c:\Users\lanfr144\Documents\DOPRO1\Antigravity\Food`
+* **Shell Command Sandbox**: Blocked system commands. Sandboxed commands allowed were:
+  `git`, `python`, `chmod`, `docker-compose`, and `Get-Content`.
+* **Network Permissions**: Restricted URL targets exclusively to local network components:
+  * Database VM host: `192.168.130.170`
+  * Agile tracking board: `192.168.130.161`
+* **Network Egress Guard**: Completely blocked external DNS lookup hooks and remote file downloads.
+
+---
+
+## Slide 7: Technical Reflections: Engineering Challenges
+
+During the lifecycle, the agent resolved three significant environment conflicts:
+
+### 1. Smudge Filter Regex Greediness
+* **Problem**: The git ident regex smudge pattern (`.*?[^$]*?$`) was too greedy, matching across newlines and corrupting python string literals.
+* **Solution**: Rewrote the pattern to be line-restricted (`[^\r\n$]+\$`) and split search literals in Python as string concatenations (e.g. `"$Form"` + `"at:"`).
+
+### 2. Smudge Filter Missing During Checkouts
+* **Problem**: Git checkout processes deleted the smudge python script `git-ident-filter.py` first, causing later file smudges to fail with `FileNotFoundError`.
+* **Solution**: Sequenced checkout commands to pull the filter script first, followed by the rest of the repository.
+
+### 3. French Accent Encoding Conflicts
+* **Problem**: Smudged names with accents (e.g. `Lange François`) caused system file write errors under differing terminal codepages.
+* **Solution**: Implemented python files sanitizer scripts to open streams with `errors='replace'` and force UTF-8 writes.
+
+---
+
+## Slide 8: Enterprise Observability & SNMPv3 Alerts
+
+We configured an agentless and agent-based telemetry system using Zabbix:
+
+### Telemetry Pipeline:
+1. **SNMPv3 User Configuration**: Established a secure user `securityUser` utilizing SHA authentication and AES encryption.
+2. **Telemetry Daemon (`zabbix_telemetry.py`)**: Runs as a daemon querying active databases, app states, and system memory.
+3. **Application Trap Notifications (`snmp_notifier.py`)**: The Streamlit interface automatically emits encrypted SNMPv3 traps on crucial security events (failed logins, password resets, heavy queries).
+4. **Alert Channels**: Connected Zabbix triggers directly to team alert webhooks (Microsoft Teams/Discord) and SMTP mail relays.
+
+---
+
+## Slide 9: Data Privacy Audit & Air-Gap Verification
+
+We verified that zero client details or dietary profile records leave the local subnet boundary:
+
+### Verification Audit Steps:
+1. **Proxy Audit**: Nginx logs verified that all HTTP queries were originating from internal network ranges (such as `192.168.1.50`).
+2. **Database Isolation**: The MySQL container has no external port mapping to public networks. It communicates exclusively via Docker bridge networks.
+3. **Anonymized Queries**: External lookups route through the local SearXNG proxy, stripping identifying tags and tracking cookies.
+4. **Active Traffic Sniffing**: Ran `tcpdump` during active clinician sessions:
+   ```bash
+   tcpdump -i eth0 dst port not 80 and dst port not 22 and dst port not 161
+   ```
+   No packet transmission was detected outbound from the server, confirming absolute privacy.
+
+---
+
+## Slide 10: Day 2 Operations & Future Milestones
+
+The current stable state serves as the starting point for ongoing maintenance operations:
+
+* **SSL/TLS Encryption**: Upgrade the Nginx configuration to support HTTPS (Port 443) using internal certificate authorities.
+* **Database Schema Migrations**: Maintain Alembic scripts to version control new patient profile tables.
+* **Rate Limiting**: Add a sliding-window request throttling algorithm in `app.py` to prevent denial-of-service attempts.
+* **UAT Feedback Integration**: Regularly integrate dietitians' feedback on warning flags to refine warning rules.

二進制
docs/Presentation_Technical.pdf


+ 782 - 0
docs/Presentation_User.html

@@ -0,0 +1,782 @@
+<!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 - Clinician User Manual 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: #080c10;
+            --slide-bg: rgba(13, 17, 23, 0.75);
+            --border-color: rgba(255, 255, 255, 0.08);
+            --primary-accent: #34d399; /* Emerald accent for users (feels medical/fresh) */
+            --secondary-accent: #60a5fa; /* Blue accent */
+            --text-primary: #f0f6fc;
+            --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(52, 211, 153, 0.15);
+        }
+
+        * {
+            box-sizing: border-box;
+            margin: 0;
+            padding: 0;
+        }
+
+        body {
+            background-color: var(--bg-color);
+            background-image: 
+                radial-gradient(at 0% 0%, rgba(52, 211, 153, 0.08) 0, transparent 50%),
+                radial-gradient(at 100% 100%, rgba(96, 165, 250, 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(52, 211, 153, 0.1);
+            border: 1px solid rgba(52, 211, 153, 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.warning-li::before {
+            content: "⚠";
+            color: #ff7b72;
+        }
+
+        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(52, 211, 153, 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;
+        }
+
+        .highlight-text {
+            color: var(--primary-accent);
+            font-weight: 500;
+        }
+
+        /* 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(52, 211, 153, 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;
+        }
+        
+        /* Badges */
+        .badge {
+            display: inline-block;
+            padding: 0.2rem 0.6rem;
+            font-size: 0.8rem;
+            font-weight: 600;
+            border-radius: 4px;
+            text-transform: uppercase;
+        }
+        .badge-red { background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.3); color: #f87171; }
+        .badge-green { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); color: #34d399; }
+        
+    </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">USER GUIDE</span>
+                </div>
+                <div class="slide-body">
+                    <h1 class="slide-title">Local Food AI Platform</h1>
+                    <p class="subtitle">Clinician User Manual & Functional Application Walkthrough</p>
+                    <div class="meta-grid">
+                        <div class="meta-item">
+                            <span class="meta-label">Audience</span>
+                            <span class="meta-value">Clinical Staff & Users</span>
+                        </div>
+                        <div class="meta-item">
+                            <span class="meta-label">Author</span>
+                            <span class="meta-value">Francois Lange</span>
+                        </div>
+                        <div class="meta-item">
+                            <span class="meta-label">Status</span>
+                            <span class="meta-value">Ready (production)</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Domain-specific Project 1 (DSP1) - Clinical Guide</span>
+                    <span>1 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 2: Vision -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Project Vision</span>
+                    <span class="title-badge">PRIVACY FIRST</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">The Vision: A Privacy-First Clinical Dietitian</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Complete HIPAA Compliance</strong>: Patient dietary parameters, medical diagnoses, and recipe histories remain 100% inside your building.</li>
+                                <li><strong>Adaptive AI Specialist</strong>: The local AI engine adapts instantly to user selections (e.g., pregnancy, kidney disease, diabetes).</li>
+                                <li><strong>Fully Local RAG Intelligence</strong>: Bypasses slow and risky cloud queries by retrieving context entirely from in-house databases.</li>
+                            </ul>
+                        </div>
+                        <div class="card" style="border-color: rgba(52, 211, 153, 0.2);">
+                            <div class="card-title"><span class="icon">🔒</span> Air-Gapped Advantage</div>
+                            <div class="card-desc">
+                                "Unlike ChatGPT or consumer dietary trackers, Local Food AI never leaks clinic logs or clinician search strings to third-party databases."
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>2 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 3: Secure Logging -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Access Control</span>
+                    <span class="title-badge">LOGIN & SECURITY</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Secure Authentication & Password Recovery</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Bcrypt Hashed Credentials</strong>: Heavy database level encryption blocks unauthorized entry.</li>
+                                <li><strong>Sidebar Session Status</strong>: Displays the active user, network mode, and active LLM version details.</li>
+                                <li><strong>Outbound Email Recovery</strong>: Integrated password reset routing via secure internal SMTP relays.</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> Cookie Timeout</div>
+                                <div class="card-desc">Secures sessions on shared ward computers, preventing patient detail access.</div>
+                            </div>
+                            <div class="card">
+                                <div class="card-title"><span class="icon">🔑</span> Self-Serve Resets</div>
+                                <div class="card-desc">Request reset loops dispatch direct recovery keys to the clinician's inbox.</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>3 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 4: Data Search -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Clinical Search</span>
+                    <span class="title-badge">DATABASE FILTERS</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Tab 1: High-Speed Database Exploration</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Real-Time Searching</strong>: Access millions of OpenFoodFacts entries in under <span class="highlight-text">0.04 seconds</span>.</li>
+                                <li><strong>Dynamic Warnings</strong>: Automatically overlays warning indicators based on active patient profile constraints.</li>
+                                <li><strong>Flexible Column Config</strong>: Custom selections allow dietitians to inspect precise vitamins and minerals.</li>
+                            </ul>
+                        </div>
+                        <div class="card">
+                            <div class="card-title"><span class="icon">🥗</span> Product Search Cards</div>
+                            <div style="margin-top: 1rem; border-top: 1px solid var(--border-color); padding-top: 1rem;">
+                                <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 0.8rem;">
+                                    <strong>Unpasteurized Brie</strong>
+                                    <span class="badge badge-red">High Risk</span>
+                                </div>
+                                <p style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 1rem;">
+                                    Listeria Risk: Avoid raw/unpasteurized soft cheeses during pregnancy.
+                                </p>
+                                <div style="display:flex; justify-content:space-between; align-items:center;">
+                                    <strong>Raw Spinach</strong>
+                                    <span class="badge badge-green">Recommended</span>
+                                </div>
+                                <p style="font-size: 0.85rem; color: var(--text-secondary);">
+                                    High Iron & Folate: Encourages fetal cell growth.
+                                </p>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>4 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 5: Plate Builder -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Plate Formulation</span>
+                    <span class="title-badge">MY PLATE BUILDER</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Tab 2: My Plate Builder & Portion Parser</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Natural Unit Parser</strong>: Clinicians can enter quantities in plain English (e.g. `1.5 cups`, `2 tablespoons`).</li>
+                                <li><strong>Density Conversions</strong>: Converts volume measurements into metric grams based on food density maps.</li>
+                                <li><strong>Cumulative Totals</strong>: Displays real-time compound macro updates (Proteins, Carbs, Fats).</li>
+                            </ul>
+                        </div>
+                        <div class="card">
+                            <div class="card-title"><span class="icon">📊</span> Macro intake vs Thresholds</div>
+                            <div style="margin-top: 1rem; display:flex; flex-direction:column; gap: 0.6rem;">
+                                <div>
+                                    <div style="display:flex; justify-content:space-between; font-size:0.8rem; margin-bottom:0.2rem;">
+                                        <span>Protein (70g Target)</span>
+                                        <span>52g (74%)</span>
+                                    </div>
+                                    <div style="width:100%; height:8px; background:#222; border-radius:4px; overflow:hidden;">
+                                        <div style="width:74%; height:100%; background:var(--primary-accent);"></div>
+                                    </div>
+                                </div>
+                                <div>
+                                    <div style="display:flex; justify-content:space-between; font-size:0.8rem; margin-bottom:0.2rem;">
+                                        <span>Sodium (2000mg Limit)</span>
+                                        <span>420mg (21%)</span>
+                                    </div>
+                                    <div style="width:100%; height:8px; background:#222; border-radius:4px; overflow:hidden;">
+                                        <div style="width:21%; height:100%; background:var(--secondary-accent);"></div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>5 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 6: Consultation Chat -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Interactive AI</span>
+                    <span class="title-badge">CONSULTATION CHAT</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Tab 3: Clinician Consultation Portal</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Client Profile Dropdown</strong>: Select medical conditions (Diabetes, Anemia, Hypertension, Pregnancy).</li>
+                                <li><strong>Verified database queries (RAG)</strong>: The local model queries local food data first to ensure factual responses.</li>
+                                <li><strong>Chain-of-Thought Explanation</strong>: Explains details step-by-step to help clinicians verify reasoning path safety.</li>
+                            </ul>
+                        </div>
+                        <div class="card" style="background:#161b22; font-family:var(--font-mono); font-size:0.85rem;">
+                            <div style="color:var(--primary-accent); margin-bottom:0.4rem;">&gt; Can my diabetic client eat dates?</div>
+                            <div style="color:var(--text-secondary); margin-bottom:0.4rem;">&lt;thought&gt;
+Checking glycaemic index... Dates contain ~66g sugar per 100g. High GI load. Injected profile is Diabetes.
+&lt;/thought&gt;</div>
+                            <div style="color:#fff;">Dates are high in simple sugars (GI ~103). Recommend limiting portion to &lt;20g and pairing with almonds to slow absorption.</div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>6 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 7: Meal Planner -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Diet Plans</span>
+                    <span class="title-badge">AI MEAL PLANNER</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Tab 4: Automated Clinical Meal Planner</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li><strong>Daily Caloric Targets</strong>: Input targets (e.g. `2000 kcal`) to scale portion recommendations.</li>
+                                <li><strong>Diet constraints integration</strong>: Enter restrictions (e.g. Halal, Vegan, Low-Fat, Kosher).</li>
+                                <li><strong>Printable Grid output</strong>: Formats the entire breakdown (Breakfast, Lunch, Dinner) into clean tables.</li>
+                            </ul>
+                        </div>
+                        <table style="width: 100%; border-collapse: collapse; font-size: 0.85rem; text-align: left;">
+                            <thead>
+                                <tr style="border-bottom: 1px solid var(--border-color); color:#fff;">
+                                    <th style="padding: 0.5rem 0;">Meal</th>
+                                    <th style="padding: 0.5rem 0;">Proposed Food</th>
+                                    <th style="padding: 0.5rem 0;">Kcal</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr style="border-bottom: 1px dashed var(--border-color);">
+                                    <td style="padding: 0.5rem 0;">Breakfast</td>
+                                    <td style="padding: 0.5rem 0; color:var(--primary-accent);">Oatmeal with Walnuts & Bananas</td>
+                                    <td style="padding: 0.5rem 0;">450</td>
+                                </tr>
+                                <tr style="border-bottom: 1px dashed var(--border-color);">
+                                    <td style="padding: 0.5rem 0;">Lunch</td>
+                                    <td style="padding: 0.5rem 0; color:var(--primary-accent);">Quinoa & Roasted Veggie Salad</td>
+                                    <td style="padding: 0.5rem 0;">650</td>
+                                </tr>
+                                <tr>
+                                    <td style="padding: 0.5rem 0;">Dinner</td>
+                                    <td style="padding: 0.5rem 0; color:var(--primary-accent);">Lentil Dahl with Brown Rice</td>
+                                    <td style="padding: 0.5rem 0;">750</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>7 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 8: Privacy Guarantees -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Security</span>
+                    <span class="title-badge">PRIVACY GUARANTEED</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">How Your Patient Data is Protected</h2>
+                    <div class="slide-grid-2">
+                        <div>
+                            <ul>
+                                <li class="warning-li"><strong>No External API Transmissions</strong>: Patient data stays locally in memory. Zero external sharing.</li>
+                                <li><strong>Anonymous SearXNG Meta-Search</strong>: External searches are stripped of cookies and clinic source tags.</li>
+                                <li><strong>Isolated Networks</strong>: System nodes run in private networks, blocking external access.</li>
+                            </ul>
+                        </div>
+                        <div class="card" style="background: rgba(52, 211, 153, 0.05); border-color: rgba(52, 211, 153, 0.2);">
+                            <div class="card-title" style="color: var(--primary-accent);"><span class="icon">🔒</span> Hospital-Grade Data Security</div>
+                            <div class="card-desc" style="color: var(--text-primary);">
+                                All processing occurs inside your facility's firewall. Local databases and local AI engines guarantee complete privacy compliance.
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>8 / 9</span>
+                </div>
+            </div>
+
+            <!-- Slide 9: User Workflow Walkthrough -->
+            <div class="slide">
+                <div class="slide-header">
+                    <span class="tag">Application Use</span>
+                    <span class="title-badge">WORKFLOW STEPS</span>
+                </div>
+                <div class="slide-body">
+                    <h2 class="slide-title">Step-by-Step Clinical Workflow</h2>
+                    <div class="slide-grid-3">
+                        <div class="card">
+                            <div class="card-title">1. Patient Profile</div>
+                            <div class="card-desc">Select active health restrictions and medical conditions in the sidebar selector.</div>
+                        </div>
+                        <div class="card">
+                            <div class="card-title">2. Build Diet Plate</div>
+                            <div class="card-desc">Search foods, add to plate, and adjust portions using natural volumes.</div>
+                        </div>
+                        <div class="card">
+                            <div class="card-title">3. Consult AI</div>
+                            <div class="card-desc">Generate meal plans, check safety flags, and view the AI's clinical reasoning.</div>
+                        </div>
+                    </div>
+                </div>
+                <div class="slide-footer">
+                    <span>Local Food AI - User Presentation</span>
+                    <span>9 / 9</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 9</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>

+ 106 - 0
docs/Presentation_User.md

@@ -0,0 +1,106 @@
+The current version is $Format:LocalFoodAI_lanfr144:Presentation_User.md:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$
+
+
+# Local Food AI - Clinician User Manual Presentation
+
+This presentation slides outline the core features, clinical workflow, and privacy guarantees of the **Local Food AI** system from a clinician's perspective.
+
+---
+
+## Slide 1: Platform Vision & Clinical Core
+
+The **Local Food AI** system is a strictly offline, privacy-first tool designed to assist clinical dietitians and nutritionists.
+
+### Core Value Pillars:
+* **True HIPAA Compliance**: All patient medical queries, health profile selections, and custom diets remain inside your local facility. No data is sent to external cloud APIs.
+* **Intelligent Clinical Guardrails**: The application adapts to specific patient profiles (e.g., pregnancy, diabetes, kidney disease) and automatically flags risks.
+* **Instant In-House Analytics**: Combines raw database search, a portion calculator, and an AI chat consultation into one simple web dashboard.
+
+---
+
+## Slide 2: Accessing the App & Sidebar Indicators
+
+Clinicians can log in securely from any workstation inside the facility network.
+
+### Accessing the Dashboard:
+1. Open your web browser (Chrome, Firefox, or Safari).
+2. Enter the address provided by your IT administrator (e.g., `http://192.168.130.170:8502`).
+3. Log in with your secure clinician credentials.
+
+### Sidebar Controls:
+* **Network Status**: Indicates if the app is in *Online/Server* mode or *Offline/Local Fallback* mode.
+* **LLM Engine**: Shows the active AI reasoning model (e.g., `llama3.2:3b`).
+* **Git Version Header**: Displays the system Git version, date, and commit code for audit logging.
+
+---
+
+## Slide 3: Tab 1: Clinical Data Search (Clinical Search)
+
+This tab enables high-speed lookups against millions of OpenFoodFacts entries.
+
+### Key Capabilities:
+* **Keyword Filter**: Search for products, brands, or barcodes (e.g., "Greek Yogurt" or "Cheddar").
+* **Nutritional Grading**: Displays the Nutri-Score (A through E) and macros per 100g.
+* **Dynamic Indicators**: Matches the product attributes against your active client profile:
+  * **[Warning] Red Flags**: Highlights high-risk ingredients (e.g., Unpasteurized dairy for pregnancy, high-sodium for hypertension, or high-sugar for diabetes).
+  * **[Recommended] Green Flags**: Highlights beneficial components (e.g., High-iron for anemia, high Vitamin C for scurvy).
+
+---
+
+## Slide 4: Tab 2: My Plate Builder (My Plate Builder)
+
+Build custom recipes or track a client's daily meals to calculate cumulative macro and micro-nutrients.
+
+### Features:
+* **Adding Items**: Click **Add to Plate** on any food item from the search results list.
+* **Natural Unit Converter**: Enter quantities using everyday units (e.g., "1.5 cups", "2 tablespoons", "150g"). The system parses the unit and converts it to metric weight based on ingredient density.
+* **Intake Metrics**: Calculates and displays total energy (kcal), proteins, fats, carbohydrates, sugars, and sodium.
+* **Comparison Graph**: Shows a bar chart of the plate's macros against recommended daily intakes.
+
+---
+
+## Slide 5: Tab 3: Consultation Chat (AI Consultation)
+
+Consult the built-in AI assistant to ask clinical questions, verify recipes, or evaluate specific ingredients.
+
+### Workflow:
+1. **Select Client Profile**: Check specific health profiles (Pregnancy, Anemia, Keto, Vegetarian, etc.) in the sidebar.
+2. **Consult the AI**: Type questions (e.g., "Is unpasteurized brie cheese safe for this client?" or "Suggest high-iron snacks").
+3. **Verified RAG Search**: The AI queries the local database and private search engines first to verify nutritional facts before answering.
+4. **Reasoning Steps**: View the AI's step-by-step reasoning explaining how it checked the ingredients against the client's medical conditions.
+
+---
+
+## Slide 6: Tab 4: AI Meal Planner (AI Planner)
+
+Generate customized, multi-meal dietary schedules directly from the clinician dashboard.
+
+### Features:
+* **Enforce Restrictions**: Enter the daily target calorie count (e.g., "2000 kcal") and select dietary constraints (e.g., Kosher, Vegan, Low Fat).
+* **Automated Diet Grid**: The local LLM queries the food database and generates a structured breakfast, lunch, and dinner table.
+* **Printable Output**: Outputs a clean Markdown table summarizing the meals, ingredients, and total daily nutritional values.
+
+---
+
+## Slide 7: Security & Resetting Credentials
+
+The system protects both patient privacy and clinician accounts.
+
+### Authentication Guidelines:
+* **Cookie-Based Sessions**: Your session is securely saved in your browser cookie jar. Click **Logout** in the sidebar to terminate it.
+* **Password Resets**:
+  1. Click **Reset Password** in the sidebar.
+  2. Input your username.
+  3. A secure link will be sent to your registered email address.
+  4. Access the link to set a new password complying with local complexity requirements.
+
+---
+
+## Slide 8: Privacy Guarantees: Keeping Data Local
+
+Why Local Food AI is safer than general-purpose online chatbots:
+
+* **No Cloud Sharing**: Traditional AI apps send your prompt history to external corporate servers, risking leaks of confidential patient data.
+* **Local Processing**: Your LLM engine and databases run entirely inside your facility's firewall.
+* **Anonymous Web Queries**: If the AI needs to check the web for a rare ingredient, it routes the query through an anonymous local proxy (SearXNG).
+* **IT Verification**: Traffic audits (tcpdump logs) confirm that zero prompt logs or profile details leave the server boundary.

二進制
docs/Presentation_User.pdf


二進制
docs/Recommendations.pdf


二進制
docs/Scrum_Artifacts.pdf


二進制
docs/Scrum_Daily.pdf


二進制
docs/Scrum_Plan.pdf


二進制
docs/Scrum_Retro.pdf


二進制
docs/Scrum_Review.pdf


二進制
docs/Scrum_Wiki.pdf


二進制
docs/Start_Stop_Procedures.pdf


二進制
docs/Technical_Document.pdf


二進制
docs/Test_Cases_Sprint8.pdf


二進制
docs/URL_Formats.pdf


+ 21 - 2
docs/Uninstall_Guide.md

@@ -24,9 +24,28 @@ docker compose down -v
 *Note: The `-v` flag is critical as it completely purges the MySQL persistent data directories.*
 
 ### Step 1.2: Remove Local Project Docker Images
-Clean up the built application images from the local Docker cache:
+List the active images associated with the project using:
 ```bash
-docker rmi food-app food-ingest
+docker image ls | grep -E "food_project|zabbix|searxng|ollama|nginx|airflow"
+```
+Example output:
+```text
+IMAGE                                             ID             DISK USAGE   CONTENT SIZE   EXTRA
+apache/airflow:2.8.1                              e5560ad0b86e       1.94GB          387MB    U
+food_project-app:latest                           3cfb2e77efc7        1.4GB          326MB    U
+food_project-ingest:latest                        6e120f3c9946        1.4GB          326MB
+food_project-mysql:latest                         6f33177ab088       1.09GB          234MB    U
+nginx:latest                                      6e23479198b9        240MB         65.8MB    U
+ollama/ollama:latest                              5600a652d108       10.1GB         3.77GB    U
+searxng/searxng:latest                            25ff3c045548        375MB         96.4MB    U
+zabbix/zabbix-agent:ubuntu-7.0-latest             db7d6490f39a        202MB         56.2MB    U
+zabbix/zabbix-server-mysql:ubuntu-7.0-latest      78c2e2f75e8d        479MB          121MB    U
+zabbix/zabbix-web-nginx-mysql:ubuntu-7.0-latest   11b12c432e1b        557MB          145MB    U
+```
+
+To permanently remove all the images related to the application stack from the local cache:
+```bash
+docker rmi food_project-app:latest food_project-ingest:latest food_project-mysql:latest nginx:latest ollama/ollama:latest searxng/searxng:latest zabbix/zabbix-agent:ubuntu-7.0-latest zabbix/zabbix-server-mysql:ubuntu-7.0-latest zabbix/zabbix-web-nginx-mysql:ubuntu-7.0-latest apache/airflow:2.8.1
 ```
 
 ### Step 1.3: Clean Up Virtual Environments, Logs & Backups

二進制
docs/Uninstall_Guide.pdf


+ 1 - 1
docs/User_Description.md

@@ -20,7 +20,7 @@ Allows practitioners to search the 24GB OpenFoodFacts dataset in real time (aver
 - **Flexible Column Customization**: Multi-select column headers to inspect specific macro and micro-nutrients.
 
 ### Tab 2: AI Clinical Chat (AI Chat)
-An interactive NLP dialogue interface powered by a local lightweight LLM (**Qwen2.5:7b**).
+An interactive NLP dialogue interface powered by a local lightweight LLM (**llama3.2:3b**).
 - **RAG-Driven Precision**: The AI dietitian automatically retrieves and reviews local database records and private meta-search results before formulating an answer.
 - **Dynamic Medical Guardrails**: The user's active illnesses, diets, and conditions are injected into the AI's system prompt in the background, forcing the AI to strictly enforce clinical safety constraints.
 

二進制
docs/User_Description.pdf


二進制
docs/User_Guide.pdf


二進制
docs/WSL_Deployment.pdf


+ 3 - 0
docs/Wiki_Home.md

@@ -8,6 +8,9 @@ Welcome to the static documentation portal. Please navigate the guides below:
 - **[Detailed Installation and Deployment Guide](Installation_Guide.md)**
 - **[Clinician User Manual](User_Guide.md)**
 - **[Technical Capstone Document](Technical_Document.md)**
+- **[Environment Variables Configuration Runbook](Env_Configuration.md)**
 - **[Agile Scrum Wiki Portal](Scrum_Wiki.md)**
 - **[System Uninstallation Guide](Uninstall_Guide.md)**
 - **[Recommendations for Future Projects](Recommendations.md)**
+- **[Technical Presentation Slides (IT Audience)](Presentation_Technical.md)** *(Interactive: [Presentation_Technical.html](Presentation_Technical.html))*
+- **[User Manual Presentation Slides (Customer Audience)](Presentation_User.md)** *(Interactive: [Presentation_User.html](Presentation_User.html))*

二進制
docs/Wiki_Home.pdf


二進制
docs/architecture.pdf


二進制
docs/disaster_recovery_plan.pdf


+ 1 - 1
docs/distributed_deployment.md

@@ -41,7 +41,7 @@ python scripts/setup_deploy.py
 The script will ask you for:
 1. **Node Role**: Choose whether this node is the Database, the Application Frontend, or the Monitoring hub.
 2. **Network IPs**: If you are setting up the Application node, it will ask you for the IP address of the Database node (e.g., the Hyper-V VM IP).
-3. **Credentials**: It will securely generate a local `.env` file containing your passwords so they are not committed to Git.
+3. **Credentials**: It will securely generate a local `.env` file containing your passwords (refer to the [Environment Variables Configuration Runbook](Env_Configuration.md) for variable explanations).
 
 ### 3. Deploy Docker
 Once the script generates the role-specific `docker-compose.yml`, run:

二進制
docs/distributed_deployment.pdf


+ 1 - 1
docs/docker_connection.md

@@ -28,7 +28,7 @@ To manage LLM models or test the AI engine:
 docker exec -it food-ollama-1 bash
 # Inside the container, you can run:
 # ollama list
-# ollama run qwen2.5:1.5b
+# ollama run llama3.2:3b
 ```
 
 ### 3. SearXNG (Web Search)

二進制
docs/docker_connection.pdf


+ 5 - 5
docs/project_report.md

@@ -12,7 +12,7 @@ The current version is #ident "@(#)$Format:LocalFoodAI_lanfr144:project_report.m
 The **Local Food AI** capstone project has successfully completed all sprint iterations. The system stands fully verified, containerized, and documented. 
 
 ### What Has Been Done
-1. **Model Upgraded to Ollama Latest**: Transitioned from the `llama3.2:3b` model to the much more robust, large reasoning-focused **`qwen2.5:7b`** model (4.7 GB) with structured XML Chain-of-Thought (CoT) calculations. Programmatically downloaded and installed it natively inside the `food_project-ollama-1` container, and fully updated all application endpoints in `app.py`.
+1. **Model Configured with Ollama**: Deployed the local instruction-following **`llama3.2:3b`** model (2.2 GB) with structured XML Chain-of-Thought (CoT) calculations. Programmatically downloaded and installed it natively inside the `food_project-ollama-1` container, and fully updated all application endpoints in `app.py`.
 2. **Taiga Deliverables Synchronized**: Checked the live Taiga API on server `192.168.130.161`. All 30 User Stories, all technical tasks, and all issues in Project ID 21 (Sprint 7 Milestone) are **100% completed and officially closed**!
 3. **Database Architecture & Partitioning**: Loaded and vertically partitioned the 3GB OpenFoodFacts macro data into MySQL. Configured matching FULLTEXT engines to search records in less than **0.04s** (averaging 90% latency reduction).
 4. **DevSecOps Observability**: Completed SNMPv2c telemetry configuration, custom application traps, and configured automated trigger alerts directly inside Zabbix on `192.168.130.170`.
@@ -33,7 +33,7 @@ Below is an exhaustive catalog of every critical file in the repository, detaili
 | **ingest_csv.py**<br>`./ingest_csv.py` | High-performance background database loader. Stream-reads and batch-inserts the 3GB OpenFoodFacts dataset into MySQL using Pandas chunking and optimizes indices post-load. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **unit_converter.py**<br>`./unit_converter.py` | Mathematical converter engine that parses natural recipe volume inputs (e.g. cups, spoons) and converts them to metric weights based on macro density mappings. | `e758e4f` | Lange François | 2026/06/15 04:29:33 | *[#1] Update regenerated documentation PDFs and Git filters placeholders* |
 | **snmp_notifier.py**<br>`./snmp_notifier.py` | Observability SNMP utility. Formulates and transmits raw SNMP trap payloads to the central Zabbix monitoring server on critical application failures. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
-| **configure_zabbix_alerts.py**<br>`./configure_zabbix_alerts.py` | DevOps provisioning script. Uses the Zabbix API to automatically set up host groups, custom templates, items, triggers, actions, and media types for alerts. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
+| **configure_zabbix_alerts.py**<br>`./configure_zabbix_alerts.py` | DevOps provisioning script. Uses the Zabbix API to automatically set up host groups, custom templates, items, triggers, actions, and media types for alerts. | `4fa24a4` | Lange François | 2026/06/17 14:10:03 | *[#1] Fix page 2 text loss, scale header logos, add DOPRO1 to footer, purge hardcoded secrets, and add header/footer guidelines* |
 | **configure_zabbix_email.py**<br>`./configure_zabbix_email.py` | Security & Monitoring. Configures email media types and SMTP server routes for Zabbix alert notifications on system downtime. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **zabbix_telemetry.py**<br>`./zabbix_telemetry.py` | Monitoring agent daemon. Queries active application statistics, memory, and query timers to supply Zabbix telemetry indicators. | `e758e4f` | Lange François | 2026/06/15 04:29:33 | *[#1] Update regenerated documentation PDFs and Git filters placeholders* |
 | **check_users.py**<br>`./check_users.py` | Security utility. Verifies user accounts inside the MySQL `users` table and checks password hashing complexity. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
@@ -48,7 +48,7 @@ Below is an exhaustive catalog of every critical file in the repository, detaili
 | **download_csv.sh**<br>`./download_csv.sh` | Downloader helper script that fetches specific smaller subsets of OpenFoodFacts CSV files. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **master_trigger.sh**<br>`./master_trigger.sh` | Orchestrator script that wakes and verifies multiple secondary subservices in sequence. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **manage_services.sh**<br>`./manage_services.sh` | DevOps service manager script. Handles automated, sequential startup, shutdown, restart, and health checking of all container elements in the stack. | `1e316d1` | Lange François | 2026/06/16 09:25:09 | *[#1] Render Mermaid flowcharts, relative cross-linking, and clean up RCS $ placeholders* |
-| **generate_docs.py**<br>`./generate_docs.py` | Dynamic doc generator. Generates and mirrors all markdown manuals under `/docs` with live Git log metadata injection. | `2a8ed05` | Lange François | 2026/06/16 21:48:22 | *[#1] Fix PDF fonts layout margins and installation guide steps* |
+| **generate_docs.py**<br>`./generate_docs.py` | Dynamic doc generator. Generates and mirrors all markdown manuals under `/docs` with live Git log metadata injection. | `96165a3` | Lange François | 2026/06/17 12:26:52 | *[#1] Fix PDF invisible text layer, Acrobat Reader save on close warning, custom headers and footers, and database partitioning Mermaid chart* |
 | **docker-compose.yml**<br>`./docker-compose.yml` | Main 10-container Docker orchestration map defining MySQL, App UI, Ollama Engine, SearXNG, Nginx proxy, Airflow stack, and Zabbix server suites. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **docker-compose_skip.yml**<br>`./docker-compose_skip.yml` | Resilient 8-container offline/local single-node orchestration manifest. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **docker-compose-wsl.yml**<br>`./docker-compose-wsl.yml` | WSL2-specific Docker Compose configuration file. Configures services with a +20 port shift to guarantee zero port conflicts on developer workstations. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
@@ -57,9 +57,9 @@ Below is an exhaustive catalog of every critical file in the repository, detaili
 | **.env**<br>`./.env` | Secret storage container holding encrypted MySQL user passwords and active environment flags. | `ca3877d` | lanfr144 | 2026/05/13 11:15:42 | *Stop save the .env file* |
 | **.gitattributes**<br>`./.gitattributes` | Git clean/smudge layout mapping enabling automatic tracking of dynamic #ident "@(#)$Format:LocalFoodAI_lanfr144:project_report.md:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$" metadata expansion within version files. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **requirements.txt**<br>`./requirements.txt` | Python runtime dependency catalog storing strict library versioning constraints. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
-| **INSTALL_WSL.md**<br>`./INSTALL_WSL.md` | WSL2 deployment guide. Provides step-by-step instructions for installing and deploying the application inside WSL2 with port shifts. | `129180a` | Lange François | 2026/06/15 12:49:05 | *[#1] Add Developer and Agent Productivity Guidelines to guides and agent workflows* |
+| **INSTALL_WSL.md**<br>`./INSTALL_WSL.md` | WSL2 deployment guide. Provides step-by-step instructions for installing and deploying the application inside WSL2 with port shifts. | `4fa24a4` | Lange François | 2026/06/17 14:10:03 | *[#1] Fix page 2 text loss, scale header logos, add DOPRO1 to footer, purge hardcoded secrets, and add header/footer guidelines* |
 | **taiga/local-food-ai-1-36f35ff9-da1b-4eb5-9309-058448c998ad.json**<br>`./taiga/local-food-ai-1-36f35ff9-da1b-4eb5-9309-058448c998ad.json` | Historical Taiga Agile export. Contains the complete project history, including all closed user stories, tasks, and sprint configurations. | `d768ead` | Lange François | 2026/06/01 07:44:40 | *TG-221 #closed - Last commit to sync all the file to ship to the teacher.* |
-| **scripts/generate_pdfs.py**<br>`./scripts/generate_pdfs.py` | PDF document builder. Converts all markdown documentation manuals under `/docs` into high-fidelity PDF format with expanded Git version headers. | `2a8ed05` | Lange François | 2026/06/16 21:48:22 | *[#1] Fix PDF fonts layout margins and installation guide steps* |
+| **scripts/generate_pdfs.py**<br>`./scripts/generate_pdfs.py` | PDF document builder. Converts all markdown documentation manuals under `/docs` into high-fidelity PDF format with expanded Git version headers. | `bc29bef` | Lange François | 2026/06/17 14:16:34 | *[#1] Center title between header logos, draw images in background, and shift top content margin to prevent collisions* |
 | **scripts/generate_project_report.py**<br>`./scripts/generate_project_report.py` | Technical project report generator. Automatically gathers codebase structure, Git commit metadata, and purpose records to construct the Project.pdf report. | `2a1cfbe` | Lange François | 2026/06/16 19:21:55 | *[#1] Fix code block line wrapping and replace literal $ placeholders with correct #ident formatting in descriptions* |
 | **scripts/setup_deploy.py**<br>`./scripts/setup_deploy.py` | DevOps deployment script. Orchestrates local and VM container sets, verifying network connectivity and system parameters. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |
 | **scripts/taiga_sync_final.py**<br>`./scripts/taiga_sync_final.py` | Taiga automated synchronization helper. Pushes bug tickets, fills wiki pages, and assigns unassigned user stories. | `a701972` | Lange François | 2026/06/16 18:25:32 | *[#1] Fix PDF corruption via renormalization and add Uninstall Guide for WSL and Windows* |

二進制
docs/project_report.pdf


二進制
docs/retro_planning.pdf


+ 1 - 1
docs/taiga_audit_report.md

@@ -96,7 +96,7 @@ The current version is #ident "@(#)$Format:LocalFoodAI_lanfr144:taiga_audit_repo
   - `[x]` Task #216: Configure Docker Log Rotation Limits (Closed)
   - `[x]` Task #217: Develop Automated Disaster Recovery Validation Script (Closed)
   - `[x]` Task #218: Tune MySQL Database Buffer Pools and Performance Parameters (Closed)
-  - `[x]` Task #220: Task 1: Update local LLM to Qwen2.5 (7B) (Closed)
+  - `[x]` Task #220: Task 1: Update local LLM to Llama3.2 (3B) (Closed)
   - `[x]` Task #221: Task 2: Refactor backend prompts to utilize <scratchpad> CoT structure (Closed)
   - `[x]` Task #222: Task 3: Implement Python parsing function to strip <scratchpad> block (Closed)
 ### [US-10] US-7: Freely chat about anything related to nutrition and get competent answers (Status: Done)

二進制
docs/taiga_audit_report.pdf


二進制
docs/zabbix_monitoring.pdf


+ 97 - 5
generate_docs.py

@@ -331,7 +331,7 @@ chmod +x data_sync.sh backup_db.sh manage_services.sh \
 
 Follow the standard runbook to initialize credentials and launch services:
 ```bash
-# 1. Create a local [.env file](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/.env) based on step 3 guidelines
+# 1. Create a local [.env file](file:///C:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/.env) based on the [Environment Variables Configuration Runbook](Env_Configuration.md) guidelines
 # 2. Run the service manager to spin up containers
 ./manage_services.sh start
 ```
@@ -612,9 +612,12 @@ Welcome to the static documentation portal. Please navigate the guides below:
 - **[Detailed Installation and Deployment Guide](Installation_Guide.md)**
 - **[Clinician User Manual](User_Guide.md)**
 - **[Technical Capstone Document](Technical_Document.md)**
+- **[Environment Variables Configuration Runbook](Env_Configuration.md)**
 - **[Agile Scrum Wiki Portal](Scrum_Wiki.md)**
 - **[System Uninstallation Guide](Uninstall_Guide.md)**
 - **[Recommendations for Future Projects](Recommendations.md)**
+- **[Technical Presentation Slides (IT Audience)](Presentation_Technical.md)** *(Interactive: [Presentation_Technical.html](Presentation_Technical.html))*
+- **[User Manual Presentation Slides (Customer Audience)](Presentation_User.md)** *(Interactive: [Presentation_User.html](Presentation_User.html))*
 """,
     "Scrum_Wiki.md": """The current version is #ident "@(#)$Format:LocalFoodAI_lanfr144:generate_docs.py:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
 
@@ -727,7 +730,7 @@ Allows practitioners to search the 24GB OpenFoodFacts dataset in real time (aver
 - **Flexible Column Customization**: Multi-select column headers to inspect specific macro and micro-nutrients.
 
 ### Tab 2: AI Clinical Chat (AI Chat)
-An interactive NLP dialogue interface powered by a local lightweight LLM (**Qwen2.5:7b**).
+An interactive NLP dialogue interface powered by a local lightweight LLM (**llama3.2:3b**).
 - **RAG-Driven Precision**: The AI dietitian automatically retrieves and reviews local database records and private meta-search results before formulating an answer.
 - **Dynamic Medical Guardrails**: The user's active illnesses, diets, and conditions are injected into the AI's system prompt in the background, forcing the AI to strictly enforce clinical safety constraints.
 
@@ -873,7 +876,7 @@ To maximize CPU/GPU efficiency and secure database read/writes, services are dis
 | :--- | :--- | :--- |
 | **streamlit-app (app.py)** | Local WSL2 (Windows) | Low-latency rendering and direct client access |
 | **mysql (Database Node)** | Hyper-V VM (Server A) | Persistent enterprise-grade disk storage |
-| **ollama (NLP Qwen2.5:7b Engine)** | VirtualBox VM (Server B) | Dedicated CPU/GPU virtualization allocation |
+| **ollama (NLP llama3.2:3b Engine)** | VirtualBox VM (Server B) | Dedicated CPU/GPU virtualization allocation |
 | **zabbix-server & web (Monitoring)** | Hyper-V VM (Server A) | Centralized SNMPv3 alert processing and logs |
 | **searxng (Meta-Search Gateway)** | Local WSL2 (Windows) | Dynamic browser-level loopbacks |
 
@@ -1055,9 +1058,28 @@ docker compose down -v
 *Note: The `-v` flag is critical as it completely purges the MySQL persistent data directories.*
 
 ### Step 1.2: Remove Local Project Docker Images
-Clean up the built application images from the local Docker cache:
+List the active images associated with the project using:
 ```bash
-docker rmi food-app food-ingest
+docker image ls | grep -E "food_project|zabbix|searxng|ollama|nginx|airflow"
+```
+Example output:
+```text
+IMAGE                                             ID             DISK USAGE   CONTENT SIZE   EXTRA
+apache/airflow:2.8.1                              e5560ad0b86e       1.94GB          387MB    U
+food_project-app:latest                           3cfb2e77efc7        1.4GB          326MB    U
+food_project-ingest:latest                        6e120f3c9946        1.4GB          326MB
+food_project-mysql:latest                         6f33177ab088       1.09GB          234MB    U
+nginx:latest                                      6e23479198b9        240MB         65.8MB    U
+ollama/ollama:latest                              5600a652d108       10.1GB         3.77GB    U
+searxng/searxng:latest                            25ff3c045548        375MB         96.4MB    U
+zabbix/zabbix-agent:ubuntu-7.0-latest             db7d6490f39a        202MB         56.2MB    U
+zabbix/zabbix-server-mysql:ubuntu-7.0-latest      78c2e2f75e8d        479MB          121MB    U
+zabbix/zabbix-web-nginx-mysql:ubuntu-7.0-latest   11b12c432e1b        557MB          145MB    U
+```
+
+To permanently remove all the images related to the application stack from the local cache:
+```bash
+docker rmi food_project-app:latest food_project-ingest:latest food_project-mysql:latest nginx:latest ollama/ollama:latest searxng/searxng:latest zabbix/zabbix-agent:ubuntu-7.0-latest zabbix/zabbix-server-mysql:ubuntu-7.0-latest zabbix/zabbix-web-nginx-mysql:ubuntu-7.0-latest apache/airflow:2.8.1
 ```
 
 ### Step 1.3: Clean Up Virtual Environments, Logs & Backups
@@ -1108,6 +1130,76 @@ Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-
 Disable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
 ```
 *Note: A system reboot is required to complete this step.*
+""",
+    "Env_Configuration.md": """The current version is #ident "@(#)$Format:LocalFoodAI_lanfr144:generate_docs.py:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
+
+
+# Local Food AI - Environment Variables Configuration Runbook
+
+This runbook catalogs every configuration key in the `.env` file, explaining its functional purpose, dependency context, and providing fillable fields for custom deployment setups.
+
+---
+
+## 1. Network & System Mode Configuration
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `NETWORK_MODE` | Controls external request checks. Set to `server` to enable telemetry alerts and database triggers, or `local` for offline development/standalone fallback. | `server` | `____________________` |
+| `LLM_MODEL` | The local Large Language Model loaded into Ollama. Default is `llama3.2:3b` for quantized local inference. | `llama3.2:3b` | `____________________` |
+
+---
+
+## 2. Database Credentials (MySQL)
+
+These credentials configure standard user authentication permissions on the vertical partitions.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `MYSQL_ROOT_PASSWORD` | MySQL root administrative account access password. | `your_mysql_root_pass` | `____________________` |
+| `DB_READER_PASS` | Password for the low-privilege `food_reader` database account. | `your_db_reader_pass` | `____________________` |
+| `DB_LOADER_PASS` | Password for the `food_loader` account executing bulk CSV imports. | `your_db_loader_pass` | `____________________` |
+| `DB_APP_AUTH_PASS` | Password for the `food_app_auth` credential checker role. | `your_db_auth_pass` | `____________________` |
+| `MYSQL_ZABBIX_PASSWORD` | Password for the Zabbix DB user connection profile. | `your_mysql_zabbix_pass` | `____________________` |
+
+---
+
+## 3. Zabbix & SNMPv3 Configuration
+
+Configures the monitoring server APIs and SNMPv3 security parameters.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `ZABBIX_URL` | Endpoint URL for Zabbix API script queries. | `http://your_zabbix_host:8081` | `____________________` |
+| `ZABBIX_USER` | Admin dashboard username for API provisioning. | `your_zabbix_user` | `____________________` |
+| `ZABBIX_PASS` | Zabbix admin dashboard password. | `your_zabbix_pass` | `____________________` |
+| `ZABBIX_SNMP_USER` | Secure SNMPv3 routing user identifier. | `your_snmp_user` | `____________________` |
+| `ZABBIX_SNMP_AUTHKEY` | SNMPv3 SHA authentication key (minimum 8 characters). | `your_snmp_authkey` | `____________________` |
+| `ZABBIX_SNMP_PRIVKEY` | SNMPv3 AES encryption key (minimum 8 characters). | `your_snmp_privkey` | `____________________` |
+| `DISCORD_WEBHOOK` | External webhook URL for automated alerts. | `https://discord.com/api/webhooks/your_webhook_id` | `____________________` |
+
+---
+
+## 4. Alert Mail Gateways & PM Integration
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `EMAIL_USER` | SMTP server username for password recovery alerts. | `your_email_user@example.com` | `____________________` |
+| `EMAIL_PASS` | SMTP server authentication password. | `your_email_password` | `____________________` |
+| `TAIGA_URL` | Endpoint path for the Taiga Agile Kanban board server. | `http://your_taiga_host/taiga` | `____________________` |
+| `TAIGA_USER` | API authentication account username. | `your_taiga_user` | `____________________` |
+| `TAIGA_PASS` | Password for the API sync user. | `your_taiga_pass` | `____________________` |
+
+---
+
+## 5. VM Server Remote Deployments
+
+Credentials required to manage SSH keys and remote container processes from the client.
+
+| Parameter | Purpose & Description | Example Value | Your Custom Value |
+| :--- | :--- | :--- | :--- |
+| `SERVER_HOST` | Remote server IP address (Server A hypervisor). | `your_server_ip` | `____________________` |
+| `SERVER_USER` | SSH login username for command line deployments. | `your_ssh_user` | `____________________` |
+| `SERVER_PASS` | SSH login password for VM operations. | `your_ssh_pass` | `____________________` |
 """
 }
 

+ 12 - 12
manage_services.sh

@@ -55,7 +55,7 @@ start_services() {
 
     # Step 1: Start MySQL Database
     log_info "Step 1/4: Starting MySQL Database Container..."
-    docker compose -f "$COMPOSE_FILE" up -d mysql
+    docker-compose -f "$COMPOSE_FILE" up -d mysql
     
     # Wait for MySQL to become fully ready and accept connections
     log_info "Waiting for MySQL database socket to be available..."
@@ -66,7 +66,7 @@ start_services() {
             DB_ROOT_PASS="$ENV_PASS"
         fi
     fi
-    until docker compose -f "$COMPOSE_FILE" exec mysql mysqladmin ping -h"localhost" -u"root" -p"$DB_ROOT_PASS" --silent; do
+    until docker-compose -f "$COMPOSE_FILE" exec mysql mysqladmin ping -h"localhost" -u"root" -p"$DB_ROOT_PASS" --silent; do
         sleep 2
         echo -n "."
     done
@@ -75,7 +75,7 @@ start_services() {
 
     # Step 2: Start local AI Engine (Ollama) and Anonymous Search (SearXNG)
     log_info "Step 2/4: Starting Ollama and SearXNG microservices..."
-    docker compose -f "$COMPOSE_FILE" up -d ollama searxng
+    docker-compose -f "$COMPOSE_FILE" up -d ollama searxng
     
     # Wait briefly for Ollama daemon bind
     sleep 3
@@ -83,17 +83,17 @@ start_services() {
 
     # Step 3: Start Core Streamlit Application UI and Nginx Gateway Proxy
     log_info "Step 3/4: Starting Streamlit UI and Nginx Proxy Gateway..."
-    docker compose -f "$COMPOSE_FILE" up -d app nginx
+    docker-compose -f "$COMPOSE_FILE" up -d app nginx
     log_success "Frontend and Proxy elements online."
 
     # Step 4: Start DevOps Orchestrator Stack (Zabbix Monitoring, Zabbix Agent, Airflow)
     log_info "Step 4/4: Deploying Zabbix Monitoring suite and Airflow Supervisors..."
     # Note: Airflow scheduler/webserver are started for pipeline supervision
-    docker compose -f "$COMPOSE_FILE" up -d zabbix-server zabbix-web zabbix-agent
+    docker-compose -f "$COMPOSE_FILE" up -d zabbix-server zabbix-web zabbix-agent
     
     # Check if Airflow service elements exist in compose file before starting
     if grep -q "airflow-webserver" "$COMPOSE_FILE"; then
-        docker compose -f "$COMPOSE_FILE" up -d airflow-webserver airflow-scheduler || log_warn "Airflow containers not defined or failed to start."
+        docker-compose -f "$COMPOSE_FILE" up -d airflow-webserver airflow-scheduler || log_warn "Airflow containers not defined or failed to start."
     fi
 
     log_success "All Local Food AI development services started successfully!"
@@ -104,25 +104,25 @@ stop_services() {
 
     # Step 1: Stop Monitoring and Supervisor Stack
     log_info "Step 1/4: Stopping Zabbix suite and Airflow Supervisors..."
-    docker compose -f "$COMPOSE_FILE" stop zabbix-agent zabbix-web zabbix-server
+    docker-compose -f "$COMPOSE_FILE" stop zabbix-agent zabbix-web zabbix-server
     if grep -q "airflow-webserver" "$COMPOSE_FILE"; then
-        docker compose -f "$COMPOSE_FILE" stop airflow-scheduler airflow-webserver || true
+        docker-compose -f "$COMPOSE_FILE" stop airflow-scheduler airflow-webserver || true
     fi
     log_success "DevOps monitoring stack shut down."
 
     # Step 2: Stop Streamlit Application and Secure Proxy Gateway
     log_info "Step 2/4: Shutting down Nginx and App frontend..."
-    docker compose -f "$COMPOSE_FILE" stop nginx app
+    docker-compose -f "$COMPOSE_FILE" stop nginx app
     log_success "Application frontend shut down."
 
     # Step 3: Stop AI Ollama Inference Engine and SearXNG Search Gateway
     log_info "Step 3/4: Stopping SearXNG and Ollama AI Engine..."
-    docker compose -f "$COMPOSE_FILE" stop searxng ollama
+    docker-compose -f "$COMPOSE_FILE" stop searxng ollama
     log_success "AI services shut down."
 
     # Step 4: Stop Core MySQL Database Container gracefully (prevent table corruption)
     log_info "Step 4/4: Stopping MySQL database..."
-    docker compose -f "$COMPOSE_FILE" stop mysql
+    docker-compose -f "$COMPOSE_FILE" stop mysql
     log_success "Database node cleanly shut down."
 
     log_success "All Local Food AI services stopped gracefully!"
@@ -130,7 +130,7 @@ stop_services() {
 
 check_status() {
     log_info "Inspecting status of stack containers..."
-    docker compose -f "$COMPOSE_FILE" ps
+    docker-compose -f "$COMPOSE_FILE" ps
     
     log_info "Active network sockets check:"
     echo "---------------------------------------------------------"

+ 22 - 0
scratch/debug_pdf.py

@@ -0,0 +1,22 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Let's clean up file:/// paths just like in generate_pdfs.py
+root_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food"
+md_content = md_content.replace("file:///" + root_dir + "/docs/", "")
+md_content = md_content.replace("file:///" + root_dir + "/", "../")
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=root_dir)
+html_content = pdf.add_section(section)
+
+print("HTML CONTENT LENGTH:", len(html_content))
+with open("scratch/inspect_html.html", "w", encoding="utf-8") as f:
+    f.write(html_content)
+print("Saved HTML to scratch/inspect_html.html")

+ 84 - 0
scratch/debug_pre_issue.py

@@ -0,0 +1,84 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Current CSS from generate_pdfs.py
+user_css = """
+* {
+    color: #1a1a1a !important;
+}
+body {
+    font-family: 'Helvetica', 'Arial', sans-serif !important;
+    color: #1a1a1a !important;
+    background-color: #ffffff !important;
+}
+h1, h2, h3, h4, h5, h6, h1 *, h2 *, h3 *, h4 *, h5 *, h6 * {
+    color: #000000 !important;
+    margin-top: 18px !important;
+    margin-bottom: 8px !important;
+}
+pre {
+    background-color: #f8f9fa !important;
+    border: 1px solid #e9ecef !important;
+    padding: 2px !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+    border-radius: 3px !important;
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+pre code, pre * {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #212529 !important;
+    background-color: #f8f9fa !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+code {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #b02a37 !important;
+    background-color: #f8f9fa !important;
+    padding: 2px 4px !important;
+    border-radius: 3px !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+a, a * {
+    color: #0d6efd !important;
+}
+blockquote, blockquote * {
+    color: #555555 !important;
+    border-left: 4px solid #ccc !important;
+    padding-left: 10px !important;
+}
+table, tr, td, th, table * {
+    color: #1a1a1a !important;
+    border-color: #cccccc !important;
+}
+th {
+    background-color: #f2f2f2 !important;
+}
+ul, li {
+    list-style-type: disc !important;
+}
+"""
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=docs_dir)
+pdf.add_section(section, user_css=user_css)
+pdf.save("scratch/debug_install.pdf")
+
+doc = fitz.open("scratch/debug_install.pdf")
+print("Text on Page 2:")
+print(doc[1].get_text())
+doc.close()

+ 23 - 0
scratch/find_am_png.py

@@ -0,0 +1,23 @@
+import os
+
+search_paths = [
+    r"c:\Users\lanfr144\Documents\DOPRO1",
+    r"c:\Users\lanfr144\Documents",
+    r"c:\Users\lanfr144"
+]
+
+found_paths = []
+for base_path in search_paths:
+    if not os.path.exists(base_path):
+        continue
+    print(f"Searching in {base_path}...")
+    for root, dirs, files in os.walk(base_path):
+        # Limit search depth to avoid extremely long runtime
+        if root.count(os.sep) - base_path.count(os.sep) > 4:
+            continue
+        if "am.png" in files:
+            found_path = os.path.join(root, "am.png")
+            found_paths.append(found_path)
+            print(f"FOUND: {found_path}")
+
+print("Search complete. Found:", found_paths)

+ 25 - 0
scratch/find_am_png_everywhere.py

@@ -0,0 +1,25 @@
+import os
+
+found = []
+# Search in key user areas first
+search_roots = [
+    r"c:\Users\lanfr144",
+    r"c:\Users\Default",
+    r"c:\ProgramData"
+]
+
+for root_dir in search_roots:
+    if not os.path.exists(root_dir):
+        continue
+    print(f"Scanning {root_dir}...")
+    for root, dirs, files in os.walk(root_dir):
+        # Skip node_modules, .git, venv
+        dirs[:] = [d for d in dirs if d not in ['.git', '.venv', 'node_modules', '__pycache__', 'AppData']]
+        if "am.png" in [f.lower() for f in files]:
+            for f in files:
+                if f.lower() == "am.png":
+                    p = os.path.join(root, f)
+                    print(f"Found match: {p}")
+                    found.append(p)
+
+print("Search complete. Matches found:", found)

+ 19 - 0
scratch/find_pdf_slashes.py

@@ -0,0 +1,19 @@
+import os
+import glob
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+pdf_files = glob.glob(os.path.join(docs_dir, "*.pdf"))
+
+for pdf_file in pdf_files:
+    try:
+        doc = fitz.open(pdf_file)
+        found = False
+        for page_idx, page in enumerate(doc):
+            text = page.get_text()
+            if "∕" in text:
+                print(f"Found math slash in {os.path.basename(pdf_file)} on page {page_idx + 1}")
+                found = True
+        doc.close()
+    except Exception as e:
+        print(f"Error checking {pdf_file}: {e}")

+ 24 - 0
scratch/inspect_pdf_pages.py

@@ -0,0 +1,24 @@
+import fitz
+
+doc = fitz.open(r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs/Installation_Guide.pdf")
+print(f"Total pages: {len(doc)}")
+page = doc[1]  # Page 2 (0-indexed)
+
+print("--- PAGE 2 DRAWINGS ---")
+drawings = page.get_drawings()
+print(f"Number of drawings: {len(drawings)}")
+for idx, draw in enumerate(drawings[:10]):
+    print(f"Drawing {idx}: rect={draw['rect']}, type={draw['type']}, fill={draw.get('fill')}, color={draw.get('color')}")
+
+import sys
+if hasattr(sys.stdout, 'reconfigure'):
+    sys.stdout.reconfigure(encoding='utf-8')
+
+print("\n--- PAGE 2 TEXT BLOCKS ---")
+blocks = page.get_text("blocks")
+print(f"Number of text blocks: {len(blocks)}")
+for idx, block in enumerate(blocks):
+    text_clean = block[4].encode('utf-8', errors='replace').decode('utf-8')
+    print(f"Block {idx}: rect={block[:4]}, text={repr(text_clean)}")
+
+doc.close()

+ 34 - 0
scratch/test_clean_save.py

@@ -0,0 +1,34 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=docs_dir)
+pdf.add_section(section)
+
+# Try saving WITH clean=True
+doc = pdf._make_doc()
+doc.save("scratch/test_clean_on.pdf", clean=True, garbage=3, deflate=True)
+doc.close()
+
+# Try saving WITHOUT clean=True (only garbage=3, deflate=True)
+doc2 = fitz.open("scratch/test_clean_on.pdf")
+print("WITH clean=True, Page 2 text length:", len(doc2[1].get_text()))
+doc2.close()
+
+pdf2 = MarkdownPdf()
+section2 = Section(md_content, paper_size="A4", root=docs_dir)
+pdf2.add_section(section2)
+doc3 = pdf2._make_doc()
+doc3.save("scratch/test_clean_off.pdf", clean=False, garbage=3, deflate=True)
+doc3.close()
+
+doc4 = fitz.open("scratch/test_clean_off.pdf")
+print("WITHOUT clean=True, Page 2 text length:", len(doc4[1].get_text()))
+doc4.close()

+ 120 - 0
scratch/test_css_and_headers.py

@@ -0,0 +1,120 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Clean paths
+root_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food"
+md_content = md_content.replace("file:///" + root_dir + "/docs/", "")
+md_content = md_content.replace("file:///" + root_dir + "/", "../")
+
+user_css = """
+* {
+    color: #1a1a1a !important;
+}
+body {
+    font-family: 'Helvetica', 'Arial', sans-serif !important;
+    color: #1a1a1a !important;
+    background-color: #ffffff !important;
+}
+h1, h2, h3, h4, h5, h6, h1 *, h2 *, h3 *, h4 *, h5 *, h6 * {
+    color: #000000 !important;
+    margin-top: 18px !important;
+    margin-bottom: 8px !important;
+}
+pre {
+    background-color: #f8f9fa !important;
+    border: 1px solid #e9ecef !important;
+    padding: 2px !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+    border-radius: 3px !important;
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+pre code, pre * {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #212529 !important;
+    background-color: #f8f9fa !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+code {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #b02a37 !important;
+    background-color: #f8f9fa !important;
+    padding: 2px 4px !important;
+    border-radius: 3px !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+a, a * {
+    color: #0d6efd !important;
+}
+blockquote, blockquote * {
+    color: #555555 !important;
+    border-left: 4px solid #ccc !important;
+    padding-left: 10px !important;
+}
+table, tr, td, th, table * {
+    color: #1a1a1a !important;
+    border-color: #cccccc !important;
+}
+th {
+    background-color: #f2f2f2 !important;
+}
+ul, li {
+    list-style-type: disc !important;
+}
+"""
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=docs_dir)
+pdf.add_section(section, user_css=user_css)
+
+doc = pdf._make_doc()
+total_pages = len(doc)
+print("Before drawing, Page 2 text length:", len(doc[1].get_text()))
+
+image_path = os.path.join(docs_dir, 'am.png')
+doc_title = "Local Food AI - Detailed Installation and Deployment Guide"
+r_text = "DOPRO1"
+l_footer = "lanfr144"
+fontsize = 8
+fontname = "helv"
+
+for page_idx in range(total_pages):
+    page = doc[page_idx]
+    width = page.rect.width
+    height = page.rect.height
+    
+    if os.path.exists(image_path):
+        image_rect = fitz.Rect(48, 12, 48 + 16, 12 + 16)
+        page.insert_image(image_rect, filename=image_path)
+        
+    m_width = fitz.get_text_length(doc_title, fontname=fontname, fontsize=fontsize)
+    r_width = fitz.get_text_length(r_text, fontname=fontname, fontsize=fontsize)
+    footer_text = f"Page {page_idx + 1} of {total_pages}"
+    f_width = fitz.get_text_length(footer_text, fontname=fontname, fontsize=fontsize)
+    
+    page.insert_text(fitz.Point((width - m_width) / 2, 22), doc_title, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point(width - 48 - r_width, 22), r_text, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point(48, height - 22), l_footer, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point((width - f_width) / 2, height - 22), footer_text, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+
+print("After drawing (in memory), Page 2 text length:", len(doc[1].get_text()))
+doc.save("scratch/test_both.pdf", clean=True, garbage=3, deflate=True)
+doc.close()
+
+doc2 = fitz.open("scratch/test_both.pdf")
+print("After saving, Page 2 text length:", len(doc2[1].get_text()))
+doc2.close()

+ 56 - 0
scratch/test_header_effect.py

@@ -0,0 +1,56 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Clean paths
+root_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food"
+md_content = md_content.replace("file:///" + root_dir + "/docs/", "")
+md_content = md_content.replace("file:///" + root_dir + "/", "../")
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=docs_dir)
+pdf.add_section(section)
+
+doc = pdf._make_doc()
+total_pages = len(doc)
+print("Before drawing, Page 2 text length:", len(doc[1].get_text()))
+
+image_path = os.path.join(docs_dir, 'am.png')
+doc_title = "Local Food AI - Detailed Installation and Deployment Guide"
+r_text = "DOPRO1"
+l_footer = "lanfr144"
+fontsize = 8
+fontname = "helv"
+
+for page_idx in range(total_pages):
+    page = doc[page_idx]
+    width = page.rect.width
+    height = page.rect.height
+    
+    if os.path.exists(image_path):
+        image_rect = fitz.Rect(48, 12, 48 + 16, 12 + 16)
+        page.insert_image(image_rect, filename=image_path)
+        
+    m_width = fitz.get_text_length(doc_title, fontname=fontname, fontsize=fontsize)
+    r_width = fitz.get_text_length(r_text, fontname=fontname, fontsize=fontsize)
+    footer_text = f"Page {page_idx + 1} of {total_pages}"
+    f_width = fitz.get_text_length(footer_text, fontname=fontname, fontsize=fontsize)
+    
+    page.insert_text(fitz.Point((width - m_width) / 2, 22), doc_title, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point(width - 48 - r_width, 22), r_text, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point(48, height - 22), l_footer, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+    page.insert_text(fitz.Point((width - f_width) / 2, height - 22), footer_text, fontname=fontname, fontsize=fontsize, color=(0.5, 0.5, 0.5))
+
+print("After drawing (in memory), Page 2 text length:", len(doc[1].get_text()))
+doc.save("scratch/test_drawn.pdf", clean=True, garbage=3, deflate=True)
+doc.close()
+
+doc2 = fitz.open("scratch/test_drawn.pdf")
+print("After saving, Page 2 text length:", len(doc2[1].get_text()))
+doc2.close()

+ 73 - 0
scratch/test_header_footer.py

@@ -0,0 +1,73 @@
+import fitz
+import os
+
+doc = fitz.open()
+page = doc.new_page()
+width = page.rect.width
+height = page.rect.height
+
+doc_title = "Local Food AI - Detailed Installation and Deployment Guide"
+r_text = "DOPRO1"
+l_footer = "lanfr144"
+footer_text = "Page 1 of 1"
+
+fontsize = 8
+fontname = "helv"
+image_path = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs/am.png"
+
+try:
+    # Test drawing image
+    if os.path.exists(image_path):
+        image_rect = fitz.Rect(48, 12, 48 + 16, 12 + 16)
+        page.insert_image(image_rect, filename=image_path)
+        print("Image inserted successfully!")
+    else:
+        print("Image file not found!")
+        
+    # Calculate widths
+    m_width = fitz.get_text_length(doc_title, fontname=fontname, fontsize=fontsize)
+    r_width = fitz.get_text_length(r_text, fontname=fontname, fontsize=fontsize)
+    f_width = fitz.get_text_length(footer_text, fontname=fontname, fontsize=fontsize)
+    
+    # 1. Middle header (centered)
+    page.insert_text(
+        fitz.Point((width - m_width) / 2, 22),
+        doc_title,
+        fontname=fontname,
+        fontsize=fontsize,
+        color=(0.5, 0.5, 0.5)
+    )
+    
+    # 2. Right header
+    page.insert_text(
+        fitz.Point(width - 48 - r_width, 22),
+        r_text,
+        fontname=fontname,
+        fontsize=fontsize,
+        color=(0.5, 0.5, 0.5)
+    )
+    
+    # 3. Left footer
+    page.insert_text(
+        fitz.Point(48, height - 22),
+        l_footer,
+        fontname=fontname,
+        fontsize=fontsize,
+        color=(0.5, 0.5, 0.5)
+    )
+    
+    # 4. Middle footer (centered)
+    page.insert_text(
+        fitz.Point((width - f_width) / 2, height - 22),
+        footer_text,
+        fontname=fontname,
+        fontsize=fontsize,
+        color=(0.5, 0.5, 0.5)
+    )
+    
+    doc.save("scratch/test_header_footer.pdf", clean=True, garbage=3, deflate=True)
+    doc.close()
+    print("PDF saved successfully!")
+except Exception as e:
+    print("Error:", e)
+

+ 100 - 0
scratch/test_pre.py

@@ -0,0 +1,100 @@
+import os
+import re
+import fitz
+from markdown_pdf import MarkdownPdf, Section
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Clean paths
+root_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food"
+md_content = md_content.replace("file:///" + root_dir + "/docs/", "")
+md_content = md_content.replace("file:///" + root_dir + "/", "../")
+
+# Try WITH <br/> replacement
+md_content_br = re.sub(r'(```[a-zA-Z0-9_-]*\n[\s\S]*?\n```)', r'\1\n<br/>', md_content)
+
+user_css = """
+* {
+    color: #1a1a1a !important;
+}
+body {
+    font-family: 'Helvetica', 'Arial', sans-serif !important;
+    color: #1a1a1a !important;
+    background-color: #ffffff !important;
+}
+h1, h2, h3, h4, h5, h6, h1 *, h2 *, h3 *, h4 *, h5 *, h6 * {
+    color: #000000 !important;
+    margin-top: 18px !important;
+    margin-bottom: 8px !important;
+}
+pre {
+    background-color: #f8f9fa !important;
+    border: 1px solid #e9ecef !important;
+    padding: 2px !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+    border-radius: 3px !important;
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+pre code, pre * {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #212529 !important;
+    background-color: #f8f9fa !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+code {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #b02a37 !important;
+    background-color: #f8f9fa !important;
+    padding: 2px 4px !important;
+    border-radius: 3px !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+a, a * {
+    color: #0d6efd !important;
+}
+blockquote, blockquote * {
+    color: #555555 !important;
+    border-left: 4px solid #ccc !important;
+    padding-left: 10px !important;
+}
+table, tr, td, th, table * {
+    color: #1a1a1a !important;
+    border-color: #cccccc !important;
+}
+th {
+    background-color: #f2f2f2 !important;
+}
+ul, li {
+    list-style-type: disc !important;
+}
+"""
+
+print("--- TESTING WITH <br/> ---")
+pdf_br = MarkdownPdf()
+pdf_br.add_section(Section(md_content_br, paper_size="A4", root=docs_dir), user_css=user_css)
+doc_br = pdf_br._make_doc()
+print(f"Total pages: {len(doc_br)}")
+print(f"Page 2 blocks count: {len(doc_br[1].get_text('blocks'))}")
+doc_br.close()
+
+print("\n--- TESTING WITH \\n\\n ---")
+# Try with \n\n instead
+md_content_nn = re.sub(r'(```[a-zA-Z0-9_-]*\n[\s\S]*?\n```)', r'\1\n\n', md_content)
+pdf_nn = MarkdownPdf()
+pdf_nn.add_section(Section(md_content_nn, paper_size="A4", root=docs_dir), user_css=user_css)
+doc_nn = pdf_nn._make_doc()
+print(f"Total pages: {len(doc_nn)}")
+print(f"Page 2 blocks count: {len(doc_nn[1].get_text('blocks'))}")
+doc_nn.close()

+ 89 - 0
scratch/test_pre_length.py

@@ -0,0 +1,89 @@
+import os
+from markdown_pdf import MarkdownPdf, Section
+import fitz
+
+docs_dir = r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs"
+md_file = os.path.join(docs_dir, "Installation_Guide.md")
+
+with open(md_file, "r", encoding="utf-8") as f:
+    md_content = f.read()
+
+# Current CSS
+user_css = """
+* {
+    color: #1a1a1a !important;
+}
+body {
+    font-family: 'Helvetica', 'Arial', sans-serif !important;
+    color: #1a1a1a !important;
+    background-color: #ffffff !important;
+}
+h1, h2, h3, h4, h5, h6, h1 *, h2 *, h3 *, h4 *, h5 *, h6 * {
+    color: #000000 !important;
+    margin-top: 18px !important;
+    margin-bottom: 8px !important;
+}
+pre {
+    background-color: #f8f9fa !important;
+    border: 1px solid #e9ecef !important;
+    padding: 2px !important;
+    margin-top: 8px !important;
+    margin-bottom: 16px !important;
+    border-radius: 3px !important;
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+pre code, pre * {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #212529 !important;
+    background-color: #f8f9fa !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+code {
+    font-family: 'Courier New', 'Courier', monospace !important;
+    font-size: 10pt !important;
+    color: #b02a37 !important;
+    background-color: #f8f9fa !important;
+    padding: 2px 4px !important;
+    border-radius: 3px !important;
+    white-space: pre-wrap !important;
+    word-break: break-all !important;
+}
+a, a * {
+    color: #0d6efd !important;
+}
+blockquote, blockquote * {
+    color: #555555 !important;
+    border-left: 4px solid #ccc !important;
+    padding-left: 10px !important;
+}
+table, tr, td, th, table * {
+    color: #1a1a1a !important;
+    border-color: #cccccc !important;
+}
+th {
+    background-color: #f2f2f2 !important;
+}
+ul, li {
+    list-style-type: disc !important;
+}
+"""
+
+pdf = MarkdownPdf()
+section = Section(md_content, paper_size="A4", root=docs_dir)
+pdf.add_section(section, user_css=user_css)
+pdf.save("scratch/test_css_on.pdf")
+
+doc = fitz.open("scratch/test_css_on.pdf")
+print("Total pages:", len(doc))
+for i in range(len(doc)):
+    txt = doc[i].get_text()
+    print(f"Page {i+1} text length: {len(txt)}")
+    if i == 1:
+        print("--- PAGE 2 TEXT CONTENT ---")
+        print(txt.replace("\ufb01", "fi"))
+doc.close()

+ 11 - 0
scratch/test_text.py

@@ -0,0 +1,11 @@
+import fitz
+import sys
+
+sys.stdout.reconfigure(encoding='utf-8')
+
+doc = fitz.open(r"c:/Users/lanfr144/Documents/DOPRO1/Antigravity/Food/docs/Installation_Guide.pdf")
+print(f"Total pages: {len(doc)}")
+for idx, page in enumerate(doc):
+    print(f"--- Page {idx + 1} ---")
+    print(page.get_text())
+doc.close()

+ 62 - 0
scratch/uninstall_reinstall_server.py

@@ -0,0 +1,62 @@
+import os
+import sys
+import paramiko
+from dotenv import load_dotenv
+
+sys.stdout.reconfigure(encoding='utf-8')
+sys.stderr.reconfigure(encoding='utf-8')
+
+def main():
+    repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    env_path = os.path.join(repo_root, ".env")
+    load_dotenv(dotenv_path=env_path)
+
+    host = os.environ.get('SERVER_HOST')
+    user = os.environ.get('SERVER_USER')
+    password = os.environ.get('SERVER_PASS')
+
+    if not all([host, user]):
+        print("Error: Server credentials not found in .env file.")
+        return
+
+    if password == "your_db_password_here" or password == "your_password_here" or not password:
+        password = None
+
+    print(f"Connecting to {user}@{host} via SSH...")
+    ssh = paramiko.SSHClient()
+    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+    
+    try:
+        ssh.connect(host, username=user, password=password, timeout=15)
+        print("Connected successfully!")
+        
+        # 1. Bring down and purge old deployment (Uninstall)
+        print("\n--- [1/3] Purging existing stack (Docker Compose Down -v) ---")
+        stdin, stdout, stderr = ssh.exec_command("cd food_project && docker-compose down -v")
+        print("Output:", stdout.read().decode('utf-8'))
+        print("Errors/Warnings:", stderr.read().decode('utf-8'))
+        
+        # 2. Fetch and pull latest code
+        print("\n--- [2/3] Pulling latest version from Git ---")
+        stdin, stdout, stderr = ssh.exec_command("cd food_project && git fetch origin main && git reset --hard origin/main && git clean -fd")
+        print("Output:", stdout.read().decode('utf-8'))
+        print("Errors/Warnings:", stderr.read().decode('utf-8'))
+        
+        # 3. Start up the new stack
+        local_model = os.environ.get('LLM_MODEL', 'llama3.2:3b')
+        print(f"\n--- [3/3] Launching new stack with model {local_model} ---")
+        command = f"cd food_project && sed -i 's/^LLM_MODEL=.*/LLM_MODEL={local_model}/' .env && docker-compose up -d --build"
+        stdin, stdout, stderr = ssh.exec_command(command)
+        print("Output:", stdout.read().decode('utf-8'))
+        print("Errors/Warnings:", stderr.read().decode('utf-8'))
+        
+        print("\nAll remote operations completed.")
+        
+    except Exception as e:
+        print(f"Error executing remote commands: {e}")
+    finally:
+        ssh.close()
+        print("SSH connection closed.")
+
+if __name__ == "__main__":
+    main()

+ 1 - 1
scripts/generate_project_report.py

@@ -229,7 +229,7 @@ def main():
 The **Local Food AI** capstone project has successfully completed all sprint iterations. The system stands fully verified, containerized, and documented. 
 
 ### What Has Been Done
-1. **Model Upgraded to Ollama Latest**: Transitioned from the `llama3.2:3b` model to the much more robust, large reasoning-focused **`qwen2.5:7b`** model (4.7 GB) with structured XML Chain-of-Thought (CoT) calculations. Programmatically downloaded and installed it natively inside the `food_project-ollama-1` container, and fully updated all application endpoints in `app.py`.
+1. **Model Configured with Ollama**: Deployed the local instruction-following **`llama3.2:3b`** model (2.2 GB) with structured XML Chain-of-Thought (CoT) calculations. Programmatically downloaded and installed it natively inside the `food_project-ollama-1` container, and fully updated all application endpoints in `app.py`.
 2. **Taiga Deliverables Synchronized**: Checked the live Taiga API on server `192.168.130.161`. All 30 User Stories, all technical tasks, and all issues in Project ID 21 (Sprint 7 Milestone) are **100% completed and officially closed**!
 3. **Database Architecture & Partitioning**: Loaded and vertically partitioned the 3GB OpenFoodFacts macro data into MySQL. Configured matching FULLTEXT engines to search records in less than **0.04s** (averaging 90% latency reduction).
 4. **DevSecOps Observability**: Completed SNMPv2c telemetry configuration, custom application traps, and configured automated trigger alerts directly inside Zabbix on `192.168.130.170`.