Agent-Praesenz mit Zehn-Minuten-Timeout versehen
Ein registrierter Agent gilt im Bueromonitor nur noch als anwesend, solange last_seen_at hoechstens zehn Minuten zurueckliegt (isAgentPresent, office-scene.js). Meldet er sich laenger nicht, bleibt sein Schreibtisch frei und er erscheint auch nicht mehr im Ueberlauf-Ruheplatz vor der Tuer, statt weiterhin faelschlich als anwesend dargestellt zu werden. Schliesst #17
This commit is contained in:
parent
db92f5126e
commit
e96b2e1843
3 changed files with 65 additions and 2 deletions
|
|
@ -27,6 +27,7 @@ import {
|
|||
mapAgentsToDesks,
|
||||
normalizeAgentForDisplay,
|
||||
resolveAgentVisualState,
|
||||
isAgentPresent,
|
||||
pickIdleActivity,
|
||||
AnimationQueue,
|
||||
detectDoorVisits,
|
||||
|
|
@ -1003,6 +1004,7 @@ function agentPlacements(agents, deskLayout) {
|
|||
let overflowIndex = 0;
|
||||
for (const raw of agents) {
|
||||
if (seatedIds.has(raw.id)) continue;
|
||||
if (!isAgentPresent(raw)) continue;
|
||||
const agent = normalizeAgentForDisplay(raw);
|
||||
const homeFeet = {
|
||||
x: 70 + (overflowIndex % 3) * 26,
|
||||
|
|
|
|||
|
|
@ -355,20 +355,49 @@ export function resolveAgentVisualState(state) {
|
|||
return AGENT_STATE_VISUALS[state] ?? AGENT_STATE_VISUALS.idle;
|
||||
}
|
||||
|
||||
// Praesenz-Timeout (Issue #17): ein registrierter Agent gilt nur noch als anwesend, solange
|
||||
// last_seen_at nicht laenger als zehn Minuten zurueckliegt. Meldet er sich laenger nicht,
|
||||
// bleibt sein Schreibtisch/Platz leer, obwohl er weiterhin registriert ist.
|
||||
export const AGENT_PRESENCE_TIMEOUT_MS = 10 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* @param {{last_seen_at?: string}} agent
|
||||
* @param {number} [now] - Referenzzeitpunkt in ms seit Epoch (injizierbar fuer Tests).
|
||||
* @returns {boolean} true, wenn der Agent als anwesend gilt. Fehlt last_seen_at (z.B.
|
||||
* Demo-Daten ohne API-Felder) oder ist es nicht parsbar, gilt der Agent defensiv als
|
||||
* anwesend, statt faelschlich einen leeren Platz zu zeigen.
|
||||
*/
|
||||
export function isAgentPresent(agent, now = Date.now()) {
|
||||
if (!agent || !agent.last_seen_at) {
|
||||
return true;
|
||||
}
|
||||
const lastSeenAt = new Date(agent.last_seen_at).getTime();
|
||||
if (Number.isNaN(lastSeenAt)) {
|
||||
return true;
|
||||
}
|
||||
return now - lastSeenAt <= AGENT_PRESENCE_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordnet eine rohe Agentenliste (GET /api/v1/agents) den Schreibtischen zu - anders als
|
||||
* buildDeskLayout()/Demo-Daten steht der Sitzplatz hier nicht in der Array-Reihenfolge,
|
||||
* sondern im Feld desk_index (API.md "Agenten"). Agenten ohne Schreibtisch (role "user"
|
||||
* oder alle Schreibtische belegt, desk_index === null) erscheinen hier nicht - sie kommen
|
||||
* gemaess F2 nur anlassbezogen durch eine Tuer und haben keinen festen Platz.
|
||||
* gemaess F2 nur anlassbezogen durch eine Tuer und haben keinen festen Platz. Agenten, die
|
||||
* laenger als AGENT_PRESENCE_TIMEOUT_MS nicht mehr gesehen wurden (last_seen_at, Issue #17),
|
||||
* erscheinen ebenfalls nicht - ihr Schreibtisch bleibt frei.
|
||||
*
|
||||
* @param {Array<object>} agents
|
||||
* @param {number} [deskCount]
|
||||
* @param {number} [now] - Referenzzeitpunkt fuer den Praesenz-Check (injizierbar fuer Tests).
|
||||
* @returns {Array<object|null>}
|
||||
*/
|
||||
export function mapAgentsToDesks(agents = [], deskCount = DEFAULT_DESK_COUNT) {
|
||||
export function mapAgentsToDesks(agents = [], deskCount = DEFAULT_DESK_COUNT, now = Date.now()) {
|
||||
const desks = new Array(deskCount).fill(null);
|
||||
for (const agent of agents) {
|
||||
if (!isAgentPresent(agent, now)) {
|
||||
continue;
|
||||
}
|
||||
const deskIndex = agent.desk_index;
|
||||
if (typeof deskIndex === "number" && deskIndex >= 0 && deskIndex < deskCount) {
|
||||
desks[deskIndex] = agent;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import {
|
|||
resolveAgentVisualState,
|
||||
mapAgentsToDesks,
|
||||
normalizeAgentForDisplay,
|
||||
isAgentPresent,
|
||||
AGENT_PRESENCE_TIMEOUT_MS,
|
||||
pickIdleActivity,
|
||||
AnimationQueue,
|
||||
DOOR_VISIT_TYPES,
|
||||
|
|
@ -281,6 +283,36 @@ test("mapAgentsToDesks ignoriert Agenten ohne Schreibtisch (desk_index null, Rol
|
|||
assert.ok(desks.every((d) => d === null));
|
||||
});
|
||||
|
||||
test("isAgentPresent gilt als anwesend, solange last_seen_at hoechstens zehn Minuten zurueckliegt (Issue #17)", () => {
|
||||
const now = Date.parse("2026-07-29T12:00:00Z");
|
||||
assert.equal(
|
||||
isAgentPresent({ last_seen_at: "2026-07-29T11:50:00Z" }, now),
|
||||
true,
|
||||
"genau zehn Minuten alt gilt noch als anwesend"
|
||||
);
|
||||
assert.equal(
|
||||
isAgentPresent({ last_seen_at: "2026-07-29T11:49:59Z" }, now),
|
||||
false,
|
||||
"aelter als zehn Minuten gilt nicht mehr als anwesend"
|
||||
);
|
||||
});
|
||||
|
||||
test("isAgentPresent faellt bei fehlendem/kaputtem last_seen_at defensiv auf anwesend zurueck", () => {
|
||||
assert.equal(isAgentPresent({ id: 1 }), true);
|
||||
assert.equal(isAgentPresent({ id: 1, last_seen_at: "nicht-parsbar" }), true);
|
||||
});
|
||||
|
||||
test("mapAgentsToDesks laesst den Schreibtisch eines Agenten frei, der laenger als zehn Minuten nicht gesehen wurde", () => {
|
||||
const now = Date.parse("2026-07-29T12:00:00Z");
|
||||
const staleSeenAt = new Date(now - AGENT_PRESENCE_TIMEOUT_MS - 1000).toISOString();
|
||||
const desks = mapAgentsToDesks(
|
||||
[{ id: 1, desk_index: 0, last_seen_at: staleSeenAt }],
|
||||
2,
|
||||
now
|
||||
);
|
||||
assert.equal(desks[0], null);
|
||||
});
|
||||
|
||||
test("normalizeAgentForDisplay leitet name/pose/location aus display_name/state ab", () => {
|
||||
const normalized = normalizeAgentForDisplay({ id: 1, display_name: "Alice", state: "working" });
|
||||
assert.equal(normalized.name, "Alice");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue