agent-web-interface 4.3.0 → 4.4.0
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/dist/src/browser/connection-utils.d.ts +48 -0
- package/dist/src/browser/connection-utils.d.ts.map +1 -0
- package/dist/src/browser/connection-utils.js +129 -0
- package/dist/src/browser/connection-utils.js.map +1 -0
- package/dist/src/browser/index.d.ts +3 -1
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +2 -1
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/session-manager.d.ts +1 -89
- package/dist/src/browser/session-manager.d.ts.map +1 -1
- package/dist/src/browser/session-manager.js +1 -116
- package/dist/src/browser/session-manager.js.map +1 -1
- package/dist/src/browser/session-manager.types.d.ts +90 -0
- package/dist/src/browser/session-manager.types.d.ts.map +1 -0
- package/dist/src/browser/session-manager.types.js +7 -0
- package/dist/src/browser/session-manager.types.js.map +1 -0
- package/dist/src/form/constraint-extraction.d.ts +31 -0
- package/dist/src/form/constraint-extraction.d.ts.map +1 -0
- package/dist/src/form/constraint-extraction.js +110 -0
- package/dist/src/form/constraint-extraction.js.map +1 -0
- package/dist/src/form/field-extractor.d.ts.map +1 -1
- package/dist/src/form/field-extractor.js +3 -444
- package/dist/src/form/field-extractor.js.map +1 -1
- package/dist/src/form/field-state-extractor.d.ts +22 -0
- package/dist/src/form/field-state-extractor.d.ts.map +1 -0
- package/dist/src/form/field-state-extractor.js +55 -0
- package/dist/src/form/field-state-extractor.js.map +1 -0
- package/dist/src/form/form-actions.d.ts +45 -0
- package/dist/src/form/form-actions.d.ts.map +1 -0
- package/dist/src/form/form-actions.js +108 -0
- package/dist/src/form/form-actions.js.map +1 -0
- package/dist/src/form/form-detector.d.ts +0 -36
- package/dist/src/form/form-detector.d.ts.map +1 -1
- package/dist/src/form/form-detector.js +11 -376
- package/dist/src/form/form-detector.js.map +1 -1
- package/dist/src/form/input-clustering.d.ts +15 -0
- package/dist/src/form/input-clustering.d.ts.map +1 -0
- package/dist/src/form/input-clustering.js +61 -0
- package/dist/src/form/input-clustering.js.map +1 -0
- package/dist/src/form/intent-inference.d.ts +28 -0
- package/dist/src/form/intent-inference.d.ts.map +1 -0
- package/dist/src/form/intent-inference.js +137 -0
- package/dist/src/form/intent-inference.js.map +1 -0
- package/dist/src/form/purpose-inference.d.ts +50 -0
- package/dist/src/form/purpose-inference.d.ts.map +1 -0
- package/dist/src/form/purpose-inference.js +313 -0
- package/dist/src/form/purpose-inference.js.map +1 -0
- package/dist/src/form/submit-detection.d.ts +36 -0
- package/dist/src/form/submit-detection.d.ts.map +1 -0
- package/dist/src/form/submit-detection.js +101 -0
- package/dist/src/form/submit-detection.js.map +1 -0
- package/dist/src/form/types.d.ts +2 -2
- package/dist/src/index.js +52 -47
- package/dist/src/index.js.map +1 -1
- package/dist/src/observation/observation-accumulator.d.ts +1 -1
- package/dist/src/observation/observation-accumulator.js +1 -1
- package/dist/src/observation/observer-script.d.ts +1 -1
- package/dist/src/observation/observer-script.d.ts.map +1 -1
- package/dist/src/observation/observer-script.js +129 -7
- package/dist/src/observation/observer-script.js.map +1 -1
- package/dist/src/query/disambiguation.d.ts +18 -0
- package/dist/src/query/disambiguation.d.ts.map +1 -0
- package/dist/src/query/disambiguation.js +123 -0
- package/dist/src/query/disambiguation.js.map +1 -0
- package/dist/src/query/fuzzy-match.d.ts +17 -0
- package/dist/src/query/fuzzy-match.d.ts.map +1 -0
- package/dist/src/query/fuzzy-match.js +34 -0
- package/dist/src/query/fuzzy-match.js.map +1 -0
- package/dist/src/query/index.d.ts +3 -0
- package/dist/src/query/index.d.ts.map +1 -1
- package/dist/src/query/index.js +6 -0
- package/dist/src/query/index.js.map +1 -1
- package/dist/src/query/query-engine.d.ts +0 -35
- package/dist/src/query/query-engine.d.ts.map +1 -1
- package/dist/src/query/query-engine.js +9 -309
- package/dist/src/query/query-engine.js.map +1 -1
- package/dist/src/query/scoring.d.ts +52 -0
- package/dist/src/query/scoring.d.ts.map +1 -0
- package/dist/src/query/scoring.js +162 -0
- package/dist/src/query/scoring.js.map +1 -0
- package/dist/src/snapshot/element-resolver.d.ts +24 -13
- package/dist/src/snapshot/element-resolver.d.ts.map +1 -1
- package/dist/src/snapshot/element-resolver.js +117 -87
- package/dist/src/snapshot/element-resolver.js.map +1 -1
- package/dist/src/snapshot/extractors/ax-extractor.d.ts +1 -1
- package/dist/src/snapshot/extractors/ax-extractor.d.ts.map +1 -1
- package/dist/src/snapshot/extractors/ax-extractor.js +4 -1
- package/dist/src/snapshot/extractors/ax-extractor.js.map +1 -1
- package/dist/src/snapshot/extractors/index.d.ts +1 -1
- package/dist/src/snapshot/extractors/index.d.ts.map +1 -1
- package/dist/src/snapshot/extractors/index.js +1 -1
- package/dist/src/snapshot/extractors/index.js.map +1 -1
- package/dist/src/snapshot/extractors/region-resolver.d.ts.map +1 -1
- package/dist/src/snapshot/extractors/region-resolver.js +8 -0
- package/dist/src/snapshot/extractors/region-resolver.js.map +1 -1
- package/dist/src/snapshot/extractors/types.d.ts +8 -0
- package/dist/src/snapshot/extractors/types.d.ts.map +1 -1
- package/dist/src/snapshot/extractors/types.js +16 -0
- package/dist/src/snapshot/extractors/types.js.map +1 -1
- package/dist/src/snapshot/frame-context.d.ts +68 -0
- package/dist/src/snapshot/frame-context.d.ts.map +1 -0
- package/dist/src/snapshot/frame-context.js +131 -0
- package/dist/src/snapshot/frame-context.js.map +1 -0
- package/dist/src/snapshot/heading-index.d.ts +28 -0
- package/dist/src/snapshot/heading-index.d.ts.map +1 -0
- package/dist/src/snapshot/heading-index.js +108 -0
- package/dist/src/snapshot/heading-index.js.map +1 -0
- package/dist/src/snapshot/index.d.ts +5 -3
- package/dist/src/snapshot/index.d.ts.map +1 -1
- package/dist/src/snapshot/index.js +3 -2
- package/dist/src/snapshot/index.js.map +1 -1
- package/dist/src/snapshot/kind-mapping.d.ts +30 -0
- package/dist/src/snapshot/kind-mapping.d.ts.map +1 -0
- package/dist/src/snapshot/kind-mapping.js +114 -0
- package/dist/src/snapshot/kind-mapping.js.map +1 -0
- package/dist/src/snapshot/node-filter.d.ts +31 -0
- package/dist/src/snapshot/node-filter.d.ts.map +1 -0
- package/dist/src/snapshot/node-filter.js +137 -0
- package/dist/src/snapshot/node-filter.js.map +1 -0
- package/dist/src/snapshot/node-synthesizer.d.ts +62 -0
- package/dist/src/snapshot/node-synthesizer.d.ts.map +1 -0
- package/dist/src/snapshot/node-synthesizer.js +185 -0
- package/dist/src/snapshot/node-synthesizer.js.map +1 -0
- package/dist/src/snapshot/snapshot-compiler.d.ts +2 -36
- package/dist/src/snapshot/snapshot-compiler.d.ts.map +1 -1
- package/dist/src/snapshot/snapshot-compiler.js +25 -547
- package/dist/src/snapshot/snapshot-compiler.js.map +1 -1
- package/dist/src/snapshot/snapshot.types.d.ts +7 -2
- package/dist/src/snapshot/snapshot.types.d.ts.map +1 -1
- package/dist/src/snapshot/snapshot.types.js +8 -0
- package/dist/src/snapshot/snapshot.types.js.map +1 -1
- package/dist/src/state/actionables-filter.d.ts +5 -0
- package/dist/src/state/actionables-filter.d.ts.map +1 -1
- package/dist/src/state/actionables-filter.js +21 -3
- package/dist/src/state/actionables-filter.js.map +1 -1
- package/dist/src/state/diff-engine.js +3 -3
- package/dist/src/state/diff-engine.js.map +1 -1
- package/dist/src/state/element-registry.d.ts.map +1 -1
- package/dist/src/state/element-registry.js +6 -4
- package/dist/src/state/element-registry.js.map +1 -1
- package/dist/src/state/hash-utils.d.ts +24 -0
- package/dist/src/state/hash-utils.d.ts.map +1 -0
- package/dist/src/state/hash-utils.js +41 -0
- package/dist/src/state/hash-utils.js.map +1 -0
- package/dist/src/state/layer-detector.d.ts.map +1 -1
- package/dist/src/state/layer-detector.js +15 -286
- package/dist/src/state/layer-detector.js.map +1 -1
- package/dist/src/state/layer-detectors/drawer-detector.d.ts +32 -0
- package/dist/src/state/layer-detectors/drawer-detector.d.ts.map +1 -0
- package/dist/src/state/layer-detectors/drawer-detector.js +96 -0
- package/dist/src/state/layer-detectors/drawer-detector.js.map +1 -0
- package/dist/src/state/layer-detectors/index.d.ts +10 -0
- package/dist/src/state/layer-detectors/index.d.ts.map +1 -0
- package/dist/src/state/layer-detectors/index.js +10 -0
- package/dist/src/state/layer-detectors/index.js.map +1 -0
- package/dist/src/state/layer-detectors/modal-detector.d.ts +30 -0
- package/dist/src/state/layer-detectors/modal-detector.d.ts.map +1 -0
- package/dist/src/state/layer-detectors/modal-detector.js +127 -0
- package/dist/src/state/layer-detectors/modal-detector.js.map +1 -0
- package/dist/src/state/layer-detectors/popover-detector.d.ts +20 -0
- package/dist/src/state/layer-detectors/popover-detector.d.ts.map +1 -0
- package/dist/src/state/layer-detectors/popover-detector.js +76 -0
- package/dist/src/state/layer-detectors/popover-detector.js.map +1 -0
- package/dist/src/state/layer-detectors/toast-detector.d.ts +24 -0
- package/dist/src/state/layer-detectors/toast-detector.d.ts.map +1 -0
- package/dist/src/state/layer-detectors/toast-detector.js +48 -0
- package/dist/src/state/layer-detectors/toast-detector.js.map +1 -0
- package/dist/src/state/region-mapping.d.ts +13 -0
- package/dist/src/state/region-mapping.d.ts.map +1 -0
- package/dist/src/state/region-mapping.js +25 -0
- package/dist/src/state/region-mapping.js.map +1 -0
- package/dist/src/state/state-manager.d.ts.map +1 -1
- package/dist/src/state/state-manager.js +8 -192
- package/dist/src/state/state-manager.js.map +1 -1
- package/dist/src/state/state-renderer.d.ts.map +1 -1
- package/dist/src/state/state-renderer.js +14 -2
- package/dist/src/state/state-renderer.js.map +1 -1
- package/dist/src/state/types.d.ts +8 -4
- package/dist/src/state/types.d.ts.map +1 -1
- package/dist/src/state/url-sanitization.d.ts +22 -0
- package/dist/src/state/url-sanitization.d.ts.map +1 -0
- package/dist/src/state/url-sanitization.js +60 -0
- package/dist/src/state/url-sanitization.js.map +1 -0
- package/dist/src/state/value-masking.d.ts +36 -0
- package/dist/src/state/value-masking.d.ts.map +1 -0
- package/dist/src/state/value-masking.js +86 -0
- package/dist/src/state/value-masking.js.map +1 -0
- package/dist/src/tools/action-context.d.ts +60 -0
- package/dist/src/tools/action-context.d.ts.map +1 -0
- package/dist/src/tools/action-context.js +78 -0
- package/dist/src/tools/action-context.js.map +1 -0
- package/dist/src/tools/action-stabilization.d.ts +48 -0
- package/dist/src/tools/action-stabilization.d.ts.map +1 -0
- package/dist/src/tools/action-stabilization.js +87 -0
- package/dist/src/tools/action-stabilization.js.map +1 -0
- package/dist/src/tools/browser-tools.d.ts +8 -167
- package/dist/src/tools/browser-tools.d.ts.map +1 -1
- package/dist/src/tools/browser-tools.js +13 -651
- package/dist/src/tools/browser-tools.js.map +1 -1
- package/dist/src/tools/effect-tracker.d.ts +25 -0
- package/dist/src/tools/effect-tracker.d.ts.map +1 -0
- package/dist/src/tools/effect-tracker.js +69 -0
- package/dist/src/tools/effect-tracker.js.map +1 -0
- package/dist/src/tools/execute-action.d.ts +1 -31
- package/dist/src/tools/execute-action.d.ts.map +1 -1
- package/dist/src/tools/execute-action.js +7 -276
- package/dist/src/tools/execute-action.js.map +1 -1
- package/dist/src/tools/form-tools.d.ts +2 -2
- package/dist/src/tools/form-tools.js +4 -4
- package/dist/src/tools/form-tools.js.map +1 -1
- package/dist/src/tools/index.d.ts +2 -2
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +10 -8
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/interaction-tools.d.ts +46 -0
- package/dist/src/tools/interaction-tools.d.ts.map +1 -0
- package/dist/src/tools/interaction-tools.js +138 -0
- package/dist/src/tools/interaction-tools.js.map +1 -0
- package/dist/src/tools/navigation-detection.d.ts +31 -0
- package/dist/src/tools/navigation-detection.d.ts.map +1 -0
- package/dist/src/tools/navigation-detection.js +46 -0
- package/dist/src/tools/navigation-detection.js.map +1 -0
- package/dist/src/tools/navigation-tools.d.ts +57 -0
- package/dist/src/tools/navigation-tools.d.ts.map +1 -0
- package/dist/src/tools/navigation-tools.js +178 -0
- package/dist/src/tools/navigation-tools.js.map +1 -0
- package/dist/src/tools/observation-tools.d.ts +53 -0
- package/dist/src/tools/observation-tools.d.ts.map +1 -0
- package/dist/src/tools/observation-tools.js +247 -0
- package/dist/src/tools/observation-tools.js.map +1 -0
- package/dist/src/tools/response-builder.js +2 -2
- package/dist/src/tools/response-builder.js.map +1 -1
- package/dist/src/tools/stale-element-retry.d.ts +37 -0
- package/dist/src/tools/stale-element-retry.d.ts.map +1 -0
- package/dist/src/tools/stale-element-retry.js +68 -0
- package/dist/src/tools/stale-element-retry.js.map +1 -0
- package/dist/src/tools/state-manager-registry.d.ts +26 -0
- package/dist/src/tools/state-manager-registry.d.ts.map +1 -0
- package/dist/src/tools/state-manager-registry.js +39 -0
- package/dist/src/tools/state-manager-registry.js.map +1 -0
- package/dist/src/tools/tool-context.js +1 -1
- package/dist/src/tools/tool-context.js.map +1 -1
- package/dist/src/tools/tool-schemas.d.ts +106 -21
- package/dist/src/tools/tool-schemas.d.ts.map +1 -1
- package/dist/src/tools/tool-schemas.js +59 -18
- package/dist/src/tools/tool-schemas.js.map +1 -1
- package/dist/src/tools/viewport-tools.d.ts +36 -0
- package/dist/src/tools/viewport-tools.d.ts.map +1 -0
- package/dist/src/tools/viewport-tools.js +105 -0
- package/dist/src/tools/viewport-tools.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disambiguation
|
|
3
|
+
*
|
|
4
|
+
* Generate suggestions to narrow ambiguous query results.
|
|
5
|
+
* Analyzes matched nodes and suggests refinements.
|
|
6
|
+
*/
|
|
7
|
+
import { normalizeText } from '../lib/text-utils.js';
|
|
8
|
+
import { normalizeLabelFilter } from './scoring.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generate disambiguation suggestions when query matches multiple elements.
|
|
11
|
+
* Suggests refinements that would narrow down the results.
|
|
12
|
+
*/
|
|
13
|
+
export function generateSuggestions(matches, request) {
|
|
14
|
+
const suggestions = [];
|
|
15
|
+
const nodes = matches.map((m) => m.node);
|
|
16
|
+
// Only generate suggestions if we have multiple matches
|
|
17
|
+
if (matches.length < 2)
|
|
18
|
+
return suggestions;
|
|
19
|
+
// 1. Suggest refining by kind if matches have different kinds
|
|
20
|
+
if (request.kind === undefined) {
|
|
21
|
+
const kindCounts = countByAttribute(nodes, (n) => n.kind);
|
|
22
|
+
if (kindCounts.size > 1) {
|
|
23
|
+
for (const [kind, count] of kindCounts) {
|
|
24
|
+
if (count < matches.length) {
|
|
25
|
+
suggestions.push({
|
|
26
|
+
type: 'refine_kind',
|
|
27
|
+
message: `Add kind: "${kind}" to narrow to ${count} result(s)`,
|
|
28
|
+
refinement: { kind },
|
|
29
|
+
expected_matches: count,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// 2. Suggest refining by region if matches span multiple regions
|
|
36
|
+
if (request.region === undefined) {
|
|
37
|
+
const regionCounts = countByAttribute(nodes, (n) => n.where.region);
|
|
38
|
+
if (regionCounts.size > 1) {
|
|
39
|
+
for (const [region, count] of regionCounts) {
|
|
40
|
+
if (count < matches.length && region !== 'unknown') {
|
|
41
|
+
suggestions.push({
|
|
42
|
+
type: 'refine_region',
|
|
43
|
+
message: `Add region: "${region}" to narrow to ${count} result(s)`,
|
|
44
|
+
refinement: { region },
|
|
45
|
+
expected_matches: count,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// 3. Suggest refining by group_id if matches have different groups
|
|
52
|
+
if (request.group_id === undefined) {
|
|
53
|
+
const groupCounts = countByAttribute(nodes, (n) => n.where.group_id);
|
|
54
|
+
groupCounts.delete(undefined); // Remove nodes without groups
|
|
55
|
+
if (groupCounts.size >= 1) {
|
|
56
|
+
for (const [groupId, count] of groupCounts) {
|
|
57
|
+
if (groupId !== undefined) {
|
|
58
|
+
suggestions.push({
|
|
59
|
+
type: 'refine_group',
|
|
60
|
+
message: `Add group_id: "${groupId}" to narrow to ${count} result(s)`,
|
|
61
|
+
refinement: { group_id: groupId },
|
|
62
|
+
expected_matches: count,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 4. Suggest adding state filters
|
|
69
|
+
if (request.state === undefined) {
|
|
70
|
+
const enabledCount = nodes.filter((n) => n.state?.enabled).length;
|
|
71
|
+
if (enabledCount > 0 && enabledCount < matches.length) {
|
|
72
|
+
suggestions.push({
|
|
73
|
+
type: 'add_state',
|
|
74
|
+
message: `Add state: { enabled: true } to narrow to ${enabledCount} result(s)`,
|
|
75
|
+
refinement: { state: { enabled: true } },
|
|
76
|
+
expected_matches: enabledCount,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const visibleCount = nodes.filter((n) => n.state?.visible).length;
|
|
80
|
+
if (visibleCount > 0 && visibleCount < matches.length) {
|
|
81
|
+
suggestions.push({
|
|
82
|
+
type: 'add_state',
|
|
83
|
+
message: `Add state: { visible: true } to narrow to ${visibleCount} result(s)`,
|
|
84
|
+
refinement: { state: { visible: true } },
|
|
85
|
+
expected_matches: visibleCount,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 5. Suggest refining label to exact match if using contains/fuzzy
|
|
90
|
+
if (request.label !== undefined) {
|
|
91
|
+
const { mode, text } = normalizeLabelFilter(request.label);
|
|
92
|
+
if (mode !== 'exact') {
|
|
93
|
+
const normalizedText = normalizeText(text.toLowerCase());
|
|
94
|
+
const exactCount = nodes.filter((n) => normalizeText(n.label.toLowerCase()) === normalizedText).length;
|
|
95
|
+
if (exactCount > 0 && exactCount < matches.length) {
|
|
96
|
+
suggestions.push({
|
|
97
|
+
type: 'refine_label',
|
|
98
|
+
message: `Use exact label match to narrow to ${exactCount} result(s)`,
|
|
99
|
+
refinement: { label: { text, mode: 'exact' } },
|
|
100
|
+
expected_matches: exactCount,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Sort by expected_matches (prefer suggestions that narrow most effectively)
|
|
106
|
+
// and limit to top 5
|
|
107
|
+
return suggestions
|
|
108
|
+
.filter((s) => s.expected_matches > 0 && s.expected_matches < matches.length)
|
|
109
|
+
.sort((a, b) => a.expected_matches - b.expected_matches)
|
|
110
|
+
.slice(0, 5);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Count nodes by a given attribute.
|
|
114
|
+
*/
|
|
115
|
+
export function countByAttribute(nodes, getter) {
|
|
116
|
+
const counts = new Map();
|
|
117
|
+
for (const node of nodes) {
|
|
118
|
+
const value = getter(node);
|
|
119
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
120
|
+
}
|
|
121
|
+
return counts;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=disambiguation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disambiguation.js","sourceRoot":"","sources":["../../../src/query/disambiguation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy Match
|
|
3
|
+
*
|
|
4
|
+
* Fuzzy label matching for query engine.
|
|
5
|
+
* Uses token-based matching with configurable thresholds.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReadableNode } from '../snapshot/snapshot.types.js';
|
|
8
|
+
import type { FuzzyMatchOptions } from './types/query.types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Filter nodes by fuzzy label matching.
|
|
11
|
+
* Returns matched nodes and a map of node_id -> match score for relevance calculation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function filterByLabelFuzzy(nodes: ReadableNode[], text: string, caseSensitive: boolean, options?: FuzzyMatchOptions): {
|
|
14
|
+
nodes: ReadableNode[];
|
|
15
|
+
scores: Map<string, number>;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=fuzzy-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuzzy-match.d.ts","sourceRoot":"","sources":["../../../src/query/fuzzy-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,OAAO,EACtB,OAAO,GAAE,iBAAsB,GAC9B;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CA0BxD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fuzzy Match
|
|
3
|
+
*
|
|
4
|
+
* Fuzzy label matching for query engine.
|
|
5
|
+
* Uses token-based matching with configurable thresholds.
|
|
6
|
+
*/
|
|
7
|
+
import { normalizeText, tokenizeForMatching, fuzzyTokensMatch } from '../lib/text-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Filter nodes by fuzzy label matching.
|
|
10
|
+
* Returns matched nodes and a map of node_id -> match score for relevance calculation.
|
|
11
|
+
*/
|
|
12
|
+
export function filterByLabelFuzzy(nodes, text, caseSensitive, options = {}) {
|
|
13
|
+
const scores = new Map();
|
|
14
|
+
const normalizedQuery = normalizeText(caseSensitive ? text : text.toLowerCase());
|
|
15
|
+
const queryTokens = tokenizeForMatching(normalizedQuery, 10, 2);
|
|
16
|
+
if (queryTokens.length === 0) {
|
|
17
|
+
return { nodes: [], scores };
|
|
18
|
+
}
|
|
19
|
+
const matched = nodes.filter((n) => {
|
|
20
|
+
const normalizedLabel = normalizeText(caseSensitive ? n.label : n.label.toLowerCase());
|
|
21
|
+
const labelTokens = tokenizeForMatching(normalizedLabel, 10, 2);
|
|
22
|
+
const result = fuzzyTokensMatch(labelTokens, queryTokens, {
|
|
23
|
+
minTokenOverlap: options.minTokenOverlap ?? 0.5,
|
|
24
|
+
prefixMatch: options.prefixMatch ?? true,
|
|
25
|
+
minSimilarity: options.minSimilarity ?? 0.8,
|
|
26
|
+
});
|
|
27
|
+
if (result.isMatch) {
|
|
28
|
+
scores.set(n.node_id, result.score);
|
|
29
|
+
}
|
|
30
|
+
return result.isMatch;
|
|
31
|
+
});
|
|
32
|
+
return { nodes: matched, scores };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=fuzzy-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuzzy-match.js","sourceRoot":"","sources":["../../../src/query/fuzzy-match.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE5F;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAqB,EACrB,IAAY,EACZ,aAAsB,EACtB,UAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACjF,MAAM,WAAW,GAAG,mBAAmB,CAAC,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAEhE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACjC,MAAM,eAAe,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;QACvF,MAAM,WAAW,GAAG,mBAAmB,CAAC,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAEhE,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,EAAE,WAAW,EAAE;YACxD,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,GAAG;YAC/C,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;YACxC,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,GAAG;SAC5C,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -6,4 +6,7 @@
|
|
|
6
6
|
export * from './types/query.types.js';
|
|
7
7
|
export { QueryEngine } from './query-engine.js';
|
|
8
8
|
export type { QueryEngineOptions } from './query-engine.js';
|
|
9
|
+
export { scoreMatch, calculateMaxPossibleScore, normalizeLabelFilter, SCORING_WEIGHTS, } from './scoring.js';
|
|
10
|
+
export { generateSuggestions, countByAttribute } from './disambiguation.js';
|
|
11
|
+
export { filterByLabelFuzzy } from './fuzzy-match.js';
|
|
9
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,wBAAwB,CAAC;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,cAAc,wBAAwB,CAAC;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EACL,UAAU,EACV,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/src/query/index.js
CHANGED
|
@@ -7,4 +7,10 @@
|
|
|
7
7
|
export * from './types/query.types.js';
|
|
8
8
|
// Query Engine
|
|
9
9
|
export { QueryEngine } from './query-engine.js';
|
|
10
|
+
// Scoring
|
|
11
|
+
export { scoreMatch, calculateMaxPossibleScore, normalizeLabelFilter, SCORING_WEIGHTS, } from './scoring.js';
|
|
12
|
+
// Disambiguation
|
|
13
|
+
export { generateSuggestions, countByAttribute } from './disambiguation.js';
|
|
14
|
+
// Fuzzy matching
|
|
15
|
+
export { filterByLabelFuzzy } from './fuzzy-match.js';
|
|
10
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,QAAQ;AACR,cAAc,wBAAwB,CAAC;AAEvC,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,QAAQ;AACR,cAAc,wBAAwB,CAAC;AAEvC,eAAe;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,UAAU;AACV,OAAO,EACL,UAAU,EACV,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAC;AAEtB,iBAAiB;AACjB,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5E,iBAAiB;AACjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -72,40 +72,5 @@ export declare class QueryEngine {
|
|
|
72
72
|
* Check if a node matches all state constraints
|
|
73
73
|
*/
|
|
74
74
|
private matchState;
|
|
75
|
-
/**
|
|
76
|
-
* Normalize a label filter to its constituent parts
|
|
77
|
-
*/
|
|
78
|
-
private normalizeLabelFilter;
|
|
79
|
-
/**
|
|
80
|
-
* Filter nodes by fuzzy label matching.
|
|
81
|
-
* Returns matched nodes and a map of node_id -> match score for relevance calculation.
|
|
82
|
-
*/
|
|
83
|
-
private filterByLabelFuzzy;
|
|
84
|
-
/**
|
|
85
|
-
* Calculate relevance score and match reasons for a node.
|
|
86
|
-
*
|
|
87
|
-
* @param node - The node to score
|
|
88
|
-
* @param request - The original query request
|
|
89
|
-
* @param labelMatchScore - Pre-computed label match score (for fuzzy matching)
|
|
90
|
-
* @returns Relevance score (0-1) and list of reasons
|
|
91
|
-
*/
|
|
92
|
-
private scoreMatch;
|
|
93
|
-
/**
|
|
94
|
-
* Calculate the maximum possible score given the request filters.
|
|
95
|
-
*/
|
|
96
|
-
private calculateMaxPossibleScore;
|
|
97
|
-
/**
|
|
98
|
-
* Truncate a label for display in reasons.
|
|
99
|
-
*/
|
|
100
|
-
private truncateLabel;
|
|
101
|
-
/**
|
|
102
|
-
* Generate disambiguation suggestions when query matches multiple elements.
|
|
103
|
-
* Suggests refinements that would narrow down the results.
|
|
104
|
-
*/
|
|
105
|
-
private generateSuggestions;
|
|
106
|
-
/**
|
|
107
|
-
* Count nodes by a given attribute.
|
|
108
|
-
*/
|
|
109
|
-
private countByAttribute;
|
|
110
75
|
}
|
|
111
76
|
//# sourceMappingURL=query-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-engine.d.ts","sourceRoot":"","sources":["../../../src/query/query-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAGb,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"query-engine.d.ts","sourceRoot":"","sources":["../../../src/query/query-engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAGb,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EAMrB,MAAM,wBAAwB,CAAC;AAMhC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;CAOvB;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC;;OAEG;gBACS,QAAQ,EAAE,YAAY,EAAE,OAAO,GAAE,kBAAuB;IAMpE;;OAEG;IACH,IAAI,CAAC,OAAO,GAAE,mBAAwB,GAAG,oBAAoB;IAwF7D;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIjD;;OAEG;IACH,eAAe,IAAI;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE;IAO9D;;OAEG;IACH,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IAS3C;;OAEG;IACH,OAAO,CAAC,YAAY;IAKpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,UAAU;IAsBlB;;OAEG;IACH,OAAO,CAAC,cAAc;IAQtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,UAAU;CA+BnB"}
|
|
@@ -9,24 +9,10 @@
|
|
|
9
9
|
* - Relevance scoring
|
|
10
10
|
* - Disambiguation suggestions
|
|
11
11
|
*/
|
|
12
|
-
import { normalizeText
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
17
|
-
const SCORING_WEIGHTS = {
|
|
18
|
-
labelMatch: {
|
|
19
|
-
exact: 0.4,
|
|
20
|
-
contains: 0.3,
|
|
21
|
-
fuzzy: 0.25, // Base, multiplied by fuzzy match quality
|
|
22
|
-
},
|
|
23
|
-
kindMatch: 0.15,
|
|
24
|
-
regionMatch: 0.12,
|
|
25
|
-
stateMatch: 0.03, // Per matching state property (max ~0.27 for 9 props)
|
|
26
|
-
groupMatch: 0.08,
|
|
27
|
-
headingMatch: 0.08,
|
|
28
|
-
visibility: 0.02,
|
|
29
|
-
};
|
|
12
|
+
import { normalizeText } from '../lib/text-utils.js';
|
|
13
|
+
import { scoreMatch, normalizeLabelFilter } from './scoring.js';
|
|
14
|
+
import { generateSuggestions } from './disambiguation.js';
|
|
15
|
+
import { filterByLabelFuzzy } from './fuzzy-match.js';
|
|
30
16
|
/**
|
|
31
17
|
* Query engine for BaseSnapshot data
|
|
32
18
|
*/
|
|
@@ -57,9 +43,9 @@ export class QueryEngine {
|
|
|
57
43
|
}
|
|
58
44
|
// Filter by label (with fuzzy support)
|
|
59
45
|
if (request.label !== undefined) {
|
|
60
|
-
const { mode, text, caseSensitive, fuzzyOptions } =
|
|
46
|
+
const { mode, text, caseSensitive, fuzzyOptions } = normalizeLabelFilter(request.label);
|
|
61
47
|
if (mode === 'fuzzy') {
|
|
62
|
-
const result =
|
|
48
|
+
const result = filterByLabelFuzzy(candidates, text, caseSensitive, fuzzyOptions);
|
|
63
49
|
candidates = result.nodes;
|
|
64
50
|
labelScores = result.scores;
|
|
65
51
|
}
|
|
@@ -85,7 +71,7 @@ export class QueryEngine {
|
|
|
85
71
|
}
|
|
86
72
|
// Score all candidates
|
|
87
73
|
const scoredMatches = candidates.map((node) => {
|
|
88
|
-
const { relevance, reasons } =
|
|
74
|
+
const { relevance, reasons } = scoreMatch(node, request, labelScores?.get(node.node_id));
|
|
89
75
|
return { node, relevance, match_reasons: reasons };
|
|
90
76
|
});
|
|
91
77
|
// Apply min_score filter
|
|
@@ -104,7 +90,7 @@ export class QueryEngine {
|
|
|
104
90
|
// Generate suggestions if requested and results are ambiguous
|
|
105
91
|
let suggestions;
|
|
106
92
|
if (request.include_suggestions && totalMatched > 1) {
|
|
107
|
-
suggestions =
|
|
93
|
+
suggestions = generateSuggestions(limitedMatches, request);
|
|
108
94
|
if (suggestions.length === 0)
|
|
109
95
|
suggestions = undefined;
|
|
110
96
|
}
|
|
@@ -154,7 +140,7 @@ export class QueryEngine {
|
|
|
154
140
|
* Filter nodes by label text
|
|
155
141
|
*/
|
|
156
142
|
filterByLabel(nodes, filter) {
|
|
157
|
-
const { text, mode, caseSensitive } =
|
|
143
|
+
const { text, mode, caseSensitive } = normalizeLabelFilter(filter);
|
|
158
144
|
if (!text) {
|
|
159
145
|
return nodes;
|
|
160
146
|
}
|
|
@@ -219,291 +205,5 @@ export class QueryEngine {
|
|
|
219
205
|
}
|
|
220
206
|
return true;
|
|
221
207
|
}
|
|
222
|
-
/**
|
|
223
|
-
* Normalize a label filter to its constituent parts
|
|
224
|
-
*/
|
|
225
|
-
normalizeLabelFilter(filter) {
|
|
226
|
-
if (typeof filter === 'string') {
|
|
227
|
-
return { text: filter, mode: 'contains', caseSensitive: false };
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
text: filter.text,
|
|
231
|
-
mode: filter.mode ?? 'contains',
|
|
232
|
-
caseSensitive: filter.caseSensitive ?? false,
|
|
233
|
-
fuzzyOptions: filter.fuzzyOptions,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
// ===========================================================================
|
|
237
|
-
// Fuzzy Matching
|
|
238
|
-
// ===========================================================================
|
|
239
|
-
/**
|
|
240
|
-
* Filter nodes by fuzzy label matching.
|
|
241
|
-
* Returns matched nodes and a map of node_id -> match score for relevance calculation.
|
|
242
|
-
*/
|
|
243
|
-
filterByLabelFuzzy(nodes, text, caseSensitive, options = {}) {
|
|
244
|
-
const scores = new Map();
|
|
245
|
-
const normalizedQuery = normalizeText(caseSensitive ? text : text.toLowerCase());
|
|
246
|
-
const queryTokens = tokenizeForMatching(normalizedQuery, 10, 2);
|
|
247
|
-
if (queryTokens.length === 0) {
|
|
248
|
-
return { nodes: [], scores };
|
|
249
|
-
}
|
|
250
|
-
const matched = nodes.filter((n) => {
|
|
251
|
-
const normalizedLabel = normalizeText(caseSensitive ? n.label : n.label.toLowerCase());
|
|
252
|
-
const labelTokens = tokenizeForMatching(normalizedLabel, 10, 2);
|
|
253
|
-
const result = fuzzyTokensMatch(labelTokens, queryTokens, {
|
|
254
|
-
minTokenOverlap: options.minTokenOverlap ?? 0.5,
|
|
255
|
-
prefixMatch: options.prefixMatch ?? true,
|
|
256
|
-
minSimilarity: options.minSimilarity ?? 0.8,
|
|
257
|
-
});
|
|
258
|
-
if (result.isMatch) {
|
|
259
|
-
scores.set(n.node_id, result.score);
|
|
260
|
-
}
|
|
261
|
-
return result.isMatch;
|
|
262
|
-
});
|
|
263
|
-
return { nodes: matched, scores };
|
|
264
|
-
}
|
|
265
|
-
// ===========================================================================
|
|
266
|
-
// Relevance Scoring
|
|
267
|
-
// ===========================================================================
|
|
268
|
-
/**
|
|
269
|
-
* Calculate relevance score and match reasons for a node.
|
|
270
|
-
*
|
|
271
|
-
* @param node - The node to score
|
|
272
|
-
* @param request - The original query request
|
|
273
|
-
* @param labelMatchScore - Pre-computed label match score (for fuzzy matching)
|
|
274
|
-
* @returns Relevance score (0-1) and list of reasons
|
|
275
|
-
*/
|
|
276
|
-
scoreMatch(node, request, labelMatchScore) {
|
|
277
|
-
let relevance = 0;
|
|
278
|
-
const reasons = [];
|
|
279
|
-
// Label scoring
|
|
280
|
-
if (request.label !== undefined) {
|
|
281
|
-
const { mode } = this.normalizeLabelFilter(request.label);
|
|
282
|
-
let labelContribution;
|
|
283
|
-
if (mode === 'fuzzy' && labelMatchScore !== undefined) {
|
|
284
|
-
// Fuzzy: base weight * match quality
|
|
285
|
-
labelContribution = SCORING_WEIGHTS.labelMatch.fuzzy * labelMatchScore;
|
|
286
|
-
}
|
|
287
|
-
else if (mode === 'exact') {
|
|
288
|
-
labelContribution = SCORING_WEIGHTS.labelMatch.exact;
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
labelContribution = SCORING_WEIGHTS.labelMatch.contains;
|
|
292
|
-
}
|
|
293
|
-
relevance += labelContribution;
|
|
294
|
-
reasons.push({
|
|
295
|
-
type: 'label',
|
|
296
|
-
description: `Label "${this.truncateLabel(node.label)}" matches query`,
|
|
297
|
-
score_contribution: labelContribution,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
// Kind scoring
|
|
301
|
-
if (request.kind !== undefined) {
|
|
302
|
-
relevance += SCORING_WEIGHTS.kindMatch;
|
|
303
|
-
reasons.push({
|
|
304
|
-
type: 'kind',
|
|
305
|
-
description: `Kind "${node.kind}" matches filter`,
|
|
306
|
-
score_contribution: SCORING_WEIGHTS.kindMatch,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
// Region scoring
|
|
310
|
-
if (request.region !== undefined) {
|
|
311
|
-
relevance += SCORING_WEIGHTS.regionMatch;
|
|
312
|
-
reasons.push({
|
|
313
|
-
type: 'region',
|
|
314
|
-
description: `Region "${node.where.region}" matches filter`,
|
|
315
|
-
score_contribution: SCORING_WEIGHTS.regionMatch,
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
// State scoring (per matched property)
|
|
319
|
-
if (request.state !== undefined && node.state) {
|
|
320
|
-
const matchedStates = Object.keys(request.state).filter((k) => request.state[k] !== undefined &&
|
|
321
|
-
request.state[k] === node.state[k]);
|
|
322
|
-
const stateContribution = matchedStates.length * SCORING_WEIGHTS.stateMatch;
|
|
323
|
-
if (stateContribution > 0) {
|
|
324
|
-
relevance += stateContribution;
|
|
325
|
-
reasons.push({
|
|
326
|
-
type: 'state',
|
|
327
|
-
description: `States match: ${matchedStates.join(', ')}`,
|
|
328
|
-
score_contribution: stateContribution,
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// Group scoring
|
|
333
|
-
if (request.group_id !== undefined) {
|
|
334
|
-
relevance += SCORING_WEIGHTS.groupMatch;
|
|
335
|
-
reasons.push({
|
|
336
|
-
type: 'group',
|
|
337
|
-
description: `Group "${node.where.group_id}" matches`,
|
|
338
|
-
score_contribution: SCORING_WEIGHTS.groupMatch,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
// Heading context scoring
|
|
342
|
-
if (request.heading_context !== undefined) {
|
|
343
|
-
relevance += SCORING_WEIGHTS.headingMatch;
|
|
344
|
-
reasons.push({
|
|
345
|
-
type: 'heading',
|
|
346
|
-
description: `Heading context "${node.where.heading_context}" matches`,
|
|
347
|
-
score_contribution: SCORING_WEIGHTS.headingMatch,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
// Visibility bonus (always applied if node is visible)
|
|
351
|
-
if (node.state?.visible) {
|
|
352
|
-
relevance += SCORING_WEIGHTS.visibility;
|
|
353
|
-
}
|
|
354
|
-
// Normalize to 0-1 range based on what was actually queried
|
|
355
|
-
// This gives higher scores when fewer filters are used but all match
|
|
356
|
-
const maxPossible = this.calculateMaxPossibleScore(request);
|
|
357
|
-
const normalizedRelevance = maxPossible > 0 ? Math.min(1, relevance / maxPossible) : 0;
|
|
358
|
-
return { relevance: normalizedRelevance, reasons };
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Calculate the maximum possible score given the request filters.
|
|
362
|
-
*/
|
|
363
|
-
calculateMaxPossibleScore(request) {
|
|
364
|
-
let max = SCORING_WEIGHTS.visibility; // Always possible
|
|
365
|
-
if (request.label !== undefined) {
|
|
366
|
-
const { mode } = this.normalizeLabelFilter(request.label);
|
|
367
|
-
max += SCORING_WEIGHTS.labelMatch[mode];
|
|
368
|
-
}
|
|
369
|
-
if (request.kind !== undefined)
|
|
370
|
-
max += SCORING_WEIGHTS.kindMatch;
|
|
371
|
-
if (request.region !== undefined)
|
|
372
|
-
max += SCORING_WEIGHTS.regionMatch;
|
|
373
|
-
if (request.state !== undefined) {
|
|
374
|
-
const stateCount = Object.keys(request.state).filter((k) => request.state[k] !== undefined).length;
|
|
375
|
-
max += stateCount * SCORING_WEIGHTS.stateMatch;
|
|
376
|
-
}
|
|
377
|
-
if (request.group_id !== undefined)
|
|
378
|
-
max += SCORING_WEIGHTS.groupMatch;
|
|
379
|
-
if (request.heading_context !== undefined)
|
|
380
|
-
max += SCORING_WEIGHTS.headingMatch;
|
|
381
|
-
return max;
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Truncate a label for display in reasons.
|
|
385
|
-
*/
|
|
386
|
-
truncateLabel(label, maxLength = 30) {
|
|
387
|
-
if (label.length <= maxLength)
|
|
388
|
-
return label;
|
|
389
|
-
return label.slice(0, maxLength - 1) + '…';
|
|
390
|
-
}
|
|
391
|
-
// ===========================================================================
|
|
392
|
-
// Disambiguation Suggestions
|
|
393
|
-
// ===========================================================================
|
|
394
|
-
/**
|
|
395
|
-
* Generate disambiguation suggestions when query matches multiple elements.
|
|
396
|
-
* Suggests refinements that would narrow down the results.
|
|
397
|
-
*/
|
|
398
|
-
generateSuggestions(matches, request) {
|
|
399
|
-
const suggestions = [];
|
|
400
|
-
const nodes = matches.map((m) => m.node);
|
|
401
|
-
// Only generate suggestions if we have multiple matches
|
|
402
|
-
if (matches.length < 2)
|
|
403
|
-
return suggestions;
|
|
404
|
-
// 1. Suggest refining by kind if matches have different kinds
|
|
405
|
-
if (request.kind === undefined) {
|
|
406
|
-
const kindCounts = this.countByAttribute(nodes, (n) => n.kind);
|
|
407
|
-
if (kindCounts.size > 1) {
|
|
408
|
-
for (const [kind, count] of kindCounts) {
|
|
409
|
-
if (count < matches.length) {
|
|
410
|
-
suggestions.push({
|
|
411
|
-
type: 'refine_kind',
|
|
412
|
-
message: `Add kind: "${kind}" to narrow to ${count} result(s)`,
|
|
413
|
-
refinement: { kind },
|
|
414
|
-
expected_matches: count,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
// 2. Suggest refining by region if matches span multiple regions
|
|
421
|
-
if (request.region === undefined) {
|
|
422
|
-
const regionCounts = this.countByAttribute(nodes, (n) => n.where.region);
|
|
423
|
-
if (regionCounts.size > 1) {
|
|
424
|
-
for (const [region, count] of regionCounts) {
|
|
425
|
-
if (count < matches.length && region !== 'unknown') {
|
|
426
|
-
suggestions.push({
|
|
427
|
-
type: 'refine_region',
|
|
428
|
-
message: `Add region: "${region}" to narrow to ${count} result(s)`,
|
|
429
|
-
refinement: { region },
|
|
430
|
-
expected_matches: count,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// 3. Suggest refining by group_id if matches have different groups
|
|
437
|
-
if (request.group_id === undefined) {
|
|
438
|
-
const groupCounts = this.countByAttribute(nodes, (n) => n.where.group_id);
|
|
439
|
-
groupCounts.delete(undefined); // Remove nodes without groups
|
|
440
|
-
if (groupCounts.size >= 1) {
|
|
441
|
-
for (const [groupId, count] of groupCounts) {
|
|
442
|
-
if (groupId !== undefined) {
|
|
443
|
-
suggestions.push({
|
|
444
|
-
type: 'refine_group',
|
|
445
|
-
message: `Add group_id: "${groupId}" to narrow to ${count} result(s)`,
|
|
446
|
-
refinement: { group_id: groupId },
|
|
447
|
-
expected_matches: count,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// 4. Suggest adding state filters
|
|
454
|
-
if (request.state === undefined) {
|
|
455
|
-
const enabledCount = nodes.filter((n) => n.state?.enabled).length;
|
|
456
|
-
if (enabledCount > 0 && enabledCount < matches.length) {
|
|
457
|
-
suggestions.push({
|
|
458
|
-
type: 'add_state',
|
|
459
|
-
message: `Add state: { enabled: true } to narrow to ${enabledCount} result(s)`,
|
|
460
|
-
refinement: { state: { enabled: true } },
|
|
461
|
-
expected_matches: enabledCount,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
const visibleCount = nodes.filter((n) => n.state?.visible).length;
|
|
465
|
-
if (visibleCount > 0 && visibleCount < matches.length) {
|
|
466
|
-
suggestions.push({
|
|
467
|
-
type: 'add_state',
|
|
468
|
-
message: `Add state: { visible: true } to narrow to ${visibleCount} result(s)`,
|
|
469
|
-
refinement: { state: { visible: true } },
|
|
470
|
-
expected_matches: visibleCount,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
// 5. Suggest refining label to exact match if using contains/fuzzy
|
|
475
|
-
if (request.label !== undefined) {
|
|
476
|
-
const { mode, text } = this.normalizeLabelFilter(request.label);
|
|
477
|
-
if (mode !== 'exact') {
|
|
478
|
-
const normalizedText = normalizeText(text.toLowerCase());
|
|
479
|
-
const exactCount = nodes.filter((n) => normalizeText(n.label.toLowerCase()) === normalizedText).length;
|
|
480
|
-
if (exactCount > 0 && exactCount < matches.length) {
|
|
481
|
-
suggestions.push({
|
|
482
|
-
type: 'refine_label',
|
|
483
|
-
message: `Use exact label match to narrow to ${exactCount} result(s)`,
|
|
484
|
-
refinement: { label: { text, mode: 'exact' } },
|
|
485
|
-
expected_matches: exactCount,
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
// Sort by expected_matches (prefer suggestions that narrow most effectively)
|
|
491
|
-
// and limit to top 5
|
|
492
|
-
return suggestions
|
|
493
|
-
.filter((s) => s.expected_matches > 0 && s.expected_matches < matches.length)
|
|
494
|
-
.sort((a, b) => a.expected_matches - b.expected_matches)
|
|
495
|
-
.slice(0, 5);
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Count nodes by a given attribute.
|
|
499
|
-
*/
|
|
500
|
-
countByAttribute(nodes, getter) {
|
|
501
|
-
const counts = new Map();
|
|
502
|
-
for (const node of nodes) {
|
|
503
|
-
const value = getter(node);
|
|
504
|
-
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
505
|
-
}
|
|
506
|
-
return counts;
|
|
507
|
-
}
|
|
508
208
|
}
|
|
509
209
|
//# sourceMappingURL=query-engine.js.map
|