Ein Zeitraum geht jetzt durch -- als Zeitraum, nicht als Tag

Filipe: "verbesser das, also ich will dass das auch so geht, mach dass
es funktioniert, ich will es so einfach und perfekt wie moeglich. also
sieh zu dass die excel auch so durch geht."

Backstage gibt eine Ausgabe fuer einen ZEITRAUM heraus --
"2026-09-01 ~ 2026-09-13", eine Zeile je Creator, alle Zahlen
aufsummiert. Am Vormittag wurde so eine Zeile abgewiesen. Jetzt geht
sie durch.

DREI WEGE WAEREN MOEGLICH GEWESEN, ZWEI DAVON WAEREN
ZAHLENFAELSCHUNG:
  * auf den ersten Tag schreiben -> dreizehn Tage auf einem Tag; die
    Zahl steht da, sie ist gross, sie sieht richtig aus, und niemand
    kann spaeter sagen, was darin steckt;
  * gleichmaessig verteilen -> erfundene Tage, die es nie gab.
Der dritte ist dieser: den Zeitraum ALS Zeitraum ablegen. Was drinsteht,
ist dann wahr -- und was er nicht sagt (welcher Tag wie lief),
behauptet er auch nicht.

EIGENE TABELLE UND NICHT EIN FELD IN `leistung`: Dort ist der
Schluessel (creator, tag). Ein Zeitraum ab dem 1. September wuerde mit
dem ECHTEN 1. September zusammenstossen, und eine der beiden Zahlen
waere weg. Getrennt kann keines das andere ueberschreiben -- und die
Wochenzahlen bleiben, was sie sind: aus Tagen gerechnet.

Der Schluessel ist (creator, von, bis): Dieselbe Datei zweimal
einzulesen ersetzt denselben Zeitraum, statt ihn zu verdoppeln.

AUF DER SEITE steht ein eigener Block zwischen Woche und Tageszeilen:
die Spanne vorn, die Zahl der Tage als Marke daneben, die Werte
darunter. Dazu EINE Umrechnung, und nur diese: "Ø 4.129 Diamanten pro
Tag" -- als Durchschnitt bezeichnet, nirgends gespeichert. Wer eine
Woche vergleichen will, braucht sie; wer sie fuer einen echten Tag
haelt, hat das Wort nicht gelesen. Darueber steht woertlich, dass diese
Zahlen nicht in die Wochenzahlen eingehen.

MIT DER ECHTEN DATEI GEMESSEN: alle 8 Zeilen gehen durch, 8 als
Zeitraum, 0 abgelehnt. Spanne 01.09.-11.09. = 11 Tage, Diamanten
53.679, Dauer 5171 Minuten.

pruef-backstage-import 86 -> 106. Die Pruefung, auf die es ankommt, ist
nicht "es wird gespeichert", sondern "es wird NICHT in die Woche
gemischt": kein Tag kommt hinzu, die Wochenzahl bleibt Ziffer fuer
Ziffer dieselbe. Dazu die Gegenprobe, dass ein Zeitraum von EINEM Tag
weiterhin ein Tag bleibt -- sonst hiesse alles andere nur, dass jetzt
alles als Zeitraum abgelegt wird.

DREI PRUEFUNGEN GEDREHT, KEINE GELOESCHT: Zwei behaupteten noch die
Ablehnung vom Vormittag. Und tagAusZelle() hat jetzt DREI Ausgaenge
statt zwei (Tag, Zeitraum, Grund) -- geprueft wird, dass nie zwei davon
gleichzeitig kommen. Gaebe es zwei, entschiede jeder Aufrufer selbst,
was Vorrang hat, und der zweite entschiede anders als der erste. Genau
daran ist heute frueh der Zeitraum-Schutz gescheitert.

ZWEI EIGENE MESSFEHLER, beide von der Pruefung gefunden: Ich verlangte
"kein einziger Tag in der Spanne" und uebersah, dass weiter oben schon
ein Tag geschrieben worden war, der hineinfaellt -- gemessen wird jetzt
die VERAENDERUNG. Und ich mass den Zeitraum-Block, waehrend ein anderer
Creator geoeffnet war: keine Frage an die Seite, sondern an die falsche
Person.

Nebenbei zum zweiten Mal heute: eine Versalzeile mit 10,88 px. Auf
.72rem angehoben, bevor pruef-css-klassen sie findet.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-14 16:23:16 +02:00
co-authored by Claude Opus 5
parent 0207b05da6
commit e82bf45f0d
35 changed files with 829 additions and 367 deletions
+168 -5
View File
@@ -337,6 +337,121 @@ for (const [rolle, code, wer] of [
`der Scout sieht seinen Creator in der Liste (${namen || "leer"})`);
}
{
/* ---- EIN ZEITRAUM GEHT JETZT DURCH (14.09.2026) ------------------
Filipe: "ich will dass das auch so geht, mach dass es
funktioniert ... sieh zu dass die excel auch so durch geht."
Am Vormittag wurde eine Zeile mit "2026-09-01 ~ 2026-09-13"
abgewiesen -- richtig, denn auf einen Tag geschrieben waeren es
dreizehn Tage auf einmal gewesen. Jetzt geht sie durch, aber als
ZEITRAUM: eigene Tabelle, eigener Block auf der Seite, und die
Wochenzahlen bleiben unberuehrt.
DAS IST DIE PRUEFUNG, AUF DIE ES ANKOMMT: nicht "es wird
gespeichert", sondern "es wird NICHT in die Woche gemischt". Eine
Summe aus dreizehn Tagen in einer Wochenzahl waere genau der
stille Fehler, den das Ganze vermeiden soll. */
const spanneText = [
"Datenzeitraum\tCreator\tDiamanten\tLIVE-Dauer\tGültige LIVE-Gehen-Tage",
"2026-09-01 ~ 2026-09-13\t@Nova_Live\t53.679\t86Std. 10Min. 52Sek.\t11",
].join("\n");
{
const v = await ruf("/workspace/api/leistung/netzwerk-import/vorschau",
keksDogi, { text: spanneText });
ok(v.status === 200, `die Vorschau nimmt den Zeitraum an (HTTP ${v.status})`);
ok(v.daten?.zeitraeume === 1,
`sie zaehlt ihn als Zeitraum, nicht als Tag (${v.daten?.zeitraeume})`);
ok(v.daten?.spanne?.tage === 13,
`und nennt die Zahl der Tage (${v.daten?.spanne?.tage})`);
ok((v.daten?.probleme || []).length === 0,
`keine Absage mehr (${(v.daten?.probleme || []).length} Probleme)`);
}
/* Die Woche VOR dem Import -- als Vergleichspunkt. Ohne ihn hiesse
"die Woche stimmt" nur, dass irgendetwas dasteht. */
const wocheVor = await (await fetch(BASIS + `/workspace/api/leistung/${idNova}`,
{ headers: { Cookie: keksDogi } })).json();
const diaVor = wocheVor?.woche?.diamanten?.wert ?? null;
/* WIE VIELE TAGESZEILEN ES SCHON GIBT. Der erste Entwurf verlangte
hier NULL -- und uebersah, dass die Backstage-Tabelle weiter oben
bereits einen Tag geschrieben hat, der in dieselbe Spanne faellt.
Die Pruefung war rot, der Code richtig. Gemessen wird deshalb die
VERAENDERUNG, nicht der Absolutwert. */
const tageVor = (() => {
const d0 = new DatabaseSync(DB, { readOnly: true });
const n = d0.prepare(
"SELECT COUNT(*) AS n FROM leistung WHERE creator_id = ? AND tag >= '2026-09-01' AND tag <= '2026-09-13'")
.get(idNova).n;
d0.close();
return n;
})();
{
const s = await ruf("/workspace/api/leistung/netzwerk-import",
keksDogi, { text: spanneText });
ok(s.status === 200, `der Zeitraum wird uebernommen (HTTP ${s.status})`);
ok(s.daten?.zeitraeume === 1 && s.daten?.geschrieben === 0,
`als Zeitraum geschrieben, nicht als Tag (${s.daten?.zeitraeume} / ${s.daten?.geschrieben})`);
}
{
const d2 = new DatabaseSync(DB, { readOnly: true });
const z = d2.prepare(
"SELECT * FROM leistung_zeitraum WHERE creator_id = ?").get(idNova);
const tage = d2.prepare(
"SELECT COUNT(*) AS n FROM leistung WHERE creator_id = ? AND tag >= '2026-09-01' AND tag <= '2026-09-13'")
.get(idNova).n;
d2.close();
ok(z && z.von === "2026-09-01" && z.bis === "2026-09-13" && z.tage === 13,
`er steht in der Datenbank (${z?.von} bis ${z?.bis}, ${z?.tage} Tage)`);
ok(z?.diamanten === 53679, `mit den Diamanten (${z?.diamanten})`);
ok(z?.dauer_min === 5171, `und der Dauer in Minuten (${z?.dauer_min})`);
/* DER WICHTIGSTE HAKEN: In der TAGES-Tabelle steht davon nichts. */
ok(tage === tageVor,
`und KEIN einziger Tag kam hinzu (${tageVor} -> ${tage} Tageszeilen)`);
}
{
/* Und die Wochenzahl ist unveraendert -- der Zeitraum mischt sich
nicht hinein. */
const nach = await (await fetch(BASIS + `/workspace/api/leistung/${idNova}`,
{ headers: { Cookie: keksDogi } })).json();
ok((nach?.woche?.diamanten?.wert ?? null) === diaVor,
`die Wochenzahl bleibt unveraendert (${diaVor} -> ${nach?.woche?.diamanten?.wert})`);
ok(Array.isArray(nach?.zeitraeume) && nach.zeitraeume.length === 1,
`aber der Zeitraum wird mitgeliefert (${(nach?.zeitraeume || []).length})`);
}
{
/* DIESELBE DATEI ZWEIMAL ersetzt denselben Zeitraum, statt ihn zu
verdoppeln -- sonst stuenden nach dem zweiten Einlesen zwei
gleiche Karten da. */
await ruf("/workspace/api/leistung/netzwerk-import", keksDogi, { text: spanneText });
const d3 = new DatabaseSync(DB, { readOnly: true });
const n = d3.prepare(
"SELECT COUNT(*) AS n FROM leistung_zeitraum WHERE creator_id = ?").get(idNova).n;
d3.close();
ok(n === 1, `zweimal einlesen ergibt EINEN Zeitraum (${n})`);
}
{
/* GEGENPROBE: Ein Zeitraum von EINEM Tag ist kein Zeitraum -- er
geht weiterhin in die Tagestabelle. Ohne sie hiesse alles oben
nur, dass jetzt alles als Zeitraum abgelegt wird. */
const einTag = [
"Datenzeitraum\tCreator\tDiamanten",
`${gestern} ~ ${gestern}\t@Nova_Live\t1.234`,
].join("\n");
const s = await ruf("/workspace/api/leistung/netzwerk-import", keksDogi, { text: einTag });
ok(s.daten?.geschrieben === 1 && (s.daten?.zeitraeume ?? 0) === 0,
`ein Ein-Tages-Zeitraum bleibt ein Tag (${s.daten?.geschrieben} Tag, `
+ `${s.daten?.zeitraeume} Zeitraeume)`);
}
}
/* =======================================================================
7. Und die Seite selbst
======================================================================= */
@@ -504,16 +619,64 @@ console.log("\n=== Die Seite ===");
await seite.waitForTimeout(1800);
const spanne = await seite.evaluate(() =>
document.getElementById("netz-vorschau")?.textContent || "");
ok(spanne.includes("mehrere Tage") && spanne.includes("2026-09-01"),
`ein Zeitraum ueber mehrere Tage wird abgelehnt und BEGRUENDET ("${spanne.slice(0, 90)}…")`);
ok(!/1 von 1 Zeilen zugeordnet/.test(spanne),
"und die Zeile wird NICHT als zugeordnet gezaehlt");
/* GEDREHT AM 14.09.2026 NACHMITTAG, NICHT GELOESCHT.
Hier stand: "ein Zeitraum ueber mehrere Tage wird abgelehnt und
BEGRUENDET" -- richtig, bis Filipe sagte: "ich will dass das auch
so geht." Jetzt geht die Zeile durch, und die Vorschau sagt, dass
es ein ZEITRAUM ist. Eine geloeschte Pruefung senkt die Zahl und
beweist nichts; eine gedrehte haelt fest, dass die Aenderung eine
Entscheidung war. */
ok(/1 von 1 Zeilen zugeordnet/.test(spanne),
`ein Zeitraum ueber mehrere Tage geht jetzt durch ("${spanne.slice(0, 60)}…")`);
ok(!/mehrere Tage auf einmal/.test(spanne),
"und wird nicht mehr abgewiesen");
if (process.env.SCHIRM) {
await seite.locator("#netz-dialog").screenshot(
{ path: join(HIER, "pruef-netz-zeitraum.png") });
}
await seite.click("#netz-zu");
/* ---- UND WIE ES DANACH AUSSIEHT (14.09.2026) --------------------
Der Block mit den Zeitraeumen ist die eigentliche Antwort auf
"sieh zu dass die excel auch so durch geht": Die Zahlen sind da,
und man sieht auf einen Blick, worauf sie sich beziehen. */
await seite.click("#netz-los");
await seite.waitForTimeout(1800);
/* AUF DEN RICHTIGEN CREATOR SCHALTEN. Der erste Entwurf mass sofort
und fand nichts -- geoeffnet war Kiro, der Zeitraum gehoert Nova.
Das war kein Befund, sondern eine Frage an die falsche Person. */
await seite.selectOption("#creator", String(idNova)).catch(() => {});
await seite.waitForTimeout(1600);
const werOffen = await seite.evaluate(() =>
document.getElementById("creator")?.selectedOptions?.[0]?.textContent || "");
ok(/Nova/.test(werOffen), `Nova ist geoeffnet ("${werOffen.slice(0, 30)}")`);
const spannenBild = await seite.evaluate(() => ({
sichtbar: document.getElementById("spannen-block")?.hidden === false,
karten: document.querySelectorAll(".spanne").length,
zeit: document.querySelector(".spanne__zeit")?.textContent || "",
tage: document.querySelector(".spanne__tage")?.textContent || "",
schnitt: document.querySelector(".spanne__schnitt")?.textContent || "",
satz: document.querySelector(".spannen__satz")?.textContent || "",
}));
ok(spannenBild.sichtbar && spannenBild.karten >= 1,
`der Zeitraum-Block steht auf der Seite (${spannenBild.karten} Karten)`);
ok(spannenBild.zeit.includes("01.09.") && spannenBild.zeit.includes("13.09."),
`mit der Spanne davor ("${spannenBild.zeit}")`);
ok(/Tage/.test(spannenBild.tage),
`und der Zahl der Tage ("${spannenBild.tage}")`);
ok(/Ø/.test(spannenBild.schnitt) && /pro Tag/.test(spannenBild.schnitt),
`der Schnitt steht als Durchschnitt da, nicht als Tageswert ("${spannenBild.schnitt}")`);
ok(/nicht für einen Tag/.test(spannenBild.satz),
"und darueber steht, dass es kein Tageswert ist");
if (process.env.SCHIRM) {
await seite.locator("#spannen-block").screenshot(
{ path: join(HIER, "pruef-spannen.png") });
}
await seite.click("#netz-zu").catch(() => {});
await seite.waitForTimeout(300);
/* Gemessen wird SATZ FUER SATZ, nicht der Block. Ein Block, der
als Ganzes erscheint, erklaert einem Scout auch den Knopf, den er
+18 -7
View File
@@ -320,7 +320,12 @@ console.log("=== Die eine Regel fuer den Tag ===");
dass sie beides kann: ablehnen UND durchlassen. */
const { tagAusZelle } = await import("./workspace-leistung.js");
const faelle = [
["2026-09-01 ~ 2026-09-11", null, "mehrere Tage"],
/* GEDREHT AM NACHMITTAG DES 14.09.2026, nicht geloescht.
Vormittags wurde ein Zeitraum ueber mehrere Tage ABGELEHNT --
richtig, denn auf einen Tag geschrieben waeren es elf Tage auf
einmal gewesen. Filipe: "ich will dass das auch so geht." Jetzt
kommt er als ZEITRAUM zurueck und wird als solcher abgelegt. */
["2026-09-01 ~ 2026-09-11", "ZEITRAUM", null],
["2026-09-06 ~ 2026-09-06", "2026-09-06", null],
["2026-09-06", "2026-09-06", null],
["06.09.2026", "2026-09-06", null],
@@ -329,19 +334,25 @@ console.log("=== Die eine Regel fuer den Tag ===");
];
for (const [text, sollTag, sollFehler] of faelle) {
const r = tagAusZelle(text);
if (sollTag) {
ok(r.tag === sollTag && !r.fehler,
if (sollTag === "ZEITRAUM") {
ok(!!r.zeitraum && !r.tag && !r.fehler,
`"${text}" kommt als Zeitraum zurueck `
+ `(${r.zeitraum ? r.zeitraum.von + " bis " + r.zeitraum.bis : r.tag || r.fehler})`);
} else if (sollTag) {
ok(r.tag === sollTag && !r.fehler && !r.zeitraum,
`"${text}" wird zum Tag ${sollTag} (${r.tag || r.fehler})`);
} else {
ok(!r.tag && typeof r.fehler === "string" && r.fehler.includes(sollFehler),
`"${text}" wird abgelehnt und begruendet ("${(r.fehler || "").slice(0, 42)}…")`);
}
}
/* Und die Regel gibt NIE beides und nie keines zurueck -- sonst
entscheidet jeder Aufrufer selbst, was Vorrang hat. */
/* DREI AUSGAENGE, NIE ZWEI DAVON GLEICHZEITIG: Tag, Zeitraum oder
Grund. Gaebe es zwei auf einmal, entschiede jeder Aufrufer selbst,
was Vorrang hat -- und der zweite entschiede anders als der erste.
Genau daran ist heute frueh der Zeitraum-Schutz gescheitert. */
const alle = faelle.map(([t]) => tagAusZelle(t));
ok(alle.every((r) => (!!r.tag) !== (!!r.fehler)),
"sie liefert immer genau eines von beidem: Tag oder Grund");
ok(alle.every((r) => [r.tag, r.zeitraum, r.fehler].filter(Boolean).length === 1),
"sie liefert immer genau EINES von dreien: Tag, Zeitraum oder Grund");
}
console.log("");
+95 -10
View File
@@ -237,7 +237,23 @@ leistungRouter.get("/workspace/api/leistung/:creatorId", (req, res) => {
anteil: Math.round((dieseWoche.verweildauer_s / ziele.verweildauer_s) * 100) } : null,
} : null;
/* DIE ZEITRAEUME AUS BACKSTAGE (14.09.2026).
Sie gehen NICHT in die Wochenzahlen ein -- die bleiben aus Tagen
gerechnet. Ein Zeitraum ueber 13 Tage in eine Woche zu mischen
waere eine Summe aus zwei verschiedenen Dingen, und niemand
koennte hinterher sagen, was darin steckt.
Sie stehen deshalb daneben, mit ihrer Spanne davor. Die juengsten
zuerst; mehr als sechs braucht niemand auf einen Blick. */
const zeitraeume = db().prepare(`
SELECT von, bis, tage, diamanten, dauer_min, gueltige_tage,
zuschauer_avg, zuschauer_max, follower_neu, erfasst
FROM leistung_zeitraum WHERE creator_id = ?
ORDER BY bis DESC, von DESC LIMIT 6`).all(creatorId);
res.json({
zeitraeume,
creator_id: creatorId,
woche: mitVergleich,
roh: { woche: dieseWoche, vorwoche },
@@ -684,14 +700,33 @@ export function zeitraumLesen(roh) {
export function tagAusZelle(roh) {
const zeitraum = zeitraumLesen(roh);
if (zeitraum && zeitraum.von !== zeitraum.bis) {
return { fehler: `Zeitraum ${zeitraum.von} bis ${zeitraum.bis} – das sind mehrere `
+ "Tage auf einmal. Stell in Backstage den Zeitraum auf EINEN Tag." };
/* MEHRERE TAGE SIND KEIN FEHLER MEHR (14.09.2026, zweite Fassung).
Am Vormittag wurde so eine Zeile abgewiesen -- richtig, denn auf
einen Tag geschrieben waeren es 13 Tage auf einmal gewesen.
Filipe: "ich will dass das auch so geht ... sieh zu dass die
excel auch so durch geht."
Also geht sie durch -- aber als das, was sie ist. Der Aufrufer
bekommt `zeitraum` statt `tag` und legt sie in
leistung_zeitraum ab. Wer nur `tag` liest, bekommt weiterhin
nichts und schreibt damit auch nichts Falsches. */
return { zeitraum };
}
const tag = zeitraum ? zeitraum.von : datumLesen(roh);
if (!tag) return { fehler: `Datum unlesbar: "${String(roh ?? "").slice(0, 40)}"` };
return { tag };
}
/** Wie viele Tage liegen in einem Zeitraum? Beide Enden zaehlen mit --
* vom 1. bis zum 1. ist EIN Tag, nicht null. */
export function tageImZeitraum(von, bis) {
const a = Date.parse(`${von}T00:00:00Z`);
const b = Date.parse(`${bis}T00:00:00Z`);
if (!Number.isFinite(a) || !Number.isFinite(b) || b < a) return 0;
return Math.round((b - a) / 86400000) + 1;
}
/** Macht aus einem Textwert ein Datum "JJJJ-MM-TT".
* Erkennt 2026-09-06, 06.09.2026 und 09/06/2026. */
export function datumLesen(roh) {
@@ -891,13 +926,19 @@ function netzwerkLesen(text, person, tagVorgabe) {
daraufhin durch den Einzel-Import, und dort fehlte sie. Jetzt
benutzen alle drei Stellen dieselbe. */
let tag = tagVorgabe;
let spanne = null;
if (ausTabelle) {
const gelesen = tagAusZelle(z[zu.tag]);
if (gelesen.fehler) { schlecht.push({ zeile: i + 1, grund: gelesen.fehler }); continue; }
tag = gelesen.tag;
if (gelesen.zeitraum) spanne = gelesen.zeitraum;
else tag = gelesen.tag;
}
if (!tag && !spanne) { schlecht.push({ zeile: i + 1, grund: "Kein Tag angegeben." }); continue; }
const letzterTag = spanne ? spanne.bis : tag;
if (letzterTag > heuteLokal()) {
schlecht.push({ zeile: i + 1, grund: `${letzterTag} liegt in der Zukunft` });
continue;
}
if (!tag) { schlecht.push({ zeile: i + 1, grund: "Kein Tag angegeben." }); continue; }
if (tag > heuteLokal()) { schlecht.push({ zeile: i + 1, grund: `${tag} liegt in der Zukunft` }); continue; }
/* Vier Versuche, in dieser Reihenfolge: Handle gegen Handle, Handle
gegen Name, Name gegen Handle, Name gegen Name. Das Eindeutige
@@ -910,7 +951,10 @@ function netzwerkLesen(text, person, tagVorgabe) {
|| (sName && nach.name.get(sName));
if (!p) { unbekannt.push(rohWer.slice(0, 60)); continue; }
const werte = { creator_id: p.id, name: p.name, tag };
const werte = spanne
? { creator_id: p.id, name: p.name, von: spanne.von, bis: spanne.bis,
tage: tageImZeitraum(spanne.von, spanne.bis) }
: { creator_id: p.id, name: p.name, tag };
let hatZahl = false;
for (const feld of messwerte) {
/* Die Dauer hat eine EINHEIT, die anderen Felder nicht.
@@ -948,12 +992,31 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import/vorschau", gleicheH
hinterher raten, ob er etwas überschrieben hat. */
const vorhanden = db().prepare(
"SELECT 1 FROM leistung WHERE creator_id = ? AND tag = ?");
let neu = 0, ersetzt = 0;
for (const t of e.treffer) (vorhanden.get(t.creator_id, t.tag) ? ersetzt++ : neu++);
const vorhandenSpanne = db().prepare(
"SELECT 1 FROM leistung_zeitraum WHERE creator_id = ? AND von = ? AND bis = ?");
let neu = 0, ersetzt = 0, zeitraeume = 0;
for (const t of e.treffer) {
if (t.von) {
zeitraeume++;
if (vorhandenSpanne.get(t.creator_id, t.von, t.bis)) ersetzt++; else neu++;
} else {
(vorhanden.get(t.creator_id, t.tag) ? ersetzt++ : neu++);
}
}
res.json({
spalten: e.spalten, person_spalten: e.person_spalten, aus_tabelle: e.aus_tabelle,
kopf: e.kopf,
/* WIE VIELE DAVON SIND ZEITRAEUME? Steht eigens da, weil es
etwas anderes ist als ein Tag -- wer eine Backstage-Ausgabe
ueber zwei Wochen einliest, soll das VOR dem Bestaetigen
sehen und nicht hinterher raten, warum die Woche leer ist. */
zeitraeume,
spanne: e.treffer.find((t) => t.von)
? { von: e.treffer.find((t) => t.von).von,
bis: e.treffer.find((t) => t.von).bis,
tage: e.treffer.find((t) => t.von).tage }
: null,
zugeordnet: e.treffer.length, neu, ersetzt,
unbekannt: e.unbekannt, probleme: e.schlecht.slice(0, 12),
probleme_gesamt: e.schlecht.length,
@@ -988,10 +1051,30 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft,
(SELECT notiz FROM leistung WHERE creator_id = ? AND tag = ?),
?,?, 'import')`);
/* DER ZWEITE WEG: ein Zeitraum geht in seine eigene Tabelle.
INSERT OR REPLACE, damit dieselbe Datei zweimal denselben
Zeitraum ersetzt statt ihn zu verdoppeln. */
const spanneSchreiben = d.prepare(`
INSERT OR REPLACE INTO leistung_zeitraum
(creator_id, von, bis, tage, diamanten, dauer_min, gueltige_tage,
zuschauer_avg, zuschauer_max, verweildauer_s, schenker, follower_neu,
erfasst, erfasst_von)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
let geschrieben = 0;
let spannen = 0;
d.exec("BEGIN");
try {
for (const t of e.treffer) {
if (t.von) {
spanneSchreiben.run(t.creator_id, t.von, t.bis, t.tage,
t.diamanten ?? null, t.dauer_min ?? null, t.gueltige_tage ?? null,
t.zuschauer_avg ?? null, t.zuschauer_max ?? null,
t.verweildauer_s ?? null, t.schenker ?? null, t.follower_neu ?? null,
new Date().toISOString(), req.person.id);
spannen++;
continue;
}
schreiben.run(t.creator_id, t.tag,
t.diamanten ?? null, t.dauer_min ?? null,
/* Ein gültiger Tag im Sinne von TikTok ist einer mit
@@ -1009,9 +1092,11 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft,
protokolliere("leistung_netzwerk_import", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `${geschrieben} Zeilen, ${e.unbekannt.length} unbekannt`.slice(0, 120),
detail: `${geschrieben} Tage, ${spannen} Zeitraeume, `
+ `${e.unbekannt.length} unbekannt`.slice(0, 120),
});
res.json({ geschrieben, unbekannt: e.unbekannt, probleme: e.schlecht.length });
res.json({ geschrieben, zeitraeume: spannen,
unbekannt: e.unbekannt, probleme: e.schlecht.length });
} catch (fehler) {
console.error("[leistung] Netzwerk-Import:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
+49
View File
@@ -3250,6 +3250,55 @@ export function db() {
CHECK (quelle IN ('hand','import')),
PRIMARY KEY (creator_id, tag)
);
/* ZAHLEN FUER EINEN ZEITRAUM (14.09.2026).
Filipe: "ich will dass das auch so geht, mach dass es
funktioniert ... sieh zu dass die excel auch so durch geht."
Backstage gibt eine Ausgabe fuer einen ZEITRAUM heraus --
"2026-09-01 ~ 2026-09-13", eine Zeile je Creator, alle Zahlen
aufsummiert. Bis heute wurde so eine Zeile abgewiesen, weil
daraus kein Tageswert wird.
DREI WEGE WAEREN MOEGLICH GEWESEN, und zwei davon waeren
Zahlenfaelschung:
* auf den ersten Tag schreiben -> 13 Tage auf einem Tag,
sieht richtig aus, ist es nicht;
* gleichmaessig verteilen -> erfundene Tage, die es nie gab.
Der dritte ist dieser: den Zeitraum ALS Zeitraum ablegen. Was
drinsteht, ist dann wahr -- und was es nicht sagt (welcher Tag
wie lief), behauptet es auch nicht.
EIGENE TABELLE UND NICHT EIN FELD IN "leistung": Dort ist der
Schluessel (creator, tag). Ein Zeitraum ab dem 1. September
wuerde mit dem ECHTEN 1. September zusammenstossen -- und eine
der beiden Zahlen waere weg. Getrennt kann keines das andere
ueberschreiben, und die Wochenzahlen bleiben, was sie sind:
aus Tagen gerechnet.
DER SCHLUESSEL IST (creator, von, bis): Dieselbe Datei zweimal
einzulesen ersetzt denselben Zeitraum, statt ihn zu
verdoppeln. */
CREATE TABLE IF NOT EXISTS leistung_zeitraum (
creator_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
von TEXT NOT NULL,
bis TEXT NOT NULL,
tage INTEGER NOT NULL,
diamanten INTEGER,
dauer_min INTEGER,
gueltige_tage INTEGER,
zuschauer_avg INTEGER,
zuschauer_max INTEGER,
verweildauer_s INTEGER,
schenker INTEGER,
follower_neu INTEGER,
erfasst TEXT NOT NULL,
erfasst_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
PRIMARY KEY (creator_id, von, bis)
);
CREATE INDEX IF NOT EXISTS idx_leistung_zeitraum_creator
ON leistung_zeitraum (creator_id, bis DESC);
CREATE INDEX IF NOT EXISTS idx_leistung_tag ON leistung (tag DESC);
/* ZIELE je Creator. Aus dem 90-Tage-Plan, den es im Profil schon