Move hard conflict and oversized group detection from JS to server
This commit is contained in:
121
main.go
121
main.go
@@ -808,6 +808,13 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc {
|
|||||||
KindB string `json:"kind_b"`
|
KindB string `json:"kind_b"`
|
||||||
}
|
}
|
||||||
var mismatches []mismatchEntry
|
var mismatches []mismatchEntry
|
||||||
|
type conflictLink struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
}
|
||||||
|
var hardConflicts [][]conflictLink
|
||||||
|
var oversizedGroups [][]string
|
||||||
|
|
||||||
if role == "admin" {
|
if role == "admin" {
|
||||||
type pairKey struct{ a, b int64 }
|
type pairKey struct{ a, b int64 }
|
||||||
@@ -890,10 +897,122 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sRows, err := db.Query("SELECT id, name FROM students WHERE trip_id = $1", tripID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer sRows.Close()
|
||||||
|
studentName := map[int64]string{}
|
||||||
|
var studentIDs []int64
|
||||||
|
for sRows.Next() {
|
||||||
|
var id int64
|
||||||
|
var name string
|
||||||
|
if err := sRows.Scan(&id, &name); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
studentName[id] = name
|
||||||
|
studentIDs = append(studentIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
mustAdj := map[int64][]int64{}
|
||||||
|
ufParent := map[int64]int64{}
|
||||||
|
for _, id := range studentIDs {
|
||||||
|
ufParent[id] = id
|
||||||
|
}
|
||||||
|
var ufFind func(int64) int64
|
||||||
|
ufFind = func(x int64) int64 {
|
||||||
|
if ufParent[x] != x {
|
||||||
|
ufParent[x] = ufFind(ufParent[x])
|
||||||
|
}
|
||||||
|
return ufParent[x]
|
||||||
|
}
|
||||||
|
for _, o := range overalls {
|
||||||
|
if o.Kind == "must" {
|
||||||
|
mustAdj[o.StudentAID] = append(mustAdj[o.StudentAID], o.StudentBID)
|
||||||
|
mustAdj[o.StudentBID] = append(mustAdj[o.StudentBID], o.StudentAID)
|
||||||
|
ra, rb := ufFind(o.StudentAID), ufFind(o.StudentBID)
|
||||||
|
if ra != rb {
|
||||||
|
ufParent[ra] = rb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMustPath := func(from, to int64) []int64 {
|
||||||
|
if from == to {
|
||||||
|
return []int64{from}
|
||||||
|
}
|
||||||
|
visited := map[int64]bool{from: true}
|
||||||
|
type qEntry struct{ path []int64 }
|
||||||
|
queue := []qEntry{{[]int64{from}}}
|
||||||
|
for len(queue) > 0 {
|
||||||
|
entry := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
curr := entry.path[len(entry.path)-1]
|
||||||
|
for _, next := range mustAdj[curr] {
|
||||||
|
if next == to {
|
||||||
|
return append(entry.path, next)
|
||||||
|
}
|
||||||
|
if !visited[next] {
|
||||||
|
visited[next] = true
|
||||||
|
p := make([]int64, len(entry.path)+1)
|
||||||
|
copy(p, entry.path)
|
||||||
|
p[len(entry.path)] = next
|
||||||
|
queue = append(queue, qEntry{p})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range overalls {
|
||||||
|
if o.Kind != "must_not" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ufFind(o.StudentAID) != ufFind(o.StudentBID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
path := findMustPath(o.StudentBID, o.StudentAID)
|
||||||
|
if path == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var chain []conflictLink
|
||||||
|
for i := range len(path) - 1 {
|
||||||
|
x, y := path[i], path[i+1]
|
||||||
|
if overallMap[pairKey{x, y}].Kind == "must" {
|
||||||
|
chain = append(chain, conflictLink{studentName[x], studentName[y], "must"})
|
||||||
|
} else {
|
||||||
|
chain = append(chain, conflictLink{studentName[y], studentName[x], "must"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chain = append(chain, conflictLink{studentName[o.StudentAID], studentName[o.StudentBID], "must_not"})
|
||||||
|
hardConflicts = append(hardConflicts, chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomSize int
|
||||||
|
db.QueryRow("SELECT room_size FROM trips WHERE id = $1", tripID).Scan(&roomSize)
|
||||||
|
mustGroups := map[int64][]string{}
|
||||||
|
for _, id := range studentIDs {
|
||||||
|
root := ufFind(id)
|
||||||
|
mustGroups[root] = append(mustGroups[root], studentName[id])
|
||||||
|
}
|
||||||
|
for _, members := range mustGroups {
|
||||||
|
if len(members) > roomSize {
|
||||||
|
oversizedGroups = append(oversizedGroups, members)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if mismatches == nil {
|
if mismatches == nil {
|
||||||
mismatches = []mismatchEntry{}
|
mismatches = []mismatchEntry{}
|
||||||
}
|
}
|
||||||
|
if hardConflicts == nil {
|
||||||
|
hardConflicts = [][]conflictLink{}
|
||||||
|
}
|
||||||
|
if oversizedGroups == nil {
|
||||||
|
oversizedGroups = [][]string{}
|
||||||
|
}
|
||||||
if overrides == nil {
|
if overrides == nil {
|
||||||
overrides = []overrideEntry{}
|
overrides = []overrideEntry{}
|
||||||
}
|
}
|
||||||
@@ -902,7 +1021,7 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]any{"constraints": constraints, "overrides": overrides, "overalls": overalls, "mismatches": mismatches})
|
json.NewEncoder(w).Encode(map[string]any{"constraints": constraints, "overrides": overrides, "overalls": overalls, "mismatches": mismatches, "hard_conflicts": hardConflicts, "oversized_groups": oversizedGroups})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,90 +82,8 @@ async function loadStudents() {
|
|||||||
lastOveralls = allOveralls;
|
lastOveralls = allOveralls;
|
||||||
|
|
||||||
const mismatchList = constraintData.mismatches;
|
const mismatchList = constraintData.mismatches;
|
||||||
|
const hardConflictList = constraintData.hard_conflicts;
|
||||||
const studentName = {};
|
const oversizedGroups = constraintData.oversized_groups;
|
||||||
for (const s of students) studentName[s.id] = s.name;
|
|
||||||
|
|
||||||
const mustAdj = {};
|
|
||||||
for (const s of students) {
|
|
||||||
for (const [bId, eff] of Object.entries(allOveralls[s.id])) {
|
|
||||||
if (eff.kind === 'must') {
|
|
||||||
const a = s.id, b = parseInt(bId);
|
|
||||||
if (!mustAdj[a]) mustAdj[a] = [];
|
|
||||||
mustAdj[a].push(b);
|
|
||||||
if (!mustAdj[b]) mustAdj[b] = [];
|
|
||||||
mustAdj[b].push(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ufParent = {};
|
|
||||||
for (const s of students) ufParent[s.id] = s.id;
|
|
||||||
const ufFind = (x) => {
|
|
||||||
if (ufParent[x] !== x) ufParent[x] = ufFind(ufParent[x]);
|
|
||||||
return ufParent[x];
|
|
||||||
};
|
|
||||||
for (const s of students) {
|
|
||||||
for (const [bId, eff] of Object.entries(allOveralls[s.id])) {
|
|
||||||
if (eff.kind === 'must') {
|
|
||||||
const ra = ufFind(s.id), rb = ufFind(parseInt(bId));
|
|
||||||
if (ra !== rb) ufParent[ra] = rb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const findMustPath = (from, to) => {
|
|
||||||
if (from === to) return [from];
|
|
||||||
const visited = new Set([from]);
|
|
||||||
const queue = [[from]];
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const path = queue.shift();
|
|
||||||
const curr = path[path.length - 1];
|
|
||||||
for (const next of (mustAdj[curr] || [])) {
|
|
||||||
if (next === to) return [...path, next];
|
|
||||||
if (!visited.has(next)) {
|
|
||||||
visited.add(next);
|
|
||||||
queue.push([...path, next]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const hardConflictList = [];
|
|
||||||
for (const s of students) {
|
|
||||||
for (const [bId, eff] of Object.entries(allOveralls[s.id])) {
|
|
||||||
if (eff.kind !== 'must_not') continue;
|
|
||||||
const b = parseInt(bId);
|
|
||||||
if (ufFind(s.id) !== ufFind(b)) continue;
|
|
||||||
const path = findMustPath(b, s.id);
|
|
||||||
if (!path) continue;
|
|
||||||
const chain = [];
|
|
||||||
for (let i = 0; i < path.length - 1; i++) {
|
|
||||||
const x = path[i], y = path[i + 1];
|
|
||||||
if (allOveralls[x]?.[y]?.kind === 'must') {
|
|
||||||
chain.push({ from: studentName[x], to: studentName[y], kind: 'must' });
|
|
||||||
} else {
|
|
||||||
chain.push({ from: studentName[y], to: studentName[x], kind: 'must' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chain.push({ from: s.name, to: studentName[b], kind: 'must_not' });
|
|
||||||
hardConflictList.push(chain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mustGroups = {};
|
|
||||||
for (const s of students) {
|
|
||||||
const root = ufFind(s.id);
|
|
||||||
if (!mustGroups[root]) mustGroups[root] = [];
|
|
||||||
mustGroups[root].push(s.name);
|
|
||||||
}
|
|
||||||
const oversizedGroups = [];
|
|
||||||
for (const members of Object.values(mustGroups)) {
|
|
||||||
if (members.length > trip.room_size) {
|
|
||||||
oversizedGroups.push(members);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const conflictsEl = document.getElementById('conflicts');
|
const conflictsEl = document.getElementById('conflicts');
|
||||||
const conflictsWasOpen = conflictsEl.querySelector('wa-details')?.open;
|
const conflictsWasOpen = conflictsEl.querySelector('wa-details')?.open;
|
||||||
|
|||||||
Reference in New Issue
Block a user