Spaces:
Running
Running
Download engine.js from KRLabsOrg/rulechef-live: direct link, hf CLI and curl.
- Browser
- Download file 3.33 kB
-
https://huggingface.co/spaces/KRLabsOrg/rulechef-live/resolve/main/engine.js
- Command line
-
hf download hf://spaces/KRLabsOrg/rulechef-live/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/KRLabsOrg/rulechef-live/resolve/main/engine.js
3.33 kB
| // Browser port of cascade.py: RuleChef's executor semantics for regex NER rules, and the gate. | |
| // Checked against the Python executor on every request and every rule set of this demo (see verify_engine.js). | |
| (function (root) { | |
| "use strict"; | |
| const cache = new Map(); | |
| function compile(src) { | |
| if (!cache.has(src)) { | |
| let re = null; | |
| try { re = new RegExp(src, "g"); } catch (e) { re = null; } | |
| cache.set(src, re); | |
| } | |
| return cache.get(src); | |
| } | |
| function sortKey(a, b) { | |
| const pa = a.priority ?? 5, pb = b.priority ?? 5; | |
| if (pa !== pb) return pb - pa; | |
| const qa = a.validated_precision ?? a.confidence ?? 0.5, qb = b.validated_precision ?? b.confidence ?? 0.5; | |
| if (qa !== qb) return qb - qa; | |
| const na = (a.name || "").toLowerCase(), nb = (b.name || "").toLowerCase(); | |
| if (na !== nb) return na < nb ? -1 : 1; | |
| const ia = a.id || "", ib = b.id || ""; | |
| return ia < ib ? -1 : ia > ib ? 1 : 0; | |
| } | |
| // rules: RuleChef rule dicts (content, priority, output_template {text, type}, ...) | |
| function applyRules(rules, text) { | |
| const out = []; | |
| for (const r of rules.slice().sort(sortKey)) { | |
| if ((r.format || "regex") !== "regex") continue; | |
| const re = compile(r.content); | |
| if (!re) continue; | |
| const tpl = r.output_template || {}; | |
| const t = tpl.text || "$0"; | |
| re.lastIndex = 0; | |
| for (const m of text.matchAll(re)) { | |
| const mt = m[0]; | |
| let s = m.index, e = m.index + mt.length, val = mt; | |
| if (/^\$\d+$/.test(t)) { | |
| const g = parseInt(t.slice(1), 10); | |
| val = g === 0 ? mt : (m[g] ?? ""); | |
| } | |
| if (val && val !== mt) { | |
| const off = mt.toLowerCase().indexOf(val.toLowerCase()); | |
| if (off >= 0) { s = s + off; e = s + val.length; } | |
| } | |
| if (out.some((x) => x.start === s && x.end === e)) continue; | |
| out.push({ start: s, end: e, type: tpl.type, text: val, rule_id: r.id }); | |
| } | |
| } | |
| const uniq = []; | |
| for (const it of out) { | |
| const dup = uniq.some((ex) => { | |
| const inter = Math.max(0, Math.min(it.end, ex.end) - Math.max(it.start, ex.start)); | |
| const uni = (it.end - it.start) + (ex.end - ex.start) - inter; | |
| return uni > 0 && inter / uni > 0.7 && it.type === ex.type; | |
| }); | |
| if (!dup) uniq.push(it); | |
| } | |
| return uniq; | |
| } | |
| const TOK = /§|[A-Za-z]+|\d+(?:[.,\/:-]\d+)*/g; | |
| function tokens(text) { | |
| const out = []; let prev = ""; | |
| for (const m of text.matchAll(TOK)) { out.push([m.index, m.index + m[0].length, m[0], prev]); prev = m[0].toLowerCase(); } | |
| return out; | |
| } | |
| const shape = (tok) => tok.replace(/\d+/g, "9"); | |
| // gate: {trigger_words: [...], skip: [[prev, shape], ...]} | |
| function unexplained(gate, text, spans) { | |
| const words = new Set(gate.trigger_words), skip = new Set(gate.skip.map((k) => k[0] + "\u0000" + k[1])); | |
| const out = []; | |
| for (const [s, e, tok, prev] of tokens(text)) { | |
| const digit = /^\d/.test(tok); | |
| if (!(digit || words.has(tok))) continue; | |
| if (spans.some((sp) => sp.start < e && s < sp.end)) continue; | |
| if (digit && skip.has(prev + "\u0000" + shape(tok))) continue; | |
| out.push(tok); | |
| } | |
| return out; | |
| } | |
| root.RCEngine = { applyRules, unexplained, compile }; | |
| })(typeof window !== "undefined" ? window : globalThis); | |