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>
|
||||||
</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 -->
|
<!-- Solver Settings Section -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<h2>Solver Settings</h2>
|
<h2>Solver Settings</h2>
|
||||||
|
|
@ -699,6 +740,17 @@
|
||||||
data[input.name] = input.checked;
|
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
|
// Collect fixed action constraints
|
||||||
const fixedActions = {};
|
const fixedActions = {};
|
||||||
form.querySelectorAll('[id^="action-constraint-"]').forEach(row => {
|
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,
|
actions=solve.ENABLED_ACTIONS,
|
||||||
agents=solve.AGENT_AVAILABILITY,
|
agents=solve.AGENT_AVAILABILITY,
|
||||||
num_steps=solve.NUM_STEPS,
|
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)
|
city, step = int(city), int(step)
|
||||||
fixed_choices["governors"][(city, step, agent)] = enabled
|
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
|
# Parse resource constraints
|
||||||
resource_constraints = None
|
resource_constraints = None
|
||||||
resource_constraints_data = data.get("resource_constraints", [])
|
resource_constraints_data = data.get("resource_constraints", [])
|
||||||
|
|
@ -165,6 +179,8 @@ def solve_handler():
|
||||||
verbose=verbose,
|
verbose=verbose,
|
||||||
fixed_choices=fixed_choices,
|
fixed_choices=fixed_choices,
|
||||||
resource_constraints=resource_constraints,
|
resource_constraints=resource_constraints,
|
||||||
|
objective_factors=objective_factors,
|
||||||
|
objective_mode=objective_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
output = output_buffer.getvalue()
|
output = output_buffer.getvalue()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue