Team Ai
Apppublic

blackopsrepl/portfolio-optimization-python

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
1likes
index.html673 linesDownload Raw Back to static
1<!doctype html>
2<html lang="en">
3<head>
4  <meta charset="utf-8">
5  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
6  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7  <title>Portfolio Optimization - SolverForge for Python</title>
8  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css">
9  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
10  <link rel="stylesheet" href="/webjars/solverforge/css/solverforge-webui.css">
11  <link rel="icon" href="/webjars/solverforge/img/solverforge-favicon.svg" type="image/svg+xml">
12  <style>
13    /* Solving spinner */
14    #solvingSpinner {
15      display: none;
16      width: 1.25rem;
17      height: 1.25rem;
18      border: 2px solid #10b981;
19      border-top-color: transparent;
20      border-radius: 50%;
21      animation: spin 0.75s linear infinite;
22      vertical-align: middle;
23    }
24    #solvingSpinner.active {
25      display: inline-block;
26    }
27    @keyframes spin {
28      to { transform: rotate(360deg); }
29    }
30
31    /* Sector badge colors */
32    .sector-badge {
33      font-size: 0.75rem;
34      padding: 4px 10px;
35      border-radius: 12px;
36      font-weight: 500;
37    }
38    .sector-Technology { background: #3b82f6; color: white; }
39    .sector-Healthcare { background: #10b981; color: white; }
40    .sector-Finance { background: #eab308; color: #1a1a1a; }
41    .sector-Energy { background: #ef4444; color: white; }
42    .sector-Consumer { background: #a855f7; color: white; }
43
44    /* KPI Cards */
45    .kpi-card {
46      background: white;
47      border-radius: 12px;
48      padding: 1.5rem;
49      text-align: center;
50      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
51      transition: transform 0.2s ease, box-shadow 0.2s ease;
52    }
53    .kpi-card:hover {
54      transform: translateY(-2px);
55      box-shadow: 0 4px 12px rgba(0,0,0,0.15);
56    }
57    .kpi-value {
58      font-size: 2.5rem;
59      font-weight: 700;
60      color: #10b981;
61      line-height: 1.2;
62      transition: all 0.3s ease;
63    }
64    .kpi-value.updating {
65      opacity: 0.5;
66    }
67    .kpi-value.positive { color: #10b981; }
68    .kpi-value.negative { color: #ef4444; }
69    .kpi-label {
70      font-size: 0.85rem;
71      color: #64748b;
72      text-transform: uppercase;
73      letter-spacing: 0.05em;
74      margin-top: 0.5rem;
75    }
76    .kpi-icon {
77      font-size: 1.5rem;
78      color: #94a3b8;
79      margin-bottom: 0.5rem;
80    }
81
82    /* Chart containers */
83    .chart-card {
84      background: white;
85      border-radius: 12px;
86      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
87      height: 100%;
88    }
89    .chart-card-header {
90      padding: 1rem 1.25rem;
91      border-bottom: 1px solid #e2e8f0;
92      font-weight: 600;
93      color: #1e293b;
94    }
95    .chart-card-body {
96      padding: 1rem;
97    }
98    .chart-container {
99      position: relative;
100      height: 280px;
101    }
102
103    /* Stock table */
104    .stock-table {
105      font-size: 0.9rem;
106    }
107    .stock-table th {
108      background: #f8fafc;
109      font-weight: 600;
110      text-transform: uppercase;
111      font-size: 0.75rem;
112      letter-spacing: 0.05em;
113      color: #64748b;
114      border-bottom: 2px solid #e2e8f0;
115      cursor: pointer;
116      user-select: none;
117    }
118    .stock-table th:hover {
119      background: #f1f5f9;
120    }
121    .stock-table th i {
122      margin-left: 4px;
123      opacity: 0.5;
124    }
125    .stock-table th.sorted i {
126      opacity: 1;
127      color: #10b981;
128    }
129    .stock-selected {
130      background: #f0fdf4 !important;
131    }
132    .stock-table tr {
133      transition: background-color 0.15s ease;
134    }
135    .stock-table tbody tr:hover {
136      background: #f8fafc;
137    }
138    .return-positive { color: #10b981; font-weight: 600; }
139    .return-negative { color: #ef4444; font-weight: 600; }
140
141    /* Score display */
142    .score-badge {
143      display: inline-block;
144      padding: 6px 12px;
145      border-radius: 8px;
146      font-weight: 600;
147      font-size: 0.9rem;
148    }
149    .score-hard {
150      background: #fef2f2;
151      color: #dc2626;
152      border: 1px solid #fecaca;
153    }
154    .score-soft {
155      background: #f0fdf4;
156      color: #16a34a;
157      border: 1px solid #bbf7d0;
158    }
159    .score-feasible {
160      background: #f0fdf4;
161      color: #16a34a;
162    }
163    .score-infeasible {
164      background: #fef2f2;
165      color: #dc2626;
166    }
167
168    /* Data selector dropdown */
169    .data-selector {
170      background: #10b981;
171      color: white;
172      border: none;
173      border-radius: 1.5rem;
174      padding: 0.5rem 1rem;
175      font-weight: 500;
176    }
177    .data-selector:focus {
178      box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.3);
179    }
180
181    /* Notification panel */
182    #notificationPanel {
183      z-index: 1050;
184    }
185
186    /* Copy button for code blocks */
187    .copy-btn {
188      position: absolute;
189      top: 0.5rem;
190      right: 0.5rem;
191      opacity: 0;
192      transition: opacity 0.2s;
193    }
194    .code-block:hover .copy-btn {
195      opacity: 1;
196    }
197    .code-block {
198      position: relative;
199    }
200
201    /* Progress animation during solving */
202    @keyframes pulse {
203      0%, 100% { opacity: 1; }
204      50% { opacity: 0.6; }
205    }
206    .solving-pulse {
207      animation: pulse 1.5s ease-in-out infinite;
208    }
209
210    /* ==========================================================
211       ADVANCED SETTINGS STYLES (OPTIONAL)
212       Remove this section for a simpler quickstart.
213       ========================================================== */
214    .settings-card {
215      background: white;
216      border-radius: 12px;
217      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
218    }
219    .settings-card .card-body {
220      padding: 1.25rem;
221    }
222    .form-range::-webkit-slider-thumb {
223      background: #10b981;
224    }
225    .form-range::-moz-range-thumb {
226      background: #10b981;
227    }
228    .preset-badge {
229      font-size: 0.7rem;
230      padding: 2px 6px;
231      border-radius: 4px;
232      background: #f1f5f9;
233      color: #64748b;
234      margin-left: 0.5rem;
235    }
236    .config-value {
237      font-weight: 600;
238      color: #10b981;
239    }
240    /* END ADVANCED SETTINGS STYLES */
241  </style>
242</head>
243<body>
244
245<header id="solverforge-auto-header">
246  <!-- Filled by solverforge-webui.js -->
247</header>
248
249<div class="tab-content">
250  <!-- Tab 1: Demo UI -->
251  <div id="demo" class="tab-pane fade show active container-fluid">
252    <div class="sticky-top d-flex justify-content-center align-items-center">
253      <div id="notificationPanel" style="position: absolute; top: .5rem;"></div>
254    </div>
255
256    <h1>Portfolio Optimization</h1>
257    <p>Select optimal stocks to maximize expected returns while meeting sector exposure constraints.</p>
258
259    <!-- Control Bar -->
260    <div class="container-fluid mb-3">
261      <div class="row justify-content-between align-items-center">
262        <div class="col-auto">
263          <ul class="nav nav-pills" role="tablist">
264            <li class="nav-item" role="presentation">
265              <button class="nav-link active" id="portfolioTab" data-bs-toggle="tab" data-bs-target="#portfolioPanel"
266                      type="button" role="tab" aria-controls="portfolioPanel" aria-selected="true">
267                <i class="fas fa-chart-pie me-1"></i> Portfolio
268              </button>
269            </li>
270            <li class="nav-item" role="presentation">
271              <button class="nav-link" id="stocksTab" data-bs-toggle="tab" data-bs-target="#stocksPanel"
272                      type="button" role="tab" aria-controls="stocksPanel" aria-selected="false">
273                <i class="fas fa-list me-1"></i> All Stocks
274              </button>
275            </li>
276          </ul>
277        </div>
278        <div class="col-auto">
279          <select id="dataSelector" class="data-selector me-2">
280            <option value="SMALL">Small (25 stocks)</option>
281            <option value="LARGE">Large (51 stocks)</option>
282          </select>
283          <!-- ============================================================
284               ADVANCED SETTINGS BUTTON (OPTIONAL)
285               Remove this button and the panel below for a simpler quickstart.
286               Also remove config.js script at the bottom of the file.
287               ============================================================ -->
288          <button class="btn btn-outline-secondary btn-sm me-2" type="button"
289                  data-bs-toggle="collapse" data-bs-target="#advancedSettings"
290                  aria-expanded="false" aria-controls="advancedSettings"
291                  id="advancedSettingsBtn">
292            <i class="fas fa-cog"></i> Advanced
293          </button>
294          <!-- END ADVANCED SETTINGS BUTTON -->
295          <button id="solveButton" type="button" class="btn btn-success">
296            <i class="fas fa-play"></i> Solve
297          </button>
298          <button id="stopSolvingButton" type="button" class="btn btn-danger" style="display: none;">
299            <i class="fas fa-stop"></i> Stop
300          </button>
301          <span id="solvingSpinner" class="ms-2"></span>
302          <span id="score" class="ms-2 fw-bold">Score: ?</span>
303          <button id="analyzeButton" type="button" class="btn btn-secondary ms-2" title="Analyze constraints">
304            <i class="fas fa-question"></i>
305          </button>
306        </div>
307      </div>
308
309      <!-- ============================================================
310           ADVANCED SETTINGS PANEL (OPTIONAL)
311           This entire panel can be removed for a simpler quickstart.
312           If removed, also remove:
313           - The "Advanced" button above
314           - config.js script at the bottom of the file
315           ============================================================ -->
316      <div class="collapse mt-3" id="advancedSettings">
317        <div class="settings-card">
318          <div class="card-body">
319            <div class="row g-4">
320              <!-- Preset Selector -->
321              <div class="col-md-3">
322                <label class="form-label fw-bold">
323                  <i class="fas fa-sliders-h me-1 text-muted"></i>Preset
324                </label>
325                <select id="presetSelector" class="form-select">
326                  <option value="balanced" selected>Balanced (Default)</option>
327                  <option value="conservative">Conservative</option>
328                  <option value="aggressive">Aggressive</option>
329                  <option value="quick">Quick Test</option>
330                  <option value="custom">Custom</option>
331                </select>
332              </div>
333
334              <!-- Target Stocks Slider -->
335              <div class="col-md-3">
336                <label class="form-label">
337                  Target Stocks: <span id="targetStocksValue" class="config-value">20</span>
338                </label>
339                <input type="range" class="form-range" id="targetStocksSlider"
340                       min="5" max="50" value="20">
341                <div class="d-flex justify-content-between text-muted" style="font-size: 0.7rem;">
342                  <span>5</span>
343                  <span>50</span>
344                </div>
345              </div>
346
347              <!-- Max Sector Slider -->
348              <div class="col-md-3">
349                <label class="form-label">
350                  Max Sector: <span id="maxSectorValue" class="config-value">25%</span>
351                </label>
352                <input type="range" class="form-range" id="maxSectorSlider"
353                       min="10" max="50" value="25">
354                <div class="d-flex justify-content-between text-muted" style="font-size: 0.7rem;">
355                  <span>10%</span>
356                  <span>50%</span>
357                </div>
358              </div>
359
360              <!-- Solver Time Slider -->
361              <div class="col-md-3">
362                <label class="form-label">
363                  Solver Time: <span id="solverTimeValue" class="config-value">30s</span>
364                </label>
365                <input type="range" class="form-range" id="solverTimeSlider"
366                       min="10" max="300" step="10" value="30">
367                <div class="d-flex justify-content-between text-muted" style="font-size: 0.7rem;">
368                  <span>10s</span>
369                  <span>5min</span>
370                </div>
371              </div>
372            </div>
373
374            <!-- Preset Description -->
375            <div class="mt-3 text-muted" style="font-size: 0.85rem;">
376              <i class="fas fa-info-circle me-1"></i>
377              <span id="presetDescription">Balanced settings for typical portfolio optimization.</span>
378            </div>
379          </div>
380        </div>
381      </div>
382      <!-- END ADVANCED SETTINGS PANEL -->
383    </div>
384
385    <!-- Inner tab content -->
386    <div class="tab-content">
387      <!-- Portfolio View -->
388      <div class="tab-pane fade show active" id="portfolioPanel" role="tabpanel" aria-labelledby="portfolioTab">
389        <!-- KPI Row 1: Core Metrics -->
390        <div class="row mb-3 g-3">
391          <div class="col-md-3 col-sm-6">
392            <div class="kpi-card">
393              <div class="kpi-icon"><i class="fas fa-check-circle"></i></div>
394              <div class="kpi-value" id="selectedCount">0/20</div>
395              <div class="kpi-label">Selected Stocks</div>
396            </div>
397          </div>
398          <div class="col-md-3 col-sm-6">
399            <div class="kpi-card">
400              <div class="kpi-icon"><i class="fas fa-chart-line"></i></div>
401              <div class="kpi-value positive" id="expectedReturn">0.00%</div>
402              <div class="kpi-label">Expected Return</div>
403            </div>
404          </div>
405          <div class="col-md-3 col-sm-6">
406            <div class="kpi-card">
407              <div class="kpi-icon"><i class="fas fa-layer-group"></i></div>
408              <div class="kpi-value" id="sectorCount">0</div>
409              <div class="kpi-label">Sectors</div>
410            </div>
411          </div>
412          <div class="col-md-3 col-sm-6">
413            <div class="kpi-card" id="diversificationTooltip" data-bs-toggle="tooltip" title="Higher = more diversified. Based on Herfindahl-Hirschman Index (HHI).">
414              <div class="kpi-icon"><i class="fas fa-shuffle"></i></div>
415              <div class="kpi-value" id="diversificationScore">0%</div>
416              <div class="kpi-label">Diversification <i class="fas fa-info-circle text-muted" style="font-size: 0.7rem;"></i></div>
417            </div>
418          </div>
419        </div>
420
421        <!-- KPI Row 2: Risk Metrics -->
422        <div class="row mb-4 g-3">
423          <div class="col-md-3 col-sm-6">
424            <div class="kpi-card" data-bs-toggle="tooltip" title="Highest single sector weight. Lower is more diversified.">
425              <div class="kpi-icon"><i class="fas fa-chart-pie"></i></div>
426              <div class="kpi-value" id="maxSectorExposure">0%</div>
427              <div class="kpi-label">Max Sector <i class="fas fa-info-circle text-muted" style="font-size: 0.7rem;"></i></div>
428            </div>
429          </div>
430          <div class="col-md-3 col-sm-6">
431            <div class="kpi-card" data-bs-toggle="tooltip" title="Standard deviation of predicted returns. Lower = less risk.">
432              <div class="kpi-icon"><i class="fas fa-wave-square"></i></div>
433              <div class="kpi-value" id="returnVolatility">0.00%</div>
434              <div class="kpi-label">Volatility <i class="fas fa-info-circle text-muted" style="font-size: 0.7rem;"></i></div>
435            </div>
436          </div>
437          <div class="col-md-3 col-sm-6">
438            <div class="kpi-card" data-bs-toggle="tooltip" title="Return / Volatility ratio. Higher = better risk-adjusted return.">
439              <div class="kpi-icon"><i class="fas fa-scale-balanced"></i></div>
440              <div class="kpi-value" id="sharpeProxy">0.00</div>
441              <div class="kpi-label">Sharpe Proxy <i class="fas fa-info-circle text-muted" style="font-size: 0.7rem;"></i></div>
442            </div>
443          </div>
444          <div class="col-md-3 col-sm-6">
445            <div class="kpi-card" data-bs-toggle="tooltip" title="Herfindahl-Hirschman Index. Lower = more diversified. <0.15 is well-diversified.">
446              <div class="kpi-icon"><i class="fas fa-bullseye"></i></div>
447              <div class="kpi-value" id="herfindahlIndex">0.000</div>
448              <div class="kpi-label">HHI <i class="fas fa-info-circle text-muted" style="font-size: 0.7rem;"></i></div>
449            </div>
450          </div>
451        </div>
452
453        <!-- Charts Row -->
454        <div class="row mb-4 g-3">
455          <div class="col-md-6">
456            <div class="chart-card">
457              <div class="chart-card-header">
458                <i class="fas fa-chart-pie me-2 text-muted"></i>Sector Allocation
459              </div>
460              <div class="chart-card-body">
461                <div class="chart-container">
462                  <canvas id="sectorChart"></canvas>
463                </div>
464              </div>
465            </div>
466          </div>
467          <div class="col-md-6">
468            <div class="chart-card">
469              <div class="chart-card-header">
470                <i class="fas fa-chart-bar me-2 text-muted"></i>Top Returns (Selected)
471              </div>
472              <div class="chart-card-body">
473                <div class="chart-container">
474                  <canvas id="returnsChart"></canvas>
475                </div>
476              </div>
477            </div>
478          </div>
479        </div>
480
481        <!-- Selected Stocks Summary -->
482        <div class="row">
483          <div class="col-12">
484            <div class="chart-card">
485              <div class="chart-card-header d-flex justify-content-between align-items-center">
486                <span><i class="fas fa-star me-2 text-warning"></i>Selected Stocks</span>
487                <span class="badge bg-success" id="selectedBadge">0 selected</span>
488              </div>
489              <div class="chart-card-body p-0">
490                <div class="table-responsive">
491                  <table class="table table-hover stock-table mb-0">
492                    <thead>
493                      <tr>
494                        <th>Ticker</th>
495                        <th>Company</th>
496                        <th>Sector</th>
497                        <th>Predicted Return</th>
498                        <th>Weight</th>
499                      </tr>
500                    </thead>
501                    <tbody id="selectedStocksTable">
502                      <tr>
503                        <td colspan="5" class="text-center text-muted py-4">
504                          <i class="fas fa-info-circle me-2"></i>Click "Solve" to optimize your portfolio
505                        </td>
506                      </tr>
507                    </tbody>
508                  </table>
509                </div>
510              </div>
511            </div>
512          </div>
513        </div>
514      </div>
515
516      <!-- All Stocks View -->
517      <div class="tab-pane fade" id="stocksPanel" role="tabpanel" aria-labelledby="stocksTab">
518        <div class="chart-card">
519          <div class="chart-card-header d-flex justify-content-between align-items-center">
520            <span><i class="fas fa-list me-2 text-muted"></i>All Available Stocks</span>
521            <div class="d-flex align-items-center gap-3">
522              <div class="form-check form-switch mb-0">
523                <input class="form-check-input" type="checkbox" id="showSelectedOnly">
524                <label class="form-check-label" for="showSelectedOnly">Selected only</label>
525              </div>
526              <span class="badge bg-secondary" id="stockCountBadge">0 stocks</span>
527            </div>
528          </div>
529          <div class="chart-card-body p-0">
530            <div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
531              <table class="table table-hover stock-table mb-0">
532                <thead class="sticky-top">
533                  <tr>
534                    <th data-sort="selected">Selected <i class="fas fa-sort"></i></th>
535                    <th data-sort="stockId">Ticker <i class="fas fa-sort"></i></th>
536                    <th data-sort="stockName">Company <i class="fas fa-sort"></i></th>
537                    <th data-sort="sector">Sector <i class="fas fa-sort"></i></th>
538                    <th data-sort="predictedReturn">Predicted Return <i class="fas fa-sort"></i></th>
539                    <th>Weight</th>
540                  </tr>
541                </thead>
542                <tbody id="allStocksTable">
543                  <tr>
544                    <td colspan="6" class="text-center text-muted py-4">
545                      <i class="fas fa-database me-2"></i>Loading stock data...
546                    </td>
547                  </tr>
548                </tbody>
549              </table>
550            </div>
551          </div>
552        </div>
553      </div>
554    </div>
555  </div>
556
557  <!-- Tab 2: REST API Guide -->
558  <div id="rest" class="tab-pane fade container-fluid">
559    <h2>REST API Guide</h2>
560    <p class="text-muted">Use these endpoints to integrate portfolio optimization into your application.</p>
561
562    <h3 class="mt-4">1. Load demo data</h3>
563    <p>Retrieve a pre-configured portfolio with sample stocks.</p>
564    <div class="code-block">
565      <button class="btn btn-outline-dark btn-sm copy-btn" onclick="copyCode(this)">
566        <i class="fas fa-copy"></i> Copy
567      </button>
568      <pre><code>curl -X GET http://localhost:8080/demo-data/SMALL</code></pre>
569    </div>
570
571    <h3 class="mt-4">2. Submit portfolio for optimization</h3>
572    <p>Start the solver to find the optimal stock selection.</p>
573    <div class="code-block">
574      <button class="btn btn-outline-dark btn-sm copy-btn" onclick="copyCode(this)">
575        <i class="fas fa-copy"></i> Copy
576      </button>
577      <pre><code>curl -X POST -H "Content-Type: application/json" \
578  -d @portfolio.json \
579  http://localhost:8080/portfolios</code></pre>
580    </div>
581
582    <h3 class="mt-4">3. Get current solution</h3>
583    <p>Poll for the latest solution while the solver is running.</p>
584    <div class="code-block">
585      <button class="btn btn-outline-dark btn-sm copy-btn" onclick="copyCode(this)">
586        <i class="fas fa-copy"></i> Copy
587      </button>
588      <pre><code>curl -X GET http://localhost:8080/portfolios/{jobId}</code></pre>
589    </div>
590
591    <h3 class="mt-4">4. Stop solving</h3>
592    <p>Terminate the solver and retrieve the best solution found.</p>
593    <div class="code-block">
594      <button class="btn btn-outline-dark btn-sm copy-btn" onclick="copyCode(this)">
595        <i class="fas fa-copy"></i> Copy
596      </button>
597      <pre><code>curl -X DELETE http://localhost:8080/portfolios/{jobId}</code></pre>
598    </div>
599
600    <h3 class="mt-4">5. Analyze constraints</h3>
601    <p>Get a detailed breakdown of constraint scores for a given solution.</p>
602    <div class="code-block">
603      <button class="btn btn-outline-dark btn-sm copy-btn" onclick="copyCode(this)">
604        <i class="fas fa-copy"></i> Copy
605      </button>
606      <pre><code>curl -X PUT -H "Content-Type: application/json" \
607  -d @portfolio.json \
608  http://localhost:8080/portfolios/analyze</code></pre>
609    </div>
610  </div>
611
612  <!-- Tab 3: OpenAPI Reference -->
613  <div id="openapi" class="tab-pane fade container-fluid">
614    <h2>OpenAPI Reference</h2>
615    <p class="text-muted mb-4">Interactive API documentation powered by Swagger UI.</p>
616    <div class="ratio ratio-1x1">
617      <iframe src="/q/swagger-ui" title="OpenAPI Documentation"></iframe>
618    </div>
619  </div>
620</div>
621
622<!-- Score Analysis Modal -->
623<div class="modal fade" id="weightModal" tabindex="-1" aria-labelledby="weightModalTitle" aria-hidden="true">
624  <div class="modal-dialog modal-lg modal-dialog-scrollable">
625    <div class="modal-content">
626      <div class="modal-header">
627        <h5 class="modal-title" id="weightModalTitle">Constraint Analysis</h5>
628        <span id="weightModalLabel" class="ms-2 badge bg-secondary"></span>
629        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
630      </div>
631      <div class="modal-body">
632        <table class="table table-striped" id="weightModalContent">
633          <thead>
634            <tr>
635              <th></th>
636              <th>Constraint</th>
637              <th>Type</th>
638              <th>Weight</th>
639              <th>Matches</th>
640              <th>Score</th>
641            </tr>
642          </thead>
643          <tbody>
644            <!-- Populated by JavaScript -->
645          </tbody>
646        </table>
647      </div>
648      <div class="modal-footer">
649        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
650      </div>
651    </div>
652  </div>
653</div>
654
655<footer id="solverforge-auto-footer">
656  <!-- Filled by solverforge-webui.js -->
657</footer>
658
659<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
660<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js"></script>
661<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
662<script src="/webjars/solverforge/js/solverforge-webui.js"></script>
663<script src="app.js"></script>
664<!-- ============================================================
665     ADVANCED CONFIGURATION SCRIPT (OPTIONAL)
666     Remove this script for a simpler quickstart.
667     Also remove the "Advanced" button and panel in the HTML above.
668     ============================================================ -->
669<script src="config.js"></script>
670<!-- END ADVANCED CONFIGURATION SCRIPT -->
671</body>
672</html>
673