467 Stellen. Nicht durch einen anderen Strich ersetzt, sondern durch das
Satzzeichen, das an der jeweiligen Stelle richtig ist: Doppelpunkt, wo eine
Erlaeuterung folgt; Komma, wo der Satz weiterlaeuft; Punkt, wo zwei
Hauptsaetze aneinanderstiessen. Ein Halbgeviertstrich waere nur derselbe
Griff mit kuerzerem Strich gewesen.
Regelbasiert ersetzt und danach nachgeprueft, weil eine Regel allein nicht
reicht. Gefunden und einzeln korrigiert:
- Eingeschobene Saetze mit Strichpaar. Nur der oeffnende Strich passte ins
Muster, der schliessende blieb als "—," stehen. Drei Stellen, jetzt mit
Klammern oder als eigener Satz.
- Doppelte Doppelpunkte, wo beide Striche eines Paars zu Doppelpunkten wurden
("Globale Regeln: Schrift, Groessen, Abstaende: kommen aus HiveGrid").
Sieben Stellen in Doku und Oberflaeche, jetzt in Klammern.
- Pfade, denen ein Komma statt eines Doppelpunkts folgte.
- Ausgerichtete Kommentartabellen, in denen der Strich eine Spalte war.
Nicht angefasst: drei Geviertstriche, die keine Prosa sind, sondern
Tabellenplatzhalter fuer "kein Wert" (Trichter, Auftraege, Kunden). Dort ist
der Strich die Angabe selbst; ihn zu entfernen liesse leere Zellen zurueck.
tsc, lint, build, verify:carbon und verify:layout laufen sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
133 lines
4.3 KiB
TypeScript
133 lines
4.3 KiB
TypeScript
import type { ZeitraumPreset } from "../data/types";
|
|
|
|
/**
|
|
* Fester "Heute"-Bezugspunkt des Prototyps.
|
|
*
|
|
* Wichtig: Die Dummy-Daten sind deterministisch relativ zu diesem Datum erzeugt.
|
|
* Dadurch bleiben alle Zahlen ueber Reloads und Screenshots hinweg identisch.
|
|
* In der echten Software wird das durch `new Date()` ersetzt.
|
|
*/
|
|
export const HEUTE = new Date("2026-07-30T12:00:00");
|
|
|
|
export interface Zeitfenster {
|
|
von: Date;
|
|
bis: Date;
|
|
}
|
|
|
|
export function addTage(datum: Date, tage: number): Date {
|
|
const d = new Date(datum);
|
|
d.setDate(d.getDate() + tage);
|
|
return d;
|
|
}
|
|
|
|
export function addMonate(datum: Date, monate: number): Date {
|
|
const d = new Date(datum);
|
|
d.setMonth(d.getMonth() + monate);
|
|
return d;
|
|
}
|
|
|
|
export function toISO(datum: Date): string {
|
|
return datum.toISOString().slice(0, 10);
|
|
}
|
|
|
|
/** Zeitfenster eines Presets, bezogen auf HEUTE. */
|
|
export function zeitfenster(preset: ZeitraumPreset): Zeitfenster {
|
|
const bis = new Date(HEUTE);
|
|
bis.setHours(23, 59, 59, 999);
|
|
|
|
switch (preset) {
|
|
case "heute": {
|
|
const von = new Date(HEUTE);
|
|
von.setHours(0, 0, 0, 0);
|
|
return { von, bis };
|
|
}
|
|
case "7_tage":
|
|
return { von: addTage(bis, -7), bis };
|
|
case "30_tage":
|
|
return { von: addTage(bis, -30), bis };
|
|
case "quartal":
|
|
return { von: addMonate(bis, -3), bis };
|
|
case "jahr":
|
|
return { von: addMonate(bis, -12), bis };
|
|
case "alles":
|
|
default:
|
|
return { von: new Date("2000-01-01"), bis };
|
|
}
|
|
}
|
|
|
|
/** Gleich langes Fenster direkt davor: Basis fuer Trend-Vergleiche. */
|
|
export function vorperiode(preset: ZeitraumPreset): Zeitfenster {
|
|
const { von, bis } = zeitfenster(preset);
|
|
const dauer = bis.getTime() - von.getTime();
|
|
return {
|
|
von: new Date(von.getTime() - dauer),
|
|
bis: new Date(von.getTime() - 1),
|
|
};
|
|
}
|
|
|
|
export function imFenster(iso: string | null, fenster: Zeitfenster): boolean {
|
|
if (!iso) return false;
|
|
const t = new Date(iso).getTime();
|
|
return t >= fenster.von.getTime() && t <= fenster.bis.getTime();
|
|
}
|
|
|
|
/** ISO-Kalenderwoche als sortierbarer Schluessel: "2026-W31". */
|
|
export function isoWoche(datum: Date): string {
|
|
const d = new Date(Date.UTC(datum.getFullYear(), datum.getMonth(), datum.getDate()));
|
|
// Donnerstag derselben Woche bestimmt nach ISO 8601 Jahr und Wochennummer.
|
|
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
|
|
const jahresstart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
const woche = Math.ceil(((d.getTime() - jahresstart.getTime()) / 86_400_000 + 1) / 7);
|
|
return `${d.getUTCFullYear()}-W${String(woche).padStart(2, "0")}`;
|
|
}
|
|
|
|
/** "2026-W31" -> "KW 31" */
|
|
export function formatWoche(schluessel: string): string {
|
|
return `KW ${schluessel.slice(6)}`;
|
|
}
|
|
|
|
/**
|
|
* Passende Buckets fuer eine Zeitreihe.
|
|
*
|
|
* Die Aufloesung folgt der Frage, nicht dem Kalender: Bei rund zwei Vorgaengen
|
|
* pro Tag ist eine Tagesreihe ueber 30 Tage Rauschen, kein Verlauf. Deshalb
|
|
* Tage nur bis eine Woche, danach Kalenderwochen, ab einem Jahr Monate.
|
|
*/
|
|
export function bucketsFuer(preset: ZeitraumPreset): {
|
|
art: "tag" | "woche" | "monat";
|
|
schluessel: string[];
|
|
} {
|
|
const { von, bis } = zeitfenster(preset);
|
|
|
|
if (preset === "heute" || preset === "7_tage") {
|
|
const schluessel: string[] = [];
|
|
for (let d = new Date(von); d <= bis; d = addTage(d, 1)) schluessel.push(toISO(d));
|
|
return { art: "tag", schluessel };
|
|
}
|
|
|
|
if (preset === "30_tage" || preset === "quartal") {
|
|
const schluessel: string[] = [];
|
|
for (let d = new Date(von); d <= bis; d = addTage(d, 7)) {
|
|
const key = isoWoche(d);
|
|
if (!schluessel.includes(key)) schluessel.push(key);
|
|
}
|
|
const letzte = isoWoche(bis);
|
|
if (!schluessel.includes(letzte)) schluessel.push(letzte);
|
|
return { art: "woche", schluessel };
|
|
}
|
|
|
|
const monate: string[] = [];
|
|
const start = preset === "alles" ? addMonate(bis, -23) : von;
|
|
for (let d = new Date(start.getFullYear(), start.getMonth(), 1); d <= bis; d = addMonate(d, 1)) {
|
|
monate.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`);
|
|
}
|
|
return { art: "monat", schluessel: monate };
|
|
}
|
|
|
|
/** Bucket-Schluessel eines Datums fuer die gewaehlte Aufloesung. */
|
|
export function bucketKey(iso: string, art: "tag" | "woche" | "monat"): string {
|
|
if (art === "monat") return iso.slice(0, 7);
|
|
if (art === "woche") return isoWoche(new Date(iso));
|
|
return iso.slice(0, 10);
|
|
}
|