Files
LiXX_Cell_Pack_Matcher/index.html
localhorst 515fc24f1d Rewrite as static webapp (#1)
Reviewed-on: #1
Co-authored-by: localhorst <localhorst@mosad.xyz>
Co-committed-by: localhorst <localhorst@mosad.xyz>
2025-12-21 09:21:09 +01:00

337 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LiXX Cell Pack Matcher</title>
<link rel="stylesheet" href="css/styles.css">
<link rel="icon" href="data/favicon.svg" type="image/svg+xml">
</head>
<body>
<div class="container">
<header>
<div class="logo">
<img src="data/favicon.svg" width="40" />
<h1>LiXX Cell Pack Matcher</h1>
</div>
<p class="subtitle">Optimal cell matching for lithium battery packs</p>
</header>
<main>
<!-- Configuration Section -->
<section class="card" aria-labelledby="config-heading">
<h2 id="config-heading">Pack Configuration</h2>
<div class="config-grid">
<div class="form-group">
<label for="cells-serial">Series (S)</label>
<input type="number" id="cells-serial" min="1" max="42" value="6"
aria-describedby="serial-help">
<small id="serial-help">Number of cells in series</small>
</div>
<div class="form-group">
<label for="cells-parallel">Parallel (P)</label>
<input type="number" id="cells-parallel" min="1" max="42" value="2"
aria-describedby="parallel-help">
<small id="parallel-help">Number of cells in parallel</small>
</div>
<div class="form-group">
<label for="config-display">Configuration</label>
<output id="config-display" class="config-badge">6S2P</output>
<small>Total: <span id="total-cells-needed">12</span> cells</small>
</div>
</div>
</section>
<!-- Cell Input Section -->
<section class="card" aria-labelledby="cells-heading">
<h2 id="cells-heading">Cell Data</h2>
<div class="cell-input-header">
<p>Enter cell data: Label, Capacity (mAh), Internal Resistance (mΩ, optional)</p>
<div class="button-group">
<button type="button" id="btn-add-cell" class="btn btn-secondary">
<span aria-hidden="true">+</span> Add Cell
</button>
<button type="button" id="btn-load-example" class="btn btn-ghost">
Load Example
</button>
<button type="button" id="btn-clear-all" class="btn btn-ghost btn-danger">
Clear All
</button>
</div>
</div>
<div class="cell-table-wrapper">
<table class="cell-table" id="cell-table" role="grid">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Label</th>
<th scope="col">Capacity (mAh)</th>
<th scope="col">IR (mΩ)</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody id="cell-tbody">
<!-- Cells will be added dynamically -->
</tbody>
</table>
</div>
<div class="cell-stats" id="cell-stats" aria-live="polite">
<span>Cells: <strong id="stat-count">0</strong></span>
<span>Avg Capacity: <strong id="stat-avg-cap">-</strong></span>
<span>Avg IR: <strong id="stat-avg-ir">-</strong></span>
</div>
</section>
<!-- Algorithm Settings -->
<section class="card" aria-labelledby="algo-heading">
<h2 id="algo-heading">Matching Settings</h2>
<div class="settings-grid">
<div class="form-group">
<label for="weight-capacity">Capacity Weight</label>
<input type="range" id="weight-capacity" min="0" max="100" value="70"
aria-describedby="weight-cap-value">
<output id="weight-cap-value">70%</output>
</div>
<div class="form-group">
<label for="weight-ir">IR Weight</label>
<input type="range" id="weight-ir" min="0" max="100" value="30"
aria-describedby="weight-ir-value">
<output id="weight-ir-value">30%</output>
</div>
<div class="form-group">
<label for="algorithm-select">Algorithm</label>
<select id="algorithm-select">
<option value="exhaustive">Exhaustive Search (Small packs only)</option>
</select>
</div>
<div class="form-group">
<label for="max-iterations">Max Iterations</label>
<input type="number" id="max-iterations" min="100" max="100000" value="5000">
</div>
</div>
<button type="button" id="btn-start-matching" class="btn btn-primary btn-large">
<span class="btn-icon" aria-hidden="true"></span>
Start Matching
</button>
</section>
<!-- Progress Section -->
<section class="card" id="progress-section" hidden aria-labelledby="progress-heading">
<h2 id="progress-heading">Matching Progress</h2>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
</div>
<div class="progress-stats-grid">
<div class="stat-item">
<span class="stat-label">Iteration</span>
<span class="stat-value" id="progress-iteration">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Combinations</span>
<span class="stat-value" id="progress-combinations">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Best Score</span>
<span class="stat-value stat-highlight" id="progress-score">-</span>
</div>
<div class="stat-item">
<span class="stat-label">Elapsed</span>
<span class="stat-value" id="progress-time">0s</span>
</div>
<div class="stat-item">
<span class="stat-label">Speed</span>
<span class="stat-value" id="progress-speed">-</span>
</div>
<div class="stat-item">
<span class="stat-label">ETA</span>
<span class="stat-value" id="progress-eta">-</span>
</div>
</div>
<!-- Live Best Configuration Preview -->
<div class="live-preview" id="live-preview" hidden>
<h3>Current Best Configuration</h3>
<div class="live-preview-content" id="live-preview-content"></div>
</div>
</div>
<button type="button" id="btn-stop-matching" class="btn btn-ghost btn-danger">
Stop Matching
</button>
</section>
<!-- Results Section -->
<section class="card" id="results-section" hidden aria-labelledby="results-heading">
<h2 id="results-heading">Matching Results</h2>
<!-- Warning Banner -->
<div class="warning-banner" role="alert">
<div class="warning-icon" aria-hidden="true">⚠️</div>
<div class="warning-content">
<strong>Safety Warning - Used Lithium Cells</strong>
<ul>
<li>Used cells may have hidden defects not detectable by capacity/IR testing</li>
<li>Internal resistance mismatch >20% can reduce cycle life by up to 40%</li>
<li>Always use a BMS with cell-level monitoring and balancing</li>
<li>Use only same model of cells in a pack</li>
<li>Never charge unattended; use fireproof storage</li>
<li>Cells with significantly different ages may degrade unpredictably</li>
</ul>
</div>
</div>
<!-- Results Summary -->
<div class="results-summary" id="results-summary">
<div class="result-metric" tabindex="0" data-tooltip="match-score">
<span class="metric-value" id="result-score">-</span>
<span class="metric-label">Match Score</span>
<div class="metric-tooltip">
<strong>Match Score</strong>
<p>Combined score from capacity and IR variance, weighted by your settings.</p>
<div class="tooltip-scale">
<span class="scale-good">&lt; 0.5 = Excellent</span>
<span class="scale-ok">0.5 - 2.0 = Good</span>
<span class="scale-bad">&gt; 2.0 = Poor</span>
</div>
<p class="tooltip-hint">Lower is better</p>
</div>
</div>
<div class="result-metric" tabindex="0" data-tooltip="cap-cv">
<span class="metric-value" id="result-cap-variance">-</span>
<span class="metric-label">Capacity CV%</span>
<div class="metric-tooltip">
<strong>Capacity Coefficient of Variation</strong>
<p>Measures how evenly matched the total capacity of each parallel group is. CV = (σ / μ) ×
100%</p>
<div class="tooltip-scale">
<span class="scale-good">&lt; 1% = Excellent</span>
<span class="scale-ok">1 - 3% = Acceptable</span>
<span class="scale-bad">&gt; 3% = Poor balance</span>
</div>
<p class="tooltip-hint">Lower is better ensures even discharge across series groups</p>
</div>
</div>
<div class="result-metric" tabindex="0" data-tooltip="ir-cv">
<span class="metric-value" id="result-ir-variance">-</span>
<span class="metric-label">IR CV%</span>
<div class="metric-tooltip">
<strong>Internal Resistance Variation</strong>
<p>Average variation of internal resistance within parallel groups. High mismatch causes
uneven current distribution.</p>
<div class="tooltip-scale">
<span class="scale-good">&lt; 5% = Excellent</span>
<span class="scale-ok">5 - 15% = Acceptable</span>
<span class="scale-bad">&gt; 20% = Risk of 40% lifetime reduction</span>
</div>
<p class="tooltip-hint">Lower is better critical for high-drain applications</p>
</div>
</div>
<div class="result-metric" tabindex="0" data-tooltip="pack-cap">
<span class="metric-value" id="result-pack-capacity">-</span>
<span class="metric-label">Pack Capacity</span>
<div class="metric-tooltip">
<strong>Effective Pack Capacity</strong>
<p>The usable capacity of your pack, limited by the smallest parallel group (weakest link).
</p>
<p>Formula: min(group capacities)</p>
<p class="tooltip-hint">Higher is better well-matched cells maximize this value</p>
</div>
</div>
</div>
<!-- Visual Pack Layout -->
<div class="pack-visualization" id="pack-visualization">
<h3>Pack Layout</h3>
<div class="pack-grid" id="pack-grid">
<!-- Generated dynamically -->
</div>
<div class="pack-legend">
<span><span class="legend-color" style="background: var(--cell-low)"></span> Lower
Capacity</span>
<span><span class="legend-color" style="background: var(--cell-mid)"></span> Average</span>
<span><span class="legend-color" style="background: var(--cell-high)"></span> Higher
Capacity</span>
</div>
</div>
<!-- Detailed Results Table -->
<div class="results-table-wrapper">
<h3>Parallel Group Details</h3>
<table class="results-table" id="results-table">
<thead>
<tr>
<th>Group</th>
<th>Cells</th>
<th>Total Capacity</th>
<th>Avg IR</th>
<th>Deviation</th>
</tr>
</thead>
<tbody id="results-tbody">
<!-- Generated dynamically -->
</tbody>
</table>
</div>
<!-- Excluded Cells -->
<div class="excluded-cells" id="excluded-cells-section" hidden>
<h3>Excluded Cells</h3>
<p id="excluded-cells-list"></p>
</div>
<!-- Export Buttons -->
<div class="export-buttons">
<button type="button" id="btn-export-json" class="btn btn-secondary">
Export JSON
</button>
<button type="button" id="btn-export-csv" class="btn btn-secondary">
Export CSV
</button>
<button type="button" id="btn-copy-results" class="btn btn-secondary">
Copy to Clipboard
</button>
</div>
</section>
</main>
<footer>
<p>
<a href="https://git.mosad.xyz/localhorst/LiXX_Cell_Pack_Matcher" target="_blank" rel="noopener">Git</a>
·
Based on research by
<a href="https://doi.org/10.1016/j.jpowsour.2013.11.064" target="_blank" rel="noopener">Wang et al.,
2013</a>
</p>
<p class="disclaimer">
This tool is for educational purposes. Always consult professional guidance for battery pack assembly.
</p>
</footer>
</div>
<!-- Keyboard Shortcuts Modal -->
<dialog id="shortcuts-dialog">
<h2>Keyboard Shortcuts</h2>
<dl class="shortcuts-list">
<dt><kbd>Alt</kbd> + <kbd>A</kbd></dt>
<dd>Add new cell</dd>
<dt><kbd>Alt</kbd> + <kbd>S</kbd></dt>
<dd>Start matching</dd>
<dt><kbd>Alt</kbd> + <kbd>E</kbd></dt>
<dd>Load example data</dd>
<dt><kbd>Esc</kbd></dt>
<dd>Stop matching / Close dialog</dd>
<dt><kbd>?</kbd></dt>
<dd>Show this help</dd>
</dl>
<button type="button" class="btn btn-primary" id="btn-close-shortcuts">Close</button>
</dialog>
<script src="js/app.js"></script>
</body>
</html>