@cachly-dev/mcp-server 0.10.129 → 0.10.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -225
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -260
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -113
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4397
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1865
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -141
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -249
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -140
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -264
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -203
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -156
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -159
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/feldreparatur.d.ts +0 -58
- package/dist/src/feldreparatur.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/interferenz.d.ts +0 -111
- package/dist/src/interferenz.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -205
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/resonanz.d.ts +0 -85
- package/dist/src/resonanz.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -187
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -148
- package/dist/src/wertbeitrag.js.map +0 -1
|
@@ -1,290 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Rüstet den vorhandenen Lektionsbestand mit Eingängen und Namensvektoren nach.
|
|
3
|
-
*
|
|
4
|
-
* ── Warum es dieses Werkzeug gibt ───────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* Seit dem 20.08.2026 schreibt `learn_from_attempts` die Fehlertext-Eingänge
|
|
7
|
-
* beim Speichern mit. Die Lektionen, die vorher entstanden sind, haben keine —
|
|
8
|
-
* und ohne Nachrüstung wirkt die Verbesserung nur für alles, was ab heute
|
|
9
|
-
* gelernt wird. Bei 501 vorhandenen und wenigen neuen Lektionen je Woche wäre
|
|
10
|
-
* das praktisch keine Wirkung.
|
|
11
|
-
*
|
|
12
|
-
* ── Was es kostet ──────────────────────────────────────────────────────────
|
|
13
|
-
*
|
|
14
|
-
* Rund 1,3 Einbettungen je Lektion (nicht jede trägt einen Fehlertext).
|
|
15
|
-
* Gemessen am 20.08.: 1440 Einbettungen in 406 s mit Admin-Schlüssel, das sind
|
|
16
|
-
* 3,5 je Sekunde. Für 501 Lektionen also wenige Minuten.
|
|
17
|
-
*
|
|
18
|
-
* OHNE Admin-Schlüssel greift die Drosselung von 60 Anfragen je Minute
|
|
19
|
-
* (api/cmd/server/routes.go). Ein paralleler Lauf hat am 19.08. 563 mal
|
|
20
|
-
* HTTP 429 erzeugt, eine Wachhund-Warnung mit unserer eigenen Adresse
|
|
21
|
-
* ausgelöst und eine automatische Hochskalierung angestoßen. Deshalb die
|
|
22
|
-
* Notbremse.
|
|
23
|
-
*
|
|
24
|
-
* ── Was es NICHT tut ───────────────────────────────────────────────────────
|
|
25
|
-
*
|
|
26
|
-
* Es rührt die Lektionen selbst nicht an und schreibt keine Vektoren. Fällt es
|
|
27
|
-
* mittendrin aus, ist der Bestand in demselben Zustand wie vorher, nur mit
|
|
28
|
-
* weniger Eingängen — ein zweiter Lauf holt den Rest.
|
|
29
|
-
*
|
|
30
|
-
* Aufruf:
|
|
31
|
-
* REDIS_URL=... CACHLY_JWT=... CACHLY_ADMIN_KEY=... \
|
|
32
|
-
* npx tsx src/eingaenge-nachruesten.ts [--probe 20] [--parallel 6]
|
|
33
|
-
* npx tsx src/eingaenge-nachruesten.ts --selbstprobe
|
|
34
|
-
*/
|
|
35
|
-
import { Redis } from 'ioredis';
|
|
36
|
-
import { schreibeEingaenge, fehlertexteAus, eingangsText, EINGANG_PRAEFIX } from './eingaenge.js';
|
|
37
|
-
import { NAME_VEKTOR_PRAEFIX, VEKTOR_PRAEFIX, packe, textFuerNamensVektor, textFuerVektor, } from './bedeutung.js';
|
|
38
|
-
const LEKTION_PRAEFIX = 'cachly:lesson:best:';
|
|
39
|
-
/** Zählt die 429er. Bei zehn ist Schluss — lieber abgebrochen als ein Vorfall. */
|
|
40
|
-
class Notbremse {
|
|
41
|
-
grenze;
|
|
42
|
-
zaehler = 0;
|
|
43
|
-
constructor(grenze = 10) {
|
|
44
|
-
this.grenze = grenze;
|
|
45
|
-
}
|
|
46
|
-
melde429() { this.zaehler++; }
|
|
47
|
-
get gezogen() { return this.zaehler >= this.grenze; }
|
|
48
|
-
get stand() { return this.zaehler; }
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Wie viele Lektionen überhaupt einen Eingang bekommen könnten.
|
|
52
|
-
*
|
|
53
|
-
* Wird VOR dem ersten Netzaufruf gerechnet und gemeldet: wer die Zahl vorher
|
|
54
|
-
* kennt, merkt sofort, wenn am Ende viel weniger herauskommt.
|
|
55
|
-
*/
|
|
56
|
-
export function wieVieleTragen(lektionen) {
|
|
57
|
-
return lektionen.filter((l) => fehlertexteAus(eingangsText(l)).length > 0).length;
|
|
58
|
-
}
|
|
59
|
-
function selbstprobe() {
|
|
60
|
-
const proben = [];
|
|
61
|
-
const p = (was, ok) => { proben.push([was, ok]); };
|
|
62
|
-
const mit = { what_worked: 'Der Worker meldete "No space left on device".' };
|
|
63
|
-
const ohne = { what_worked: 'Wir haben die Reihenfolge der Schritte getauscht.' };
|
|
64
|
-
p('zählt nur Lektionen mit Fehlertext', wieVieleTragen([mit, ohne, mit]) === 2);
|
|
65
|
-
p('leerer Bestand ergibt null', wieVieleTragen([]) === 0);
|
|
66
|
-
const b = new Notbremse(3);
|
|
67
|
-
b.melde429();
|
|
68
|
-
b.melde429();
|
|
69
|
-
p('zwei von drei reichen nicht', !b.gezogen);
|
|
70
|
-
b.melde429();
|
|
71
|
-
p('drei ziehen die Bremse', b.gezogen);
|
|
72
|
-
let rot = 0;
|
|
73
|
-
for (const [was, ok] of proben) {
|
|
74
|
-
console.log(`${ok ? 'ok ' : 'ROT '} ${was}`);
|
|
75
|
-
if (!ok)
|
|
76
|
-
rot++;
|
|
77
|
-
}
|
|
78
|
-
console.log(rot === 0 ? 'Selbstprobe bestanden.' : `Selbstprobe ROT: ${rot} von ${proben.length}.`);
|
|
79
|
-
process.exit(rot === 0 ? 0 : 1);
|
|
80
|
-
}
|
|
81
|
-
async function main() {
|
|
82
|
-
const argv = process.argv.slice(2);
|
|
83
|
-
const flag = (n) => {
|
|
84
|
-
const i = argv.indexOf(`--${n}`);
|
|
85
|
-
return i !== -1 ? argv[i + 1] : undefined;
|
|
86
|
-
};
|
|
87
|
-
const url = process.env.CACHLY_REDIS_URL ?? process.env.REDIS_URL;
|
|
88
|
-
const API = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
89
|
-
const JWT = process.env.CACHLY_JWT ?? '';
|
|
90
|
-
const ADMIN = process.env.CACHLY_ADMIN_KEY ?? '';
|
|
91
|
-
if (!url) {
|
|
92
|
-
console.error('NICHT GELAUFEN: CACHLY_REDIS_URL oder REDIS_URL fehlt.');
|
|
93
|
-
process.exit(2);
|
|
94
|
-
}
|
|
95
|
-
if (!JWT) {
|
|
96
|
-
console.error('NICHT GELAUFEN: CACHLY_JWT fehlt.');
|
|
97
|
-
process.exit(2);
|
|
98
|
-
}
|
|
99
|
-
const probe = Number(flag('probe') ?? '0');
|
|
100
|
-
const parallel = Number(flag('parallel') ?? (ADMIN ? '6' : '2'));
|
|
101
|
-
const bremse = new Notbremse(10);
|
|
102
|
-
const einbetten = async (text) => {
|
|
103
|
-
for (let versuch = 0; versuch < 4; versuch++) {
|
|
104
|
-
if (bremse.gezogen)
|
|
105
|
-
return null;
|
|
106
|
-
try {
|
|
107
|
-
const kopf = { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` };
|
|
108
|
-
if (ADMIN)
|
|
109
|
-
kopf['X-Admin-Key'] = ADMIN;
|
|
110
|
-
const r = await fetch(`${API}/api/v1/embed`, {
|
|
111
|
-
method: 'POST', headers: kopf,
|
|
112
|
-
body: JSON.stringify({ text: text.slice(0, 2000) }),
|
|
113
|
-
signal: AbortSignal.timeout(60_000),
|
|
114
|
-
});
|
|
115
|
-
if (r.status === 429) {
|
|
116
|
-
bremse.melde429();
|
|
117
|
-
await new Promise((f) => setTimeout(f, 2000 * (versuch + 1)));
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (!r.ok)
|
|
121
|
-
return null;
|
|
122
|
-
const j = await r.json();
|
|
123
|
-
return j.embedding ?? j.data?.[0]?.embedding ?? null;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
await new Promise((f) => setTimeout(f, 800 * (versuch + 1)));
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
};
|
|
131
|
-
const redis = new Redis(url, { maxRetriesPerRequest: 2, connectTimeout: 8000 });
|
|
132
|
-
try {
|
|
133
|
-
const schluessel = [];
|
|
134
|
-
let cursor = '0';
|
|
135
|
-
do {
|
|
136
|
-
const [next, gefunden] = await redis.scan(cursor, 'MATCH', `${LEKTION_PRAEFIX}*`, 'COUNT', 500);
|
|
137
|
-
cursor = next;
|
|
138
|
-
schluessel.push(...gefunden);
|
|
139
|
-
} while (cursor !== '0');
|
|
140
|
-
if (schluessel.length === 0) {
|
|
141
|
-
console.error(`NICHT GELAUFEN: keine Lektionen unter ${LEKTION_PRAEFIX}* — falsche Instanz?`);
|
|
142
|
-
process.exit(3);
|
|
143
|
-
}
|
|
144
|
-
const lektionen = [];
|
|
145
|
-
for (let i = 0; i < schluessel.length; i += 100) {
|
|
146
|
-
const block = schluessel.slice(i, i + 100);
|
|
147
|
-
const werte = await redis.mget(...block);
|
|
148
|
-
for (const [j, roh] of werte.entries()) {
|
|
149
|
-
if (!roh)
|
|
150
|
-
continue;
|
|
151
|
-
try {
|
|
152
|
-
const o = JSON.parse(roh);
|
|
153
|
-
const topic = typeof o.topic === 'string' && o.topic ? o.topic : block[j].slice(LEKTION_PRAEFIX.length);
|
|
154
|
-
if (topic)
|
|
155
|
-
lektionen.push({ topic, inhalt: o });
|
|
156
|
-
}
|
|
157
|
-
catch { /* eine kaputte Zeile ist ein Thema fuer doctor */ }
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// Was schon da ist, wird übersprungen — ein zweiter Lauf ist billig.
|
|
161
|
-
const schonDa = async (praefix) => {
|
|
162
|
-
const menge = new Set();
|
|
163
|
-
let c = '0';
|
|
164
|
-
do {
|
|
165
|
-
const [next, gefunden] = await redis.scan(c, 'MATCH', `${praefix}*`, 'COUNT', 500);
|
|
166
|
-
c = next;
|
|
167
|
-
for (const k of gefunden)
|
|
168
|
-
menge.add(k.slice(praefix.length));
|
|
169
|
-
} while (c !== '0');
|
|
170
|
-
return menge;
|
|
171
|
-
};
|
|
172
|
-
let fertigNamen = 0;
|
|
173
|
-
const vorhanden = await schonDa(EINGANG_PRAEFIX);
|
|
174
|
-
const namenDa = await schonDa(NAME_VEKTOR_PRAEFIX);
|
|
175
|
-
const volltextDa = await schonDa(VEKTOR_PRAEFIX);
|
|
176
|
-
// ── Volltextvektoren ─────────────────────────────────────────────────────
|
|
177
|
-
//
|
|
178
|
-
// Gemessen am 20.08.2026 gegen die Produktion: `cachly:lesson:vec:*` war
|
|
179
|
-
// LEER — bei 506 Lektionen kein einziger Vektor. Damit lief der
|
|
180
|
-
// Bedeutungsabgleich dort nie: `vektorbestand.groesse === 0` steigt in
|
|
181
|
-
// brain.ts sofort aus und faellt auf den reinen Wortabgleich zurueck.
|
|
182
|
-
//
|
|
183
|
-
// Das ist die Grundlage von allem anderen. Ohne diese Vektoren nuetzen
|
|
184
|
-
// weder Namensvektoren noch Eingaenge etwas.
|
|
185
|
-
const ohneVolltext = lektionen.filter((l) => !volltextDa.has(l.topic));
|
|
186
|
-
console.log(`${volltextDa.size} Lektionen haben einen Volltextvektor, ${ohneVolltext.length} fehlen.`);
|
|
187
|
-
if (ohneVolltext.length > 0) {
|
|
188
|
-
let n = 0;
|
|
189
|
-
let fertigVoll = 0;
|
|
190
|
-
let naechsterVoll = 0;
|
|
191
|
-
const vollArbeiter = Array.from({ length: parallel }, async () => {
|
|
192
|
-
for (;;) {
|
|
193
|
-
const i = naechsterVoll++;
|
|
194
|
-
if (i >= ohneVolltext.length || bremse.gezogen)
|
|
195
|
-
return;
|
|
196
|
-
const v = await einbetten(textFuerVektor(ohneVolltext[i].inhalt));
|
|
197
|
-
if (v?.length) {
|
|
198
|
-
await redis.set(`${VEKTOR_PRAEFIX}${ohneVolltext[i].topic}`, packe(v));
|
|
199
|
-
n++;
|
|
200
|
-
}
|
|
201
|
-
if (++fertigVoll % 100 === 0)
|
|
202
|
-
console.log(` Volltext ${fertigVoll}/${ohneVolltext.length}`);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
await Promise.all(vollArbeiter);
|
|
206
|
-
console.log(`${n} Volltextvektoren geschrieben.`);
|
|
207
|
-
}
|
|
208
|
-
// ── Namensvektoren zuerst ────────────────────────────────────────────────
|
|
209
|
-
//
|
|
210
|
-
// Sie sind die Voraussetzung dafür, dass der Sortierer aus rangfolge.ts
|
|
211
|
-
// überhaupt greifen kann: ohne sie ist `naeheThema` für jede Lektion -2,
|
|
212
|
-
// und ein Merkmal mit Gewicht 0,6 trägt dann nichts bei.
|
|
213
|
-
const ohneNamen = lektionen.filter((l) => !namenDa.has(l.topic));
|
|
214
|
-
console.log(`${namenDa.size} Lektionen haben einen Namensvektor, ${ohneNamen.length} fehlen.`);
|
|
215
|
-
if (ohneNamen.length > 0) {
|
|
216
|
-
let n = 0;
|
|
217
|
-
let naechsterName = 0;
|
|
218
|
-
const namensArbeiter = Array.from({ length: parallel }, async () => {
|
|
219
|
-
for (;;) {
|
|
220
|
-
const i = naechsterName++;
|
|
221
|
-
if (i >= ohneNamen.length || bremse.gezogen)
|
|
222
|
-
return;
|
|
223
|
-
const v = await einbetten(textFuerNamensVektor(ohneNamen[i].topic));
|
|
224
|
-
if (v?.length) {
|
|
225
|
-
await redis.set(`${NAME_VEKTOR_PRAEFIX}${ohneNamen[i].topic}`, packe(v));
|
|
226
|
-
n++;
|
|
227
|
-
}
|
|
228
|
-
if (++fertigNamen % 100 === 0)
|
|
229
|
-
console.log(` Namen ${fertigNamen}/${ohneNamen.length}`);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
await Promise.all(namensArbeiter);
|
|
233
|
-
console.log(`${n} Namensvektoren geschrieben.`);
|
|
234
|
-
}
|
|
235
|
-
let offen = lektionen.filter((l) => !vorhanden.has(l.topic));
|
|
236
|
-
const koennten = wieVieleTragen(offen.map((l) => l.inhalt));
|
|
237
|
-
if (probe > 0)
|
|
238
|
-
offen = offen.slice(0, probe);
|
|
239
|
-
console.log(`${lektionen.length} Lektionen, ${vorhanden.size} haben schon Eingänge.`);
|
|
240
|
-
console.log(`${offen.length} offen, davon tragen ${koennten} einen Fehlertext.`);
|
|
241
|
-
console.log(ADMIN
|
|
242
|
-
? `Admin-Schlüssel gesetzt — ${parallel} gleichzeitig, Notbremse bei 10x 429.`
|
|
243
|
-
: `KEIN Admin-Schlüssel — gedrosselt mit ${parallel} gleichzeitig. Das dauert.`);
|
|
244
|
-
let fertig = 0;
|
|
245
|
-
let geschrieben = 0;
|
|
246
|
-
let leer = 0;
|
|
247
|
-
const begonnen = Date.now();
|
|
248
|
-
let naechster = 0;
|
|
249
|
-
const arbeiter = Array.from({ length: parallel }, async () => {
|
|
250
|
-
for (;;) {
|
|
251
|
-
const i = naechster++;
|
|
252
|
-
if (i >= offen.length || bremse.gezogen)
|
|
253
|
-
return;
|
|
254
|
-
const l = offen[i];
|
|
255
|
-
try {
|
|
256
|
-
const n = await schreibeEingaenge(redis, l.topic, l.inhalt, einbetten);
|
|
257
|
-
if (n > 0)
|
|
258
|
-
geschrieben += n;
|
|
259
|
-
else
|
|
260
|
-
leer++;
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
leer++;
|
|
264
|
-
}
|
|
265
|
-
fertig++;
|
|
266
|
-
if (fertig % 50 === 0) {
|
|
267
|
-
const proSek = fertig / ((Date.now() - begonnen) / 1000);
|
|
268
|
-
console.log(` ${fertig}/${offen.length} (${proSek.toFixed(1)}/s, 429er: ${bremse.stand})`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
await Promise.all(arbeiter);
|
|
273
|
-
const dauer = ((Date.now() - begonnen) / 1000).toFixed(0);
|
|
274
|
-
console.log(`Fertig in ${dauer} s. ${geschrieben} Eingänge für ${offen.length - leer} Lektionen.`);
|
|
275
|
-
console.log(`${leer} Lektionen tragen keinen Fehlertext — die bleiben ohne Eingang.`);
|
|
276
|
-
if (bremse.gezogen) {
|
|
277
|
-
console.error(`NOTBREMSE: ${bremse.stand} mal HTTP 429 — abgebrochen. Ein zweiter Lauf holt den Rest.`);
|
|
278
|
-
process.exit(4);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
finally {
|
|
282
|
-
redis.disconnect();
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
const direktGestartet = process.argv[1]?.replace(/\\/g, '/').endsWith('/eingaenge-nachruesten.ts');
|
|
286
|
-
if (direktGestartet && process.argv.includes('--selbstprobe'))
|
|
287
|
-
selbstprobe();
|
|
288
|
-
else if (direktGestartet)
|
|
289
|
-
void main();
|
|
290
|
-
//# sourceMappingURL=eingaenge-nachruesten.js.map
|
|
1
|
+
import{Redis as K}from"ioredis";import{schreibeEingaenge as M,fehlertexteAus as W,eingangsText as J,EINGANG_PRAEFIX as X}from"./eingaenge.js";import{NAME_VEKTOR_PRAEFIX as F,VEKTOR_PRAEFIX as D,packe as P,textFuerNamensVektor as j,textFuerVektor as B}from"./bedeutung.js";const T="cachly:lesson:best:";class H{grenze;zaehler=0;constructor(o=10){this.grenze=o}melde429(){this.zaehler++}get gezogen(){return this.zaehler>=this.grenze}get stand(){return this.zaehler}}function L(c){return c.filter(o=>W(J(o)).length>0).length}function q(){const c=[],o=($,r)=>{c.push([$,r])},N={what_worked:'Der Worker meldete "No space left on device".'};o("z\xE4hlt nur Lektionen mit Fehlertext",L([N,{what_worked:"Wir haben die Reihenfolge der Schritte getauscht."},N])===2),o("leerer Bestand ergibt null",L([])===0);const f=new H(3);f.melde429(),f.melde429(),o("zwei von drei reichen nicht",!f.gezogen),f.melde429(),o("drei ziehen die Bremse",f.gezogen);let a=0;for(const[$,r]of c)console.log(`${r?"ok ":"ROT "} ${$}`),r||a++;console.log(a===0?"Selbstprobe bestanden.":`Selbstprobe ROT: ${a} von ${c.length}.`),process.exit(a===0?0:1)}async function Q(){const c=process.argv.slice(2),o=m=>{const s=c.indexOf(`--${m}`);return s!==-1?c[s+1]:void 0},N=process.env.CACHLY_REDIS_URL??process.env.REDIS_URL,R=process.env.CACHLY_API_URL??"https://api.cachly.dev",f=process.env.CACHLY_JWT??"",a=process.env.CACHLY_ADMIN_KEY??"";N||(console.error("NICHT GELAUFEN: CACHLY_REDIS_URL oder REDIS_URL fehlt."),process.exit(2)),f||(console.error("NICHT GELAUFEN: CACHLY_JWT fehlt."),process.exit(2));const $=Number(o("probe")??"0"),r=Number(o("parallel")??(a?"6":"2")),d=new H(10),x=async m=>{for(let s=0;s<4;s++){if(d.gezogen)return null;try{const l={"Content-Type":"application/json",Authorization:`Bearer ${f}`};a&&(l["X-Admin-Key"]=a);const b=await fetch(`${R}/api/v1/embed`,{method:"POST",headers:l,body:JSON.stringify({text:m.slice(0,2e3)}),signal:AbortSignal.timeout(6e4)});if(b.status===429){d.melde429(),await new Promise(E=>setTimeout(E,2e3*(s+1)));continue}if(!b.ok)return null;const k=await b.json();return k.embedding??k.data?.[0]?.embedding??null}catch{await new Promise(l=>setTimeout(l,800*(s+1)))}}return null},u=new K(N,{maxRetriesPerRequest:2,connectTimeout:8e3});try{const m=[];let s="0";do{const[e,t]=await u.scan(s,"MATCH",`${T}*`,"COUNT",500);s=e,m.push(...t)}while(s!=="0");m.length===0&&(console.error(`NICHT GELAUFEN: keine Lektionen unter ${T}* \u2014 falsche Instanz?`),process.exit(3));const l=[];for(let e=0;e<m.length;e+=100){const t=m.slice(e,e+100),n=await u.mget(...t);for(const[g,i]of n.entries())if(i)try{const h=JSON.parse(i),S=typeof h.topic=="string"&&h.topic?h.topic:t[g].slice(T.length);S&&l.push({topic:S,inhalt:h})}catch{}}const b=async e=>{const t=new Set;let n="0";do{const[g,i]=await u.scan(n,"MATCH",`${e}*`,"COUNT",500);n=g;for(const h of i)t.add(h.slice(e.length))}while(n!=="0");return t};let k=0;const E=await b(X),y=await b(F),C=await b(D),w=l.filter(e=>!C.has(e.topic));if(console.log(`${C.size} Lektionen haben einen Volltextvektor, ${w.length} fehlen.`),w.length>0){let e=0,t=0,n=0;const g=Array.from({length:r},async()=>{for(;;){const i=n++;if(i>=w.length||d.gezogen)return;const h=await x(B(w[i].inhalt));h?.length&&(await u.set(`${D}${w[i].topic}`,P(h)),e++),++t%100===0&&console.log(` Volltext ${t}/${w.length}`)}});await Promise.all(g),console.log(`${e} Volltextvektoren geschrieben.`)}const A=l.filter(e=>!y.has(e.topic));if(console.log(`${y.size} Lektionen haben einen Namensvektor, ${A.length} fehlen.`),A.length>0){let e=0,t=0;const n=Array.from({length:r},async()=>{for(;;){const g=t++;if(g>=A.length||d.gezogen)return;const i=await x(j(A[g].topic));i?.length&&(await u.set(`${F}${A[g].topic}`,P(i)),e++),++k%100===0&&console.log(` Namen ${k}/${A.length}`)}});await Promise.all(n),console.log(`${e} Namensvektoren geschrieben.`)}let p=l.filter(e=>!E.has(e.topic));const V=L(p.map(e=>e.inhalt));$>0&&(p=p.slice(0,$)),console.log(`${l.length} Lektionen, ${E.size} haben schon Eing\xE4nge.`),console.log(`${p.length} offen, davon tragen ${V} einen Fehlertext.`),console.log(a?`Admin-Schl\xFCssel gesetzt \u2014 ${r} gleichzeitig, Notbremse bei 10x 429.`:`KEIN Admin-Schl\xFCssel \u2014 gedrosselt mit ${r} gleichzeitig. Das dauert.`);let z=0,_=0,v=0;const I=Date.now();let U=0;const Y=Array.from({length:r},async()=>{for(;;){const e=U++;if(e>=p.length||d.gezogen)return;const t=p[e];try{const n=await M(u,t.topic,t.inhalt,x);n>0?_+=n:v++}catch{v++}if(z++,z%50===0){const n=z/((Date.now()-I)/1e3);console.log(` ${z}/${p.length} (${n.toFixed(1)}/s, 429er: ${d.stand})`)}}});await Promise.all(Y);const G=((Date.now()-I)/1e3).toFixed(0);console.log(`Fertig in ${G} s. ${_} Eing\xE4nge f\xFCr ${p.length-v} Lektionen.`),console.log(`${v} Lektionen tragen keinen Fehlertext \u2014 die bleiben ohne Eingang.`),d.gezogen&&(console.error(`NOTBREMSE: ${d.stand} mal HTTP 429 \u2014 abgebrochen. Ein zweiter Lauf holt den Rest.`),process.exit(4))}finally{u.disconnect()}}const O=process.argv[1]?.replace(/\\/g,"/").endsWith("/eingaenge-nachruesten.ts");O&&process.argv.includes("--selbstprobe")?q():O&&Q();export{L as wieVieleTragen};
|
package/dist/src/eingaenge.js
CHANGED
|
@@ -1,260 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* ── Was gemessen wurde, und was daraus folgt ────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* Am 20.08.2026 an 499 echten Lektionen mit zwei unabhängigen 100-Fragen-Sätzen
|
|
7
|
-
* (`.agent/cachly/tor0-tor1-ergebnis.md`):
|
|
8
|
-
*
|
|
9
|
-
* Findequote@3, frischer Prüfsatz
|
|
10
|
-
* heute, nur Volltextvektor 52 %
|
|
11
|
-
* Eingänge in der Vorauswahl 56 %
|
|
12
|
-
* dazu das Merkmal "bester Eingang" im Sortierer 58 %
|
|
13
|
-
* alle Eingänge AUSSER den Fehlertexten 52 % ← null Gewinn
|
|
14
|
-
*
|
|
15
|
-
* Die letzte Zeile ist die wichtigste: von fünf ausprobierten Türarten trägt
|
|
16
|
-
* GENAU EINE. Der erste Satz von what_worked bringt nichts, die Übersetzung
|
|
17
|
-
* bringt nichts, der Themenname steckt längst im Sortierer. Deshalb baut
|
|
18
|
-
* dieses Modul nur die Fehlertexte — alles andere wäre Speicher ohne Wirkung.
|
|
19
|
-
*
|
|
20
|
-
* ── Warum ausgerechnet der Fehlertext ───────────────────────────────────────
|
|
21
|
-
*
|
|
22
|
-
* Er ist ein scharfer Schlüssel. Nutzer fügen Meldungen wörtlich ein; passt der
|
|
23
|
-
* Schlüssel, zieht er die Lektion nach ganz vorn. Der Preis steht in derselben
|
|
24
|
-
* Messung und wird hier offen genannt: dieselben Eingänge SENKEN die Decke von
|
|
25
|
-
* 89 auf 86 % — sie ziehen auch falsche Lektionen in die Vorauswahl. Für den
|
|
26
|
-
* Nutzer zählt trotzdem die Findequote: er sieht drei Lektionen, nicht 25.
|
|
27
|
-
*
|
|
28
|
-
* ── Eine Wahrheit ──────────────────────────────────────────────────────────
|
|
29
|
-
*
|
|
30
|
-
* Die Extraktion stand zuerst im Messwerkzeug (src/bench/eingaenge-b.ts). Sie
|
|
31
|
-
* steht jetzt HIER, und der Bench importiert sie. Zwei Fassungen desselben
|
|
32
|
-
* regulären Ausdrucks wären die Fehlerklasse, die dieses Haus am häufigsten
|
|
33
|
-
* trifft — und die Messung würde etwas anderes prüfen als das Produkt.
|
|
34
|
-
*/
|
|
35
|
-
import { kosinus, packe, entpacke } from './bedeutung.js';
|
|
36
|
-
/** Wo die Eingänge einer Lektion liegen: EIN Hash je Lektion, neben ihr. */
|
|
37
|
-
export const EINGANG_PRAEFIX = 'cachly:lesson:eing:';
|
|
38
|
-
/** Höchstens so viele Eingänge je Lektion. Mehr Türen kosten Decke. */
|
|
39
|
-
export const EINGAENGE_JE_LEKTION = 3;
|
|
40
|
-
/**
|
|
41
|
-
* Die Feld-Markierungen, die im Bestand IM Text stehen.
|
|
42
|
-
*
|
|
43
|
-
* Gemessen: `what_worked` enthält bei vielen Lektionen wörtlich
|
|
44
|
-
* `</what_worked>\n<what_failed>`. Wer das nicht wegnimmt, bettet Feldnamen ein
|
|
45
|
-
* und wundert sich über die Nähe zwischen zwei völlig fremden Lektionen.
|
|
46
|
-
*/
|
|
47
|
-
export function ohneMarkierungen(text) {
|
|
48
|
-
return text.replace(/<\/?(what_worked|what_failed|context|topic)>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Wörtliche Fehlertexte, Codes und Kernsätze aus einer Lektion.
|
|
52
|
-
*
|
|
53
|
-
* Fünf Muster, alle an echten Lektionen belegt:
|
|
54
|
-
* 1. der GROSSGESCHRIEBENE Kernsatz am Anfang (Schreibregel des Hauses)
|
|
55
|
-
* 2. alles in Anführungszeichen oder Rückwärts-Haken
|
|
56
|
-
* 3. Fehlerklassen und Meldungen samt Umfeld (Error, panic, FATAL, HTTP 5xx)
|
|
57
|
-
* 4. Systemfehler-Kürzel MIT Umfeld — "EACCES" allein ist als Vektor blind
|
|
58
|
-
* 5. englische Meldungssätze mitten im deutschen Text
|
|
59
|
-
*/
|
|
60
|
-
export function fehlertexteAus(text, hoechstens = EINGAENGE_JE_LEKTION) {
|
|
61
|
-
const t = ohneMarkierungen(text);
|
|
62
|
-
const aus = [];
|
|
63
|
-
const nimm = (s) => {
|
|
64
|
-
if (!s)
|
|
65
|
-
return;
|
|
66
|
-
const k = s.replace(/\s+/g, ' ').trim().replace(/^["'`„“]|["'`„“]$/g, '').trim();
|
|
67
|
-
if (k.length < 8 || k.length > 160)
|
|
68
|
-
return;
|
|
69
|
-
if (aus.some((x) => x.toLowerCase() === k.toLowerCase()))
|
|
70
|
-
return;
|
|
71
|
-
aus.push(k);
|
|
72
|
-
};
|
|
73
|
-
const gross = /^[^a-zäöüß]{15,200}?(?=[.:]|\s[a-zäöüß])/.exec(t);
|
|
74
|
-
if (gross && /[A-ZÄÖÜ]{3,}/.test(gross[0]))
|
|
75
|
-
nimm(gross[0]);
|
|
76
|
-
for (const m of t.matchAll(/[„"'`]([^„"'`\n]{8,160})[“"'`]/g))
|
|
77
|
-
nimm(m[1]);
|
|
78
|
-
for (const m of t.matchAll(/\b((?:[A-Z][a-zA-Z]*(?:Error|Exception)|panic:|FATAL|ERROR|Traceback|exit(?:ed with)? (?:code )?\d+|HTTP [45]\d{2}|status [45]\d{2})[^.\n]{0,90})/g))
|
|
79
|
-
nimm(m[1]);
|
|
80
|
-
for (const m of t.matchAll(/\b(E[A-Z]{3,10}\b[^.\n]{0,60})/g))
|
|
81
|
-
nimm(m[1]);
|
|
82
|
-
for (const m of t.matchAll(/\b((?:no such|not found|permission denied|connection refused|no space left|too many|already exists|cannot|failed to|unable to)[^.,;\n]{0,70})/gi))
|
|
83
|
-
nimm(m[1]);
|
|
84
|
-
return aus.slice(0, hoechstens);
|
|
85
|
-
}
|
|
86
|
-
/** Der Text einer Lektion, aus dem Eingänge gezogen werden. */
|
|
87
|
-
export function eingangsText(lektion) {
|
|
88
|
-
const s = (v) => (typeof v === 'string' ? v : '');
|
|
89
|
-
return `${s(lektion.what_worked)}\n${s(lektion.what_failed)}`;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Schreibt die Eingänge einer Lektion — als EIN Hash, nicht als n Schlüssel.
|
|
93
|
-
*
|
|
94
|
-
* Warum ein Hash: die Eingänge gehören zusammen und werden zusammen gelesen.
|
|
95
|
-
* Drei Schlüssel je Lektion wären bei 2000 Lektionen 6000 Schlüssel im SCAN.
|
|
96
|
-
*
|
|
97
|
-
* Der Hash wird zuerst gelöscht: hat sich der Text der Lektion geändert, sind
|
|
98
|
-
* die alten Fehlertexte falsch. Eine Lektion neu zu lernen darf keine Reste
|
|
99
|
-
* hinterlassen — das wäre eine zweite Wahrheit über denselben Text.
|
|
100
|
-
*
|
|
101
|
-
* Gibt zurück, wie viele Eingänge geschrieben wurden.
|
|
102
|
-
*/
|
|
103
|
-
export async function schreibeEingaenge(redis, topic, lektion, einbetten) {
|
|
104
|
-
const texte = fehlertexteAus(eingangsText(lektion));
|
|
105
|
-
const schluessel = `${EINGANG_PRAEFIX}${topic}`;
|
|
106
|
-
if (texte.length === 0) {
|
|
107
|
-
// Kein Fehlertext in dieser Lektion — dann bekommt sie keinen Eingang.
|
|
108
|
-
// Eine leere Tür ist besser als eine erfundene.
|
|
109
|
-
await redis.del(schluessel);
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
112
|
-
const felder = [];
|
|
113
|
-
for (const text of texte) {
|
|
114
|
-
const v = await einbetten(text);
|
|
115
|
-
if (v?.length)
|
|
116
|
-
felder.push(text.slice(0, 200), packe(v));
|
|
117
|
-
}
|
|
118
|
-
if (felder.length === 0)
|
|
119
|
-
return 0;
|
|
120
|
-
await redis.del(schluessel);
|
|
121
|
-
await redis.hset(schluessel, ...felder);
|
|
122
|
-
return felder.length / 2;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Die Eingänge aller Lektionen im Arbeitsspeicher.
|
|
126
|
-
*
|
|
127
|
-
* Baugleich zu `Vektorbestand` in bedeutung.ts und aus demselben Grund: bei
|
|
128
|
-
* jeder Frage aus dem Speicher zu holen wäre langsamer als der Wortabgleich,
|
|
129
|
-
* den wir gerade ersetzen. Der MCP-Server läuft lange, also wird einmal geladen
|
|
130
|
-
* und danach nur nachgetragen.
|
|
131
|
-
*/
|
|
132
|
-
export class Eingangsbestand {
|
|
133
|
-
frischeMs;
|
|
134
|
-
eingaenge = new Map();
|
|
135
|
-
geladen = 0;
|
|
136
|
-
constructor(frischeMs = 60_000) {
|
|
137
|
-
this.frischeMs = frischeMs;
|
|
138
|
-
}
|
|
139
|
-
get groesse() { return this.eingaenge.size; }
|
|
140
|
-
get anzahlEingaenge() {
|
|
141
|
-
let n = 0;
|
|
142
|
-
for (const vs of this.eingaenge.values())
|
|
143
|
-
n += vs.length;
|
|
144
|
-
return n;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* @param praefixe welche Hashes zum Bestand gehoeren
|
|
148
|
-
*
|
|
149
|
-
* Zwei statt einem: die aus dem Text gezogenen Fehlertexte
|
|
150
|
-
* (`cachly:lesson:eing:`) und die aus der Nutzung gelernten Fragen
|
|
151
|
-
* (`cachly:lesson:pfad:`). Sie liegen getrennt, weil `schreibeEingaenge`
|
|
152
|
-
* seinen Hash vor jedem Schreiben loescht — laegen die gelernten Fragen
|
|
153
|
-
* dort, wuerde jedes erneute Lernen die gesammelte Erfahrung wegwerfen.
|
|
154
|
-
* Fuer die SUCHE sind sie dasselbe: Tueren zu derselben Lektion.
|
|
155
|
-
*/
|
|
156
|
-
async aktualisiere(redis, jetzt = Date.now(), praefixe = [EINGANG_PRAEFIX, 'cachly:lesson:pfad:']) {
|
|
157
|
-
if (this.eingaenge.size > 0 && jetzt - this.geladen < this.frischeMs)
|
|
158
|
-
return;
|
|
159
|
-
const frisch = new Map();
|
|
160
|
-
for (const praefix of praefixe) {
|
|
161
|
-
const schluessel = [];
|
|
162
|
-
let cursor = '0';
|
|
163
|
-
do {
|
|
164
|
-
const [next, gefunden] = await redis.scan(cursor, 'MATCH', `${praefix}*`, 'COUNT', 500);
|
|
165
|
-
cursor = next;
|
|
166
|
-
schluessel.push(...gefunden);
|
|
167
|
-
} while (cursor !== '0');
|
|
168
|
-
for (const k of schluessel) {
|
|
169
|
-
const hash = await redis.hgetall(k);
|
|
170
|
-
const topic = k.slice(praefix.length);
|
|
171
|
-
const vs = frisch.get(topic) ?? [];
|
|
172
|
-
for (const roh of Object.values(hash)) {
|
|
173
|
-
const v = entpacke(roh);
|
|
174
|
-
if (v)
|
|
175
|
-
vs.push(v);
|
|
176
|
-
}
|
|
177
|
-
if (vs.length)
|
|
178
|
-
frisch.set(topic, vs);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
this.eingaenge = frisch;
|
|
182
|
-
this.geladen = jetzt;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Die Nähe zum BESTEN Eingang einer Lektion. -2, wenn sie keinen hat.
|
|
186
|
-
*
|
|
187
|
-
* Maximum, nicht Mittelwert: eine Lektion wird über ihre passendste Tür
|
|
188
|
-
* gefunden, und die anderen dürfen schlecht passen. Der Mittelwert würde
|
|
189
|
-
* Lektionen mit vielen Türen bestrafen — gemessen und belegt am 20.08.
|
|
190
|
-
*/
|
|
191
|
-
besteNaehe(frage, topic) {
|
|
192
|
-
const vs = this.eingaenge.get(topic);
|
|
193
|
-
if (!vs?.length)
|
|
194
|
-
return -2;
|
|
195
|
-
let best = -2;
|
|
196
|
-
for (const v of vs) {
|
|
197
|
-
const k = kosinus(frage, v);
|
|
198
|
-
if (k > best)
|
|
199
|
-
best = k;
|
|
200
|
-
}
|
|
201
|
-
return best;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Die MITTLERE Nähe über alle Eingänge einer Lektion. -2, wenn sie keine hat.
|
|
205
|
-
*
|
|
206
|
-
* Sie ersetzt `besteNaehe` nicht, sie steht daneben. Am 21.08.2026 wurde
|
|
207
|
-
* "Mittelwert STATT Maximum" gemessen und verworfen — er verlor auf der
|
|
208
|
-
* Findequote@3 (55 und 62 Prozent gegen 58 und 63).
|
|
209
|
-
*
|
|
210
|
-
* Dieselbe Messung zeigte aber, dass die beiden ENTGEGENGESETZT ziehen: das
|
|
211
|
-
* Maximum gewinnt auf @3, der Mittelwert auf Platz 1, gleichgerichtet auf
|
|
212
|
-
* zwei unabhängigen Fragensätzen. Sie messen Verschiedenes.
|
|
213
|
-
*
|
|
214
|
-
* Maximum "gibt es EINE Tür, die genau auf diese Frage passt?"
|
|
215
|
-
* Mittelwert "passt die Lektion als GANZE zu dieser Frage?"
|
|
216
|
-
*
|
|
217
|
-
* Das Maximum allein belohnt eine Lektion, die zufällig eine gut passende
|
|
218
|
-
* Tür trägt und sonst am Thema vorbeigeht. Der Mittelwert allein bestraft
|
|
219
|
-
* eine breit aufgestellte Lektion, die nur mit einer Tür trifft. Nebeneinander
|
|
220
|
-
* heben sich die beiden Fehler teilweise auf.
|
|
221
|
-
*
|
|
222
|
-
* Ohne Schwelle, anders als das Maximum: ein Mittelwert liegt naturgemäß
|
|
223
|
-
* niedriger, und die für das Maximum abgetastete 0,5 würde ihn fast überall
|
|
224
|
-
* auf "kein Wert" drücken — das Merkmal wäre still abgeschaltet.
|
|
225
|
-
*/
|
|
226
|
-
mittelNaehe(frage, topic) {
|
|
227
|
-
const vs = this.eingaenge.get(topic);
|
|
228
|
-
if (!vs?.length)
|
|
229
|
-
return -2;
|
|
230
|
-
let summe = 0;
|
|
231
|
-
for (const v of vs)
|
|
232
|
-
summe += kosinus(frage, v);
|
|
233
|
-
return summe / vs.length;
|
|
234
|
-
}
|
|
235
|
-
/** Die ähnlichsten Lektionen, je Lektion ihr bester Eingang. */
|
|
236
|
-
aehnlichste(frage, anzahl) {
|
|
237
|
-
const aus = [];
|
|
238
|
-
for (const topic of this.eingaenge.keys()) {
|
|
239
|
-
aus.push({ topic, naehe: this.besteNaehe(frage, topic) });
|
|
240
|
-
}
|
|
241
|
-
aus.sort((a, b) => b.naehe - a.naehe);
|
|
242
|
-
return aus.slice(0, anzahl);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Das Gewicht des Merkmals "bester Eingang" im Sortierer.
|
|
247
|
-
*
|
|
248
|
-
* Gemessen am 20.08.2026: 0,3 / 0,5 / 0,8 / 1,3 ergeben 57 / 58 / 57 / 57 %
|
|
249
|
-
* Findequote@3. Das Merkmal ist also unempfindlich gegen sein Gewicht — wer
|
|
250
|
-
* hier weiterdreht, misst Rauschen. 0,5 ist die Mitte des flachen Bereichs.
|
|
251
|
-
*/
|
|
252
|
-
export const EINGANG_GEWICHT = 0.5;
|
|
253
|
-
export function beurteileDeckung(lektionen, vektoren) {
|
|
254
|
-
if (lektionen === 0)
|
|
255
|
-
return 'gut'; // Ein leeres Brain ist nicht krank.
|
|
256
|
-
if (vektoren === 0)
|
|
257
|
-
return 'aus';
|
|
258
|
-
return (vektoren / lektionen) * 100 < 90 ? 'luecke' : 'gut';
|
|
259
|
-
}
|
|
260
|
-
//# sourceMappingURL=eingaenge.js.map
|
|
1
|
+
import{kosinus as u,packe as m,entpacke as x}from"./bedeutung.js";const p="cachly:lesson:eing:",w=3;function A(c){return c.replace(/<\/?(what_worked|what_failed|context|topic)>/g," ").replace(/\s+/g," ").trim()}function E(c,t=w){const n=A(c),o=[],e=s=>{if(!s)return;const a=s.replace(/\s+/g," ").trim().replace(/^["'`„“]|["'`„“]$/g,"").trim();a.length<8||a.length>160||o.some(i=>i.toLowerCase()===a.toLowerCase())||o.push(a)},r=/^[^a-zäöüß]{15,200}?(?=[.:]|\s[a-zäöüß])/.exec(n);r&&/[A-ZÄÖÜ]{3,}/.test(r[0])&&e(r[0]);for(const s of n.matchAll(/[„"'`]([^„"'`\n]{8,160})[“"'`]/g))e(s[1]);for(const s of n.matchAll(/\b((?:[A-Z][a-zA-Z]*(?:Error|Exception)|panic:|FATAL|ERROR|Traceback|exit(?:ed with)? (?:code )?\d+|HTTP [45]\d{2}|status [45]\d{2})[^.\n]{0,90})/g))e(s[1]);for(const s of n.matchAll(/\b(E[A-Z]{3,10}\b[^.\n]{0,60})/g))e(s[1]);for(const s of n.matchAll(/\b((?:no such|not found|permission denied|connection refused|no space left|too many|already exists|cannot|failed to|unable to)[^.,;\n]{0,70})/gi))e(s[1]);return o.slice(0,t)}function b(c){const t=n=>typeof n=="string"?n:"";return`${t(c.what_worked)}
|
|
2
|
+
${t(c.what_failed)}`}async function N(c,t,n,o){const e=E(b(n)),r=`${p}${t}`;if(e.length===0)return await c.del(r),0;const s=[];for(const a of e){const i=await o(a);i?.length&&s.push(a.slice(0,200),m(i))}return s.length===0?0:(await c.del(r),await c.hset(r,...s),s.length/2)}class T{frischeMs;eingaenge=new Map;geladen=0;constructor(t=6e4){this.frischeMs=t}get groesse(){return this.eingaenge.size}get anzahlEingaenge(){let t=0;for(const n of this.eingaenge.values())t+=n.length;return t}async aktualisiere(t,n=Date.now(),o=[p,"cachly:lesson:pfad:"]){if(this.eingaenge.size>0&&n-this.geladen<this.frischeMs)return;const e=new Map;for(const r of o){const s=[];let a="0";do{const[i,h]=await t.scan(a,"MATCH",`${r}*`,"COUNT",500);a=i,s.push(...h)}while(a!=="0");for(const i of s){const h=await t.hgetall(i),g=i.slice(r.length),l=e.get(g)??[];for(const d of Object.values(h)){const f=x(d);f&&l.push(f)}l.length&&e.set(g,l)}}this.eingaenge=e,this.geladen=n}besteNaehe(t,n){const o=this.eingaenge.get(n);if(!o?.length)return-2;let e=-2;for(const r of o){const s=u(t,r);s>e&&(e=s)}return e}mittelNaehe(t,n){const o=this.eingaenge.get(n);if(!o?.length)return-2;let e=0;for(const r of o)e+=u(t,r);return e/o.length}aehnlichste(t,n){const o=[];for(const e of this.eingaenge.keys())o.push({topic:e,naehe:this.besteNaehe(t,e)});return o.sort((e,r)=>r.naehe-e.naehe),o.slice(0,n)}}const _=.5;function v(c,t){return c===0?"gut":t===0?"aus":t/c*100<90?"luecke":"gut"}export{w as EINGAENGE_JE_LEKTION,_ as EINGANG_GEWICHT,p as EINGANG_PRAEFIX,T as Eingangsbestand,v as beurteileDeckung,b as eingangsText,E as fehlertexteAus,A as ohneMarkierungen,N as schreibeEingaenge};
|