blackopsrepl/portfolio-optimization-python
1
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 