Add configurable no-prefer penalty to solver
This commit is contained in:
48
main.go
48
main.go
@@ -240,13 +240,13 @@ func handleListTrips(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
rows, err := db.Query(`
|
rows, err := db.Query(`
|
||||||
SELECT t.id, t.name, t.room_size, t.prefer_not_multiple, COALESCE(
|
SELECT t.id, t.name, t.room_size, t.prefer_not_multiple, t.no_prefer_cost, COALESCE(
|
||||||
json_agg(json_build_object('id', ta.id, 'email', ta.email)) FILTER (WHERE ta.id IS NOT NULL),
|
json_agg(json_build_object('id', ta.id, 'email', ta.email)) FILTER (WHERE ta.id IS NOT NULL),
|
||||||
'[]'
|
'[]'
|
||||||
)
|
)
|
||||||
FROM trips t
|
FROM trips t
|
||||||
LEFT JOIN trip_admins ta ON ta.trip_id = t.id
|
LEFT JOIN trip_admins ta ON ta.trip_id = t.id
|
||||||
GROUP BY t.id, t.name, t.room_size, t.prefer_not_multiple
|
GROUP BY t.id, t.name, t.room_size, t.prefer_not_multiple, t.no_prefer_cost
|
||||||
ORDER BY t.id`)
|
ORDER BY t.id`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
@@ -263,6 +263,7 @@ func handleListTrips(db *sql.DB) http.HandlerFunc {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
RoomSize int `json:"room_size"`
|
RoomSize int `json:"room_size"`
|
||||||
PreferNotMultiple int `json:"prefer_not_multiple"`
|
PreferNotMultiple int `json:"prefer_not_multiple"`
|
||||||
|
NoPreferCost int `json:"no_prefer_cost"`
|
||||||
Admins []tripAdmin `json:"admins"`
|
Admins []tripAdmin `json:"admins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,7 +271,7 @@ func handleListTrips(db *sql.DB) http.HandlerFunc {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var t trip
|
var t trip
|
||||||
var adminsJSON string
|
var adminsJSON string
|
||||||
if err := rows.Scan(&t.ID, &t.Name, &t.RoomSize, &t.PreferNotMultiple, &adminsJSON); err != nil {
|
if err := rows.Scan(&t.ID, &t.Name, &t.RoomSize, &t.PreferNotMultiple, &t.NoPreferCost, &adminsJSON); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -389,14 +390,14 @@ func handleGetTrip(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var name string
|
var name string
|
||||||
var roomSize, preferNotMultiple int
|
var roomSize, preferNotMultiple, noPreferCost int
|
||||||
err := db.QueryRow("SELECT name, room_size, prefer_not_multiple FROM trips WHERE id = $1", tripID).Scan(&name, &roomSize, &preferNotMultiple)
|
err := db.QueryRow("SELECT name, room_size, prefer_not_multiple, no_prefer_cost FROM trips WHERE id = $1", tripID).Scan(&name, &roomSize, &preferNotMultiple, &noPreferCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "trip not found", http.StatusNotFound)
|
http.Error(w, "trip not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]any{"id": tripID, "name": name, "room_size": roomSize, "prefer_not_multiple": preferNotMultiple})
|
json.NewEncoder(w).Encode(map[string]any{"id": tripID, "name": name, "room_size": roomSize, "prefer_not_multiple": preferNotMultiple, "no_prefer_cost": noPreferCost})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,6 +565,7 @@ func handleUpdateTrip(db *sql.DB) http.HandlerFunc {
|
|||||||
var body struct {
|
var body struct {
|
||||||
RoomSize *int `json:"room_size"`
|
RoomSize *int `json:"room_size"`
|
||||||
PreferNotMultiple *int `json:"prefer_not_multiple"`
|
PreferNotMultiple *int `json:"prefer_not_multiple"`
|
||||||
|
NoPreferCost *int `json:"no_prefer_cost"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||||
@@ -581,6 +583,12 @@ func handleUpdateTrip(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if body.NoPreferCost != nil {
|
||||||
|
if *body.NoPreferCost < 0 {
|
||||||
|
http.Error(w, "no_prefer_cost must be at least 0", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if body.RoomSize != nil {
|
if body.RoomSize != nil {
|
||||||
if _, err := db.Exec("UPDATE trips SET room_size = $1 WHERE id = $2", *body.RoomSize, tripID); err != nil {
|
if _, err := db.Exec("UPDATE trips SET room_size = $1 WHERE id = $2", *body.RoomSize, tripID); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
@@ -593,6 +601,12 @@ func handleUpdateTrip(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if body.NoPreferCost != nil {
|
||||||
|
if _, err := db.Exec("UPDATE trips SET no_prefer_cost = $1 WHERE id = $2", *body.NoPreferCost, tripID); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -729,8 +743,8 @@ func handleSolve(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomSize, pnMultiple int
|
var roomSize, pnMultiple, npCost int
|
||||||
err := db.QueryRow("SELECT room_size, prefer_not_multiple FROM trips WHERE id = $1", tripID).Scan(&roomSize, &pnMultiple)
|
err := db.QueryRow("SELECT room_size, prefer_not_multiple, no_prefer_cost FROM trips WHERE id = $1", tripID).Scan(&roomSize, &pnMultiple, &npCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "trip not found", http.StatusNotFound)
|
http.Error(w, "trip not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -863,18 +877,34 @@ func handleSolve(db *sql.DB) http.HandlerFunc {
|
|||||||
groups[root] = append(groups[root], i)
|
groups[root] = append(groups[root], i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasPrefer := make([]bool, n)
|
||||||
|
for pk, kind := range overalls {
|
||||||
|
if kind == "prefer" {
|
||||||
|
hasPrefer[idx[pk.a]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
score := func(assignment []int) int {
|
score := func(assignment []int) int {
|
||||||
s := 0
|
s := 0
|
||||||
|
gotPrefer := make([]bool, n)
|
||||||
for pk, kind := range overalls {
|
for pk, kind := range overalls {
|
||||||
ai, bi := idx[pk.a], idx[pk.b]
|
ai, bi := idx[pk.a], idx[pk.b]
|
||||||
sameRoom := assignment[ai] == assignment[bi]
|
sameRoom := assignment[ai] == assignment[bi]
|
||||||
switch kind {
|
switch kind {
|
||||||
case "prefer":
|
case "prefer":
|
||||||
if sameRoom { s++ }
|
if sameRoom {
|
||||||
|
s++
|
||||||
|
gotPrefer[ai] = true
|
||||||
|
}
|
||||||
case "prefer_not":
|
case "prefer_not":
|
||||||
if sameRoom { s -= pnMultiple }
|
if sameRoom { s -= pnMultiple }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if hasPrefer[i] && !gotPrefer[i] {
|
||||||
|
s -= npCost
|
||||||
|
}
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ CREATE TABLE IF NOT EXISTS trips (
|
|||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
room_size INTEGER NOT NULL DEFAULT 2,
|
room_size INTEGER NOT NULL DEFAULT 2,
|
||||||
prefer_not_multiple INTEGER NOT NULL DEFAULT 5
|
prefer_not_multiple INTEGER NOT NULL DEFAULT 5,
|
||||||
|
no_prefer_cost INTEGER NOT NULL DEFAULT 10
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS trip_admins (
|
CREATE TABLE IF NOT EXISTS trip_admins (
|
||||||
|
|||||||
@@ -87,6 +87,7 @@
|
|||||||
<div id="trip-settings">
|
<div id="trip-settings">
|
||||||
<label>Students per room: <input id="room-size" type="number" min="1"></label>
|
<label>Students per room: <input id="room-size" type="number" min="1"></label>
|
||||||
<label>Prefer Not cost: <input id="pn-multiple" type="number" min="1"></label>
|
<label>Prefer Not cost: <input id="pn-multiple" type="number" min="1"></label>
|
||||||
|
<label>No Prefer cost: <input id="np-cost" type="number" min="0"></label>
|
||||||
</div>
|
</div>
|
||||||
<div id="conflicts"></div>
|
<div id="conflicts"></div>
|
||||||
<div id="mismatches"></div>
|
<div id="mismatches"></div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ try {
|
|||||||
document.getElementById('trip-name').textContent = trip.name;
|
document.getElementById('trip-name').textContent = trip.name;
|
||||||
document.getElementById('room-size').value = trip.room_size;
|
document.getElementById('room-size').value = trip.room_size;
|
||||||
document.getElementById('pn-multiple').value = trip.prefer_not_multiple;
|
document.getElementById('pn-multiple').value = trip.prefer_not_multiple;
|
||||||
|
document.getElementById('np-cost').value = trip.no_prefer_cost;
|
||||||
document.getElementById('main').style.display = 'block';
|
document.getElementById('main').style.display = 'block';
|
||||||
document.getElementById('logout-btn').addEventListener('click', logout);
|
document.getElementById('logout-btn').addEventListener('click', logout);
|
||||||
document.getElementById('room-size').addEventListener('change', async () => {
|
document.getElementById('room-size').addEventListener('change', async () => {
|
||||||
@@ -27,6 +28,10 @@ document.getElementById('pn-multiple').addEventListener('change', async () => {
|
|||||||
const val = parseInt(document.getElementById('pn-multiple').value);
|
const val = parseInt(document.getElementById('pn-multiple').value);
|
||||||
if (val >= 1) await api('PATCH', '/api/trips/' + tripID, { prefer_not_multiple: val });
|
if (val >= 1) await api('PATCH', '/api/trips/' + tripID, { prefer_not_multiple: val });
|
||||||
});
|
});
|
||||||
|
document.getElementById('np-cost').addEventListener('change', async () => {
|
||||||
|
const val = parseInt(document.getElementById('np-cost').value);
|
||||||
|
if (val >= 0) await api('PATCH', '/api/trips/' + tripID, { no_prefer_cost: val });
|
||||||
|
});
|
||||||
|
|
||||||
let lastOveralls = {};
|
let lastOveralls = {};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user