Alles aus der Excel-Datei -- und der Fund, der alles blockiert haette
Filipe: "ich will dass alles von der excel datei genommen wird.
perfektionnier das, aber wenn ich dir runter lade soll alles notiert
und angezeigt werden." Dazu eine echte Ausgabe als Vorlage.
DER SCHWERSTE FUND STECKTE VOR DEN DATEN, NICHT IN IHNEN.
Seine Ausgabe "Creator_innendaten" hat DREI Spalten, die nach Person
aussehen: "Creator*in-ID", "Creator*innen-Anmeldename" und "Agent".
`personSpaltenRaten` nahm die erste mit dem Wort "creator" darin --
die ID. Danach wurde nach einem Creator namens "700001" gesucht.
Nachgemessen an seiner echten Kopfzeile: handle = KEINE, name =
"Creator*in-ID". Diese Datei haette KEINE EINZIGE Zeile zugeordnet,
mit einem Hinweis ("steht bei keinem Creator im Feld TikTok"), der in
die voellig falsche Richtung zeigt.
Jetzt ist "Anmeldename" der Handle, eine Kennnummer ist fuer beide
Spalten ausgeschlossen, und "creator" allein reicht nicht mehr als
Namensspalte -- sonst haette weiter hinten "Neue*r LIVE-Creator*innen"
(Wert: "Nein") die Stelle uebernommen. An fuenf Kopfzeilen gemessen.
UND DANN: NICHTS FAELLT MEHR WEG.
41 Spalten in der Datei, acht werden gedeutet. Die restlichen 33 --
letzter Monat, fuenf Prozentwerte, Matches, Multi-Gast-LIVEs, Fanclub,
Graduierungs- und Stufenstatus -- wurden lautlos weggeworfen.
KEINE 33 NEUEN SPALTEN, sondern eine Zeile je Spalte mit dem NAMEN als
Schluessel. Eine abgeschriebene Spaltenliste hat in diesem Haus schon
zweimal Daten gekostet und waere beim naechsten Backstage-Update
falsch. Gegenprobe in der Pruefung: eine erfundene Spalte
("Sternenstaub pro Woche") kommt genauso durch -- es wird also keine
Liste gepflegt, die Datei entscheidet.
An SEINER echten Datei gemessen, ohne sie irgendwo hineinzuschreiben:
40 von 41 Spalten gespeichert (die 41. ist leer), Zeitraum 01.09.-
13.09. erkannt, 32 als Zahl, 8 als Text.
UND EIN MESSFEHLER, DER LEHRREICH IST: Meine erste Pruefung meldete
"zugeklappt ist die Liste 141 px hoch", im Bildschirmfoto war dort
nichts. An einem Miniaturfall nachgemessen: getBoundingClientRect,
offsetHeight, offsetParent und getClientRects liefern bei einem
<details> in BEIDEN Zustaenden identische Werte -- Chromium verbirgt
den Inhalt mit content-visibility:hidden, und das behaelt die letzte
Ausmessung. Nur checkVisibility() kann es unterscheiden. Die Messung
log, nicht die Seite.
pruef-backstage-import 160 (war 133), pruef-xlsx 75,
pruef-leistung-optik 59, pruef-leistung, pruef-css-klassen und
pruef-auskunft (46, DSGVO -- die neue Tabelle ist automatisch dabei,
weil die Auskunft ihre Liste aus PRAGMA foreign_key_list ableitet):
alle gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -252,6 +252,31 @@ leistungRouter.get("/workspace/api/leistung/:creatorId", (req, res) => {
|
||||
FROM leistung_zeitraum WHERE creator_id = ?
|
||||
ORDER BY bis DESC, von DESC LIMIT 6`).all(creatorId);
|
||||
|
||||
/* ALLES, WAS IN DER DATEI STAND -- IN EINER EINZIGEN ABFRAGE.
|
||||
|
||||
Filipe: "wenn ich dir runter lade soll alles notiert und
|
||||
angezeigt werden." Notiert allein reicht nicht; was niemand
|
||||
sieht, ist so gut wie nicht da.
|
||||
|
||||
EINE Abfrage fuer alle sechs Zeitraeume statt sechs einzelner --
|
||||
dieselbe Regel wie bei den Chat-Reaktionen. Sortiert nach `nr`,
|
||||
also in der Reihenfolge der Datei: Backstage gruppiert seine
|
||||
Spalten bereits sinnvoll (dieser Zeitraum, letzter Monat,
|
||||
Prozente, Matches, Fanclub). Eine eigene Gruppierung waere eine
|
||||
erfundene Ordnung, die beim naechsten Backstage-Update falsch
|
||||
ist. */
|
||||
if (zeitraeume.length) {
|
||||
const nach = new Map(zeitraeume.map((z) => [z.von + "|" + z.bis, z]));
|
||||
for (const z of zeitraeume) z.felder = [];
|
||||
for (const f of db().prepare(`
|
||||
SELECT von, bis, nr, name, text, zahl
|
||||
FROM leistung_zeitraum_feld WHERE creator_id = ?
|
||||
ORDER BY nr`).all(creatorId)) {
|
||||
nach.get(f.von + "|" + f.bis)?.felder.push(
|
||||
{ nr: f.nr, name: f.name, text: f.text, zahl: f.zahl });
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
zeitraeume,
|
||||
creator_id: creatorId,
|
||||
@@ -586,6 +611,59 @@ export function zahlLesen(roh) {
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
ALLES, WAS IN DER ZEILE STAND (16.09.2026)
|
||||
|
||||
Filipe: "ich will dass alles von der excel datei genommen wird ...
|
||||
wenn ich dir runter lade soll alles notiert und angezeigt werden."
|
||||
|
||||
Seine echte Ausgabe hat 41 Spalten; `spaltenRaten` kennt acht davon.
|
||||
Die restlichen 33 fielen beim Einlesen weg -- lautlos, ohne dass
|
||||
irgendwo stand, dass es sie gab: Zahlen vom letzten Monat, fuenf
|
||||
Prozentwerte, Matches, Multi-Gast-LIVEs, Fanclub, Graduierungs- und
|
||||
Stufenstatus.
|
||||
|
||||
DIESE FUNKTION DEUTET NICHTS. Sie nimmt Ueberschrift und Zelle,
|
||||
so wie sie dastehen. Das ist Absicht: Was "Diamanten - Prozent
|
||||
erreicht" genau bedeutet, weiss TikTok, nicht ich -- und eine
|
||||
erfundene Bedeutung waere schlimmer als gar keine. Nachgerechnet
|
||||
habe ich es trotzdem einmal (13.210 / 29.488 = 44,79 %, und dasselbe
|
||||
Verhaeltnis stimmt bei allen fuenf Groessen), aber eine Herleitung
|
||||
aus EINER Zeile ist kein Beweis, und sie steht deshalb nicht im Code.
|
||||
|
||||
WAS UEBERSPRUNGEN WIRD, und warum genau das:
|
||||
* Spalten OHNE Ueberschrift -- ohne Namen gibt es keinen
|
||||
Schluessel, und "Spalte 37" ist keine Auskunft.
|
||||
* DOPPELTE Ueberschriften -- die erste gilt. Der Schluessel in der
|
||||
Datenbank ist der Name; zwei gleiche wuerden einander
|
||||
ueberschreiben, und dann haenge es von der Reihenfolge ab, welche
|
||||
gewinnt.
|
||||
* LEERE Zellen -- eine leere Zelle ist kein Wert. In Filipes Datei
|
||||
ist "Status" leer; eine Zeile "Status: (nichts)" waere eine
|
||||
Auskunft, die die Datei gar nicht gibt.
|
||||
|
||||
BEIDES WIRD BEHALTEN: der Text woertlich und, falls lesbar, die
|
||||
Zahl. Nur die Zahl hiesse "Nicht graduiert" wegzuwerfen; nur den
|
||||
Text hiesse, nicht rechnen zu koennen. */
|
||||
export function felderAus(kopf, zeile) {
|
||||
const felder = [];
|
||||
const gesehen = new Set();
|
||||
for (let i = 0; i < kopf.length; i++) {
|
||||
const name = String(kopf[i] ?? "").trim();
|
||||
if (!name || gesehen.has(name)) continue;
|
||||
gesehen.add(name);
|
||||
const text = String(zeile[i] ?? "").trim();
|
||||
if (!text) continue;
|
||||
felder.push({
|
||||
nr: i,
|
||||
name: name.slice(0, 120),
|
||||
text: text.slice(0, 300),
|
||||
zahl: zahlLesen(text),
|
||||
});
|
||||
}
|
||||
return felder;
|
||||
}
|
||||
|
||||
/** Macht aus "86Std. 10Min. 52Sek." Minuten.
|
||||
*
|
||||
* GEMESSEN AN DER ECHTEN BACKSTAGE-AUSGABE (14.09.2026): Die Spalte
|
||||
@@ -874,9 +952,48 @@ function handleSchluessel(roh) {
|
||||
* erst das Handle (eindeutig), dann der Name (mehrdeutig, aber
|
||||
* besser als nichts). */
|
||||
function personSpaltenRaten(kopf) {
|
||||
const handle = kopf.findIndex((n) => /handle|username|user.?name|tiktok.?id|@/i.test(String(n)));
|
||||
const name = kopf.findIndex((n, i) => i !== handle
|
||||
&& /nick|anzeige|display|name|creator/i.test(String(n)));
|
||||
/* AN SEINER ECHTEN AUSGABE GEMESSEN (16.09.2026) -- und sie ist hier
|
||||
durchgefallen, bevor eine einzige Zahl ankam.
|
||||
|
||||
"Creator_innendaten" hat DREI Spalten, die nach Person aussehen:
|
||||
|
||||
Creator*in-ID 700001
|
||||
Creator*innen-Anmeldename nova_live
|
||||
Agent <Scout>
|
||||
|
||||
Die alte Regel nahm die ERSTE, die das Wort "creator" enthaelt --
|
||||
also die ID. Danach wurde nach einem Creator namens "700001"
|
||||
gesucht, und den gibt es nicht. Ergebnis: die ganze Datei
|
||||
unzugeordnet, mit der Meldung "steht bei keinem Creator im Feld
|
||||
TikTok". Der Hinweis war richtig formuliert und zeigte in die
|
||||
voellig falsche Richtung.
|
||||
|
||||
ZWEI AENDERUNGEN, und beide sind Ausschluesse, keine Zusaetze:
|
||||
|
||||
1. EINE ID IST KEIN NAME. Was wie eine Kennnummer heisst, kommt
|
||||
fuer beide Spalten nicht in Frage. Eine Nummer trifft nie einen
|
||||
Namen -- sie kann also nur schaden.
|
||||
2. "ANMELDENAME" IST DER HANDLE. So heisst bei TikTok der Name
|
||||
mit dem @. Damit wird die richtige Spalte zum Handle, statt gar
|
||||
keine zu finden.
|
||||
|
||||
UND "creator" ALLEIN REICHT NICHT MEHR als Namensspalte. In
|
||||
derselben Datei steht weiter hinten "Neue*r LIVE-Creator*innen"
|
||||
mit dem Wert "Nein" -- die alte Regel haette nach dem Ausschluss
|
||||
der ID genau dort weitergesucht und einen Creator namens "Nein"
|
||||
gesucht. Ein Spaltenname zaehlt nur noch, wenn er WIRKLICH nach
|
||||
Name klingt (nick, anzeige, display, name) oder fuer sich allein
|
||||
"Creator" heisst -- so wie in den aelteren Ausgaben.
|
||||
|
||||
KEINE BACKSLASHES IM MUSTER, mit Absicht: Diese Datei wird oefter
|
||||
ueber Textwerkzeuge bearbeitet, und ein verschlucktes Zeichen in
|
||||
einer Regex faellt erst auf, wenn Daten fehlen. */
|
||||
const istKennung = /(^|[^a-z])id([^a-z]|$)|nummer/i;
|
||||
const handle = kopf.findIndex((n) => !istKennung.test(String(n))
|
||||
&& /handle|username|user.?name|anmeldename|anmelde.?name|login|@/i.test(String(n)));
|
||||
const name = kopf.findIndex((n, i) => i !== handle && !istKennung.test(String(n))
|
||||
&& (/nick|anzeige|display|name/i.test(String(n))
|
||||
|| /^[\s]*creator[\s*:in]*[\s]*$/i.test(String(n))));
|
||||
return { handle, name };
|
||||
}
|
||||
|
||||
@@ -974,7 +1091,12 @@ function netzwerkLesen(text, person, tagVorgabe) {
|
||||
|
||||
const werte = spanne
|
||||
? { creator_id: p.id, name: p.name, von: spanne.von, bis: spanne.bis,
|
||||
tage: tageImZeitraum(spanne.von, spanne.bis) }
|
||||
tage: tageImZeitraum(spanne.von, spanne.bis),
|
||||
/* ALLE Spalten der Zeile, nicht nur die acht erkannten.
|
||||
Nur beim Zeitraum: Die Tageszeilen haben ihre eigene
|
||||
Tabelle, und ein halb gebautes Feld dort waere Datenhaltung
|
||||
ohne Anzeige -- also nichts, was jemand je saehe. */
|
||||
felder: felderAus(kopf, z) }
|
||||
: { creator_id: p.id, name: p.name, tag };
|
||||
let hatZahl = false;
|
||||
for (const feld of messwerte) {
|
||||
@@ -1039,6 +1161,18 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import/vorschau", gleicheH
|
||||
tage: e.treffer.find((t) => t.von).tage }
|
||||
: null,
|
||||
zugeordnet: e.treffer.length, neu, ersetzt,
|
||||
/* WIE VIELE SPALTEN LANDEN WIRKLICH IN DER ABLAGE?
|
||||
|
||||
Filipes Sorge war genau die: "ich will dass alles von der
|
||||
excel datei genommen wird." Das gehoert VOR das Bestaetigen,
|
||||
nicht hinterher -- sonst muesste er der Zusage glauben,
|
||||
statt die Zahl zu sehen.
|
||||
|
||||
Gemessen am ERSTEN Zeitraum der Datei, nicht ueber alle
|
||||
summiert: Alle Zeilen haben dieselben Spalten, eine Summe
|
||||
ueber 40 Creator waere eine grosse Zahl ohne Aussage. */
|
||||
spalten_gesamt: (e.kopf || []).length,
|
||||
felder: (e.treffer.find((t) => t.felder) || {}).felder?.length ?? 0,
|
||||
unbekannt: e.unbekannt, probleme: e.schlecht.slice(0, 12),
|
||||
probleme_gesamt: e.schlecht.length,
|
||||
creator_gesamt: e.creator_gesamt,
|
||||
@@ -1082,12 +1216,28 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft,
|
||||
erfasst, erfasst_von)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
||||
|
||||
/* ERST WEGRAEUMEN, DANN SCHREIBEN. Ohne das Loeschen bliebe eine
|
||||
Spalte, die beim ersten Mal gefuellt war und beim zweiten leer
|
||||
ist, mit ihrem ALTEN Wert stehen -- ein Wert aus einer Datei,
|
||||
die es nicht mehr gibt, und niemand koennte ihn von einem
|
||||
frischen unterscheiden. */
|
||||
const felderWeg = d.prepare(`DELETE FROM leistung_zeitraum_feld
|
||||
WHERE creator_id = ? AND von = ? AND bis = ?`);
|
||||
const feldSchreiben = d.prepare(`INSERT OR REPLACE INTO leistung_zeitraum_feld
|
||||
(creator_id, von, bis, nr, name, text, zahl) VALUES (?,?,?,?,?,?,?)`);
|
||||
|
||||
let geschrieben = 0;
|
||||
let spannen = 0;
|
||||
let felder = 0;
|
||||
d.exec("BEGIN");
|
||||
try {
|
||||
for (const t of e.treffer) {
|
||||
if (t.von) {
|
||||
felderWeg.run(t.creator_id, t.von, t.bis);
|
||||
for (const f of (t.felder || [])) {
|
||||
feldSchreiben.run(t.creator_id, t.von, t.bis, f.nr, f.name, f.text, f.zahl);
|
||||
felder++;
|
||||
}
|
||||
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,
|
||||
@@ -1113,10 +1263,10 @@ 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} Tage, ${spannen} Zeitraeume, `
|
||||
detail: `${geschrieben} Tage, ${spannen} Zeitraeume, ${felder} Felder, `
|
||||
+ `${e.unbekannt.length} unbekannt`.slice(0, 120),
|
||||
});
|
||||
res.json({ geschrieben, zeitraeume: spannen,
|
||||
res.json({ geschrieben, zeitraeume: spannen, felder,
|
||||
unbekannt: e.unbekannt, probleme: e.schlecht.length });
|
||||
} catch (fehler) {
|
||||
console.error("[leistung] Netzwerk-Import:", fehler?.message);
|
||||
@@ -1368,6 +1518,22 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft
|
||||
erfasst, erfasst_von)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
||||
|
||||
/* UND ALLE UEBRIGEN SPALTEN -- WORTGLEICH WIE IM ANDEREN WEG.
|
||||
|
||||
Es gibt zwei Wege in dieses System (eine Datei je Creator und
|
||||
eine Tabelle fuer alle), und sie sind in diesem Modul schon
|
||||
dreimal auseinandergelaufen: beim Zeitraumschutz, bei der
|
||||
Dauer-Einheit und beim dritten Ausgang. Jedes Mal, weil eine
|
||||
Sache an zwei Stellen geschrieben stand.
|
||||
|
||||
Deshalb kommt die Liste der Felder hier NICHT aus einer zweiten
|
||||
Rechnung, sondern aus derselben Funktion `felderAus`. */
|
||||
const felderWeg = d.prepare(`DELETE FROM leistung_zeitraum_feld
|
||||
WHERE creator_id = ? AND von = ? AND bis = ?`);
|
||||
const feldSchreiben = d.prepare(`INSERT OR REPLACE INTO leistung_zeitraum_feld
|
||||
(creator_id, von, bis, nr, name, text, zahl) VALUES (?,?,?,?,?,?,?)`);
|
||||
let felder = 0;
|
||||
|
||||
let geschrieben = 0, uebersprungen = 0, spannen = 0;
|
||||
d.exec("BEGIN");
|
||||
try {
|
||||
@@ -1391,6 +1557,11 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft
|
||||
wz("zuschauer_avg"), wz("zuschauer_max"), wz("verweildauer_s"),
|
||||
wz("schenker"), wz("follower_neu"),
|
||||
jetzt(), req.person.id);
|
||||
felderWeg.run(creatorId, von, bis);
|
||||
for (const f of felderAus(zeilen[0], z)) {
|
||||
feldSchreiben.run(creatorId, von, bis, f.nr, f.name, f.text, f.zahl);
|
||||
felder++;
|
||||
}
|
||||
spannen++;
|
||||
continue;
|
||||
}
|
||||
@@ -1420,9 +1591,9 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft
|
||||
|
||||
protokolliere("leistung_import", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `Creator #${creatorId}: ${geschrieben} Tage`,
|
||||
detail: `Creator #${creatorId}: ${geschrieben} Tage, ${spannen} Zeitraeume, ${felder} Felder`,
|
||||
});
|
||||
res.json({ ok: true, geschrieben, zeitraeume: spannen, uebersprungen });
|
||||
res.json({ ok: true, geschrieben, zeitraeume: spannen, felder, uebersprungen });
|
||||
} catch (fehler) {
|
||||
console.error("[leistung] Import:", fehler?.message);
|
||||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||||
|
||||
Reference in New Issue
Block a user