Entscheidungsinstanz an der Spitze: Mensch, Agent oder Mensch zuerst mit Frist (#690)

- cases.top in instance.json: Human | Agent | HumanFirst, agentId, humanFirstHours
- TopRole: der entscheidende Agent steht in keiner Einheit, liest keine Fremdinhalte
  und hat nur Taskboard, Escalation, Memory, FileRW, AgentComm; geprueft beim Setzen,
  bei jeder Zustellung und vor jeder Entscheidung
- CaseDesk: was bisher an der Spitze beim Menschen landete, geht nach Einstellung an
  den Agenten; Tagesdeckel und Frist gehen weiter an den Menschen. handle verwehrt,
  forward geht an den Menschen. Mensch zuerst: Uebergabe nach Ablauf, eigene Frist.
  Der Mensch kann einen Fall des Agenten selbst entscheiden.
- Beschluesse des Agenten: ByTopAgent, zum Lesen vorgelegt; jeder Beschluss laesst
  sich mit Begruendung zuruecknehmen und bindet dann niemanden mehr
- Briefing fuer den Agenten an der Spitze, auch ohne Einheiten
- OrgAdministration.SetTop mit Audit; Werkzeugsperre und Verschiebesperre
- WebUI Vorgaenge: Reiter Spitze, Lesen/Zuruecknehmen, Zaehler; i18n en/de
- Tests: TopRoleTests (15), OrgAdministrationTests (+2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-29 09:27:26 +02:00
co-authored by Claude Opus 5.5
parent e22edb418a
commit e8964e3c74
16 changed files with 1020 additions and 26 deletions
@@ -19,5 +19,46 @@ public sealed class CaseSettings
[JsonPropertyName("escalationsPerUnitPerDay")]
public int EscalationsPerUnitPerDay { get; set; } = 10;
/// <summary>#690: Who decides at the top of the tree.</summary>
[JsonPropertyName("top")]
public TopSettings Top { get; set; } = new();
public TimeSpan Deadline => TimeSpan.FromHours(Math.Clamp(DeadlineHours, 0.25, 24 * 14));
}
/// <summary>#690: How the top of the tree is filled — a role, not a person.</summary>
[JsonConverter(typeof(JsonStringEnumConverter<TopMode>))]
public enum TopMode
{
/// <summary>A human. Cases wait in the web interface.</summary>
Human,
/// <summary>An agent — a strong model that runs rarely, decides and gives reasons.</summary>
Agent,
/// <summary>
/// The human first; without an answer in <see cref="TopSettings.HumanFirstHours"/> the agent
/// decides and presents the decision. For an organisation that goes on at night.
/// </summary>
HumanFirst
}
public sealed class TopSettings
{
[JsonPropertyName("mode")]
public TopMode Mode { get; set; } = TopMode.Human;
/// <summary>The deciding agent, for <see cref="TopMode.Agent"/> and <see cref="TopMode.HumanFirst"/>.</summary>
[JsonPropertyName("agentId")]
public string? AgentId { get; set; }
/// <summary>How long the human has before the agent decides, in <see cref="TopMode.HumanFirst"/>.</summary>
[JsonPropertyName("humanFirstHours")]
public double HumanFirstHours { get; set; } = 2;
public TimeSpan HumanFirst => TimeSpan.FromHours(Math.Clamp(HumanFirstHours, 0.25, 24 * 7));
/// <summary>Whether an agent takes part at the top at all.</summary>
[JsonIgnore]
public bool UsesAgent => Mode != TopMode.Human && !string.IsNullOrWhiteSpace(AgentId);
}