feat: expose objective_factors and objective_mode in web UI
The solver form gains an Objective section with a product/sum mode select and per-resource factor inputs (E/B/S/C, zero = excluded). The /solve handler parses both fields and forwards them to solve(), falling back to solve.py defaults when omitted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
47e21678df
commit
24f58765ac
2 changed files with 68 additions and 0 deletions
|
|
@ -414,6 +414,47 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Objective Section -->
|
||||
<div class="form-section">
|
||||
<h2>Objective</h2>
|
||||
<p style="color: #666; font-size: 13px; margin-bottom: 15px;">
|
||||
Factor = exponent in product mode, weight in sum mode. A factor of 0 excludes that
|
||||
resource from the objective (it is not forced to zero). Negative factors are only
|
||||
allowed in sum mode. Factors are unaffected by the internal x10 resource scaling.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="objective_mode">Mode</label>
|
||||
<select id="objective_mode" name="objective_mode">
|
||||
<option value="product" {% if objective_mode=="product" %}selected{% endif %}>Product
|
||||
(maximize E^a × B^b × ...)</option>
|
||||
<option value="sum" {% if objective_mode=="sum" %}selected{% endif %}>Sum
|
||||
(maximize a·E + b·B + ...)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="resource-inputs">
|
||||
<div class="form-group">
|
||||
<label for="objective_factor_E">Electrum Factor</label>
|
||||
<input type="number" id="objective_factor_E" name="objective_factor_E"
|
||||
value="{{ objective_factors.get('E', 0) }}" step="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="objective_factor_B">Brass Factor</label>
|
||||
<input type="number" id="objective_factor_B" name="objective_factor_B"
|
||||
value="{{ objective_factors.get('B', 0) }}" step="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="objective_factor_S">Steel Factor</label>
|
||||
<input type="number" id="objective_factor_S" name="objective_factor_S"
|
||||
value="{{ objective_factors.get('S', 0) }}" step="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="objective_factor_C">Capital Factor</label>
|
||||
<input type="number" id="objective_factor_C" name="objective_factor_C"
|
||||
value="{{ objective_factors.get('C', 0) }}" step="1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Solver Settings Section -->
|
||||
<div class="form-section">
|
||||
<h2>Solver Settings</h2>
|
||||
|
|
@ -699,6 +740,17 @@
|
|||
data[input.name] = input.checked;
|
||||
});
|
||||
|
||||
// Collect objective mode and factors (only nonzero factors; missing = excluded)
|
||||
data.objective_mode = document.getElementById('objective_mode').value;
|
||||
const objectiveFactors = {};
|
||||
['E', 'B', 'S', 'C'].forEach(r => {
|
||||
const factor = parseInt(document.getElementById(`objective_factor_${r}`).value);
|
||||
if (factor) {
|
||||
objectiveFactors[r] = factor;
|
||||
}
|
||||
});
|
||||
data.objective_factors = objectiveFactors;
|
||||
|
||||
// Collect fixed action constraints
|
||||
const fixedActions = {};
|
||||
form.querySelectorAll('[id^="action-constraint-"]').forEach(row => {
|
||||
|
|
|
|||
16
web_solve.py
16
web_solve.py
|
|
@ -21,6 +21,8 @@ def index():
|
|||
actions=solve.ENABLED_ACTIONS,
|
||||
agents=solve.AGENT_AVAILABILITY,
|
||||
num_steps=solve.NUM_STEPS,
|
||||
objective_factors=solve.OBJECTIVE_FACTORS,
|
||||
objective_mode=solve.OBJECTIVE_MODE,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -138,6 +140,18 @@ def solve_handler():
|
|||
city, step = int(city), int(step)
|
||||
fixed_choices["governors"][(city, step, agent)] = enabled
|
||||
|
||||
# Parse objective (factor = exponent in "product" mode, weight in
|
||||
# "sum" mode; missing keys = resource excluded). Factors are NOT x10
|
||||
# scaled — they apply to the scaled resource totals and the report
|
||||
# descales the objective. None falls back to solve.py's defaults.
|
||||
objective_mode = data.get("objective_mode") or None
|
||||
objective_factors = None
|
||||
objective_factors_data = data.get("objective_factors")
|
||||
if objective_factors_data is not None:
|
||||
# All-zero dict passes through so solve()'s validation rejects it
|
||||
# with a clear error instead of silently using the defaults.
|
||||
objective_factors = {r: int(f) for r, f in objective_factors_data.items()}
|
||||
|
||||
# Parse resource constraints
|
||||
resource_constraints = None
|
||||
resource_constraints_data = data.get("resource_constraints", [])
|
||||
|
|
@ -165,6 +179,8 @@ def solve_handler():
|
|||
verbose=verbose,
|
||||
fixed_choices=fixed_choices,
|
||||
resource_constraints=resource_constraints,
|
||||
objective_factors=objective_factors,
|
||||
objective_mode=objective_mode,
|
||||
)
|
||||
|
||||
output = output_buffer.getvalue()
|
||||
|
|
|
|||
Loading…
Reference in a new issue