@pellux/goodvibes-sdk 0.28.16 → 0.28.18

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.
Files changed (70) hide show
  1. package/dist/_internal/contracts/artifacts/operator-contract.json +70 -1
  2. package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
  3. package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
  4. package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
  5. package/dist/_internal/contracts/generated/operator-contract.js +70 -1
  6. package/dist/_internal/platform/artifacts/store.d.ts +2 -0
  7. package/dist/_internal/platform/artifacts/store.d.ts.map +1 -1
  8. package/dist/_internal/platform/artifacts/store.js +20 -0
  9. package/dist/_internal/platform/control-plane/method-catalog-homegraph.d.ts.map +1 -1
  10. package/dist/_internal/platform/control-plane/method-catalog-homegraph.js +9 -0
  11. package/dist/_internal/platform/control-plane/operator-contract-schemas-homegraph.d.ts.map +1 -1
  12. package/dist/_internal/platform/control-plane/operator-contract-schemas-homegraph.js +15 -1
  13. package/dist/_internal/platform/knowledge/home-graph/ask-page-refresh.d.ts +11 -0
  14. package/dist/_internal/platform/knowledge/home-graph/ask-page-refresh.d.ts.map +1 -0
  15. package/dist/_internal/platform/knowledge/home-graph/ask-page-refresh.js +31 -0
  16. package/dist/_internal/platform/knowledge/home-graph/ask.d.ts.map +1 -1
  17. package/dist/_internal/platform/knowledge/home-graph/ask.js +24 -1
  18. package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
  19. package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +92 -20
  20. package/dist/_internal/platform/knowledge/home-graph/map-view.d.ts.map +1 -1
  21. package/dist/_internal/platform/knowledge/home-graph/map-view.js +14 -0
  22. package/dist/_internal/platform/knowledge/home-graph/pages.d.ts +3 -1
  23. package/dist/_internal/platform/knowledge/home-graph/pages.d.ts.map +1 -1
  24. package/dist/_internal/platform/knowledge/home-graph/pages.js +104 -1
  25. package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts +0 -1
  26. package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts.map +1 -1
  27. package/dist/_internal/platform/knowledge/home-graph/rendering.js +0 -12
  28. package/dist/_internal/platform/knowledge/home-graph/reset.d.ts +2 -1
  29. package/dist/_internal/platform/knowledge/home-graph/reset.d.ts.map +1 -1
  30. package/dist/_internal/platform/knowledge/home-graph/reset.js +35 -2
  31. package/dist/_internal/platform/knowledge/home-graph/search.d.ts.map +1 -1
  32. package/dist/_internal/platform/knowledge/home-graph/search.js +2 -18
  33. package/dist/_internal/platform/knowledge/home-graph/service.d.ts +1 -1
  34. package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
  35. package/dist/_internal/platform/knowledge/home-graph/service.js +17 -5
  36. package/dist/_internal/platform/knowledge/home-graph/source-links.d.ts +3 -0
  37. package/dist/_internal/platform/knowledge/home-graph/source-links.d.ts.map +1 -0
  38. package/dist/_internal/platform/knowledge/home-graph/source-links.js +30 -0
  39. package/dist/_internal/platform/knowledge/home-graph/state.d.ts.map +1 -1
  40. package/dist/_internal/platform/knowledge/home-graph/state.js +43 -1
  41. package/dist/_internal/platform/knowledge/home-graph/types.d.ts +39 -1
  42. package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
  43. package/dist/_internal/platform/knowledge/ingest-compile.js +1 -1
  44. package/dist/_internal/platform/knowledge/pdf-extractor.d.ts.map +1 -1
  45. package/dist/_internal/platform/knowledge/pdf-extractor.js +8 -3
  46. package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts.map +1 -1
  47. package/dist/_internal/platform/knowledge/semantic/answer-fallback.js +21 -16
  48. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.d.ts +7 -0
  49. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.d.ts.map +1 -0
  50. package/dist/_internal/platform/knowledge/semantic/answer-source-ranking.js +57 -0
  51. package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
  52. package/dist/_internal/platform/knowledge/semantic/answer.js +76 -9
  53. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts +0 -1
  54. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts.map +1 -1
  55. package/dist/_internal/platform/knowledge/semantic/fact-quality.js +18 -18
  56. package/dist/_internal/platform/knowledge/semantic/homeassistant-scope.d.ts.map +1 -1
  57. package/dist/_internal/platform/knowledge/semantic/homeassistant-scope.js +63 -5
  58. package/dist/_internal/platform/knowledge/semantic/repair-profile.d.ts +16 -0
  59. package/dist/_internal/platform/knowledge/semantic/repair-profile.d.ts.map +1 -0
  60. package/dist/_internal/platform/knowledge/semantic/repair-profile.js +152 -0
  61. package/dist/_internal/platform/knowledge/semantic/repair-subjects.d.ts +16 -0
  62. package/dist/_internal/platform/knowledge/semantic/repair-subjects.d.ts.map +1 -0
  63. package/dist/_internal/platform/knowledge/semantic/repair-subjects.js +52 -0
  64. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts.map +1 -1
  65. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.js +122 -57
  66. package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts.map +1 -1
  67. package/dist/_internal/platform/knowledge/semantic/self-improvement.js +40 -13
  68. package/dist/_internal/platform/knowledge/semantic/service.js +1 -1
  69. package/dist/_internal/platform/version.js +1 -1
  70. package/package.json +2 -1
@@ -94,7 +94,7 @@ export async function recompileKnowledgeSource(context, source) {
94
94
  function extractionNeedsRefresh(extraction) {
95
95
  if (!extraction)
96
96
  return true;
97
- if (extraction.format === 'pdf' && extraction.extractorId !== 'pdfjs')
97
+ if (extraction.format === 'pdf' && extraction.extractorId === 'pdf')
98
98
  return true;
99
99
  const searchText = readSearchText(extraction.structure) ?? readSearchText(extraction.metadata);
100
100
  if (searchText)
@@ -1 +1 @@
1
- {"version":3,"file":"pdf-extractor.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/knowledge/pdf-extractor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAgEjE,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAInF"}
1
+ {"version":3,"file":"pdf-extractor.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/knowledge/pdf-extractor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAgEjE,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAMnF"}
@@ -63,7 +63,10 @@ export async function extractPdf(buffer) {
63
63
  const parsed = await extractPdfWithPdfJs(buffer);
64
64
  if (parsed)
65
65
  return parsed;
66
- return extractPdfRawStreams(buffer);
66
+ const raw = extractPdfRawStreams(buffer);
67
+ if (raw)
68
+ return raw;
69
+ throw new Error('PDF extraction failed: no readable text was extracted. OCR or a dedicated PDF provider may be required.');
67
70
  }
68
71
  async function extractPdfWithPdfJs(buffer) {
69
72
  try {
@@ -148,11 +151,13 @@ function extractPdfRawStreams(buffer) {
148
151
  const combined = uniqueStrings(texts, 64).join('\n');
149
152
  const searchable = uniqueStrings(texts, 512).join('\n');
150
153
  const searchText = searchTextPayload(searchable);
154
+ if (!searchText)
155
+ return undefined;
151
156
  return {
152
- extractorId: 'pdf',
157
+ extractorId: 'pdf-raw',
153
158
  format: 'pdf',
154
159
  title: firstNonEmptyLine(combined) ?? 'PDF document',
155
- summary: summarizeText(combined) ?? 'PDF extraction produced limited text; OCR is not used in-core.',
160
+ summary: summarizeText(combined) ?? 'PDF document text extracted from raw streams.',
156
161
  excerpt: excerptText(combined),
157
162
  sections: uniqueStrings(combined.split(/\n+/), 8),
158
163
  links: uniqueStrings(Array.from(combined.matchAll(/\bhttps?:\/\/[^\s)]+/g), (linkMatch) => linkMatch[0]), 50),
@@ -1 +1 @@
1
- {"version":3,"file":"answer-fallback.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,cAAc,CAuBhB"}
1
+ {"version":3,"file":"answer-fallback.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAIvD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,sBAAsB,EAAE,EAC3C,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,cAAc,CAuBhB"}
@@ -1,3 +1,4 @@
1
+ import { isLowValueFeatureOrSpecText } from './fact-quality.js';
1
2
  import { clampText, normalizeWhitespace, readString } from './utils.js';
2
3
  export function renderFallbackAnswer(query, mode, evidence, facts) {
3
4
  const factLimit = mode === 'detailed' ? 12 : mode === 'concise' ? 3 : 8;
@@ -5,17 +6,17 @@ export function renderFallbackAnswer(query, mode, evidence, facts) {
5
6
  if (factPhrases.length > 0) {
6
7
  return {
7
8
  synthesized: true,
8
- text: `The source-backed facts I found indicate ${joinFactPhrases(factPhrases)}.`,
9
+ text: `Available source-backed details include ${joinFactPhrases(factPhrases)}.`,
9
10
  };
10
11
  }
11
- const evidencePhrases = evidence
12
- .slice(0, mode === 'detailed' ? 8 : mode === 'concise' ? 2 : 5)
13
- .map(renderEvidencePhrase)
12
+ const sourceTitles = evidence
13
+ .slice(0, mode === 'detailed' ? 6 : mode === 'concise' ? 2 : 4)
14
+ .map((item) => normalizeWhitespace(item.title))
14
15
  .filter(Boolean);
15
- if (evidencePhrases.length > 0) {
16
+ if (sourceTitles.length > 0) {
16
17
  return {
17
- synthesized: true,
18
- text: `The indexed evidence currently indicates ${joinFactPhrases(evidencePhrases)}. It does not yet contain enough extracted source-backed facts to answer every part of "${query}".`,
18
+ synthesized: false,
19
+ text: `I found matching sources (${joinFactPhrases(sourceTitles)}), but they have not produced enough source-backed facts to answer "${query}" yet.`,
19
20
  };
20
21
  }
21
22
  return {
@@ -26,9 +27,19 @@ export function renderFallbackAnswer(query, mode, evidence, facts) {
26
27
  function renderFactPhrase(fact) {
27
28
  const value = readString(fact.metadata.value);
28
29
  const summary = fact.summary ?? readString(fact.metadata.evidence);
29
- if (value)
30
- return `${fact.title}: ${value}`;
31
- return summary ? `${fact.title}: ${summary}` : fact.title;
30
+ const phrase = value ? `${fact.title}: ${value}` : summary ? `${fact.title}: ${summary}` : fact.title;
31
+ const cleaned = normalizeWhitespace(clampText(phrase, 220));
32
+ if (isRawSourceFragment(cleaned))
33
+ return '';
34
+ return isLowValueFeatureOrSpecText(cleaned) ? '' : cleaned;
35
+ }
36
+ function isRawSourceFragment(value) {
37
+ const lower = value.toLowerCase();
38
+ return /\bsemantic-gap-repair\b/.test(lower)
39
+ || /\bsource-backed facts identify\b/.test(lower)
40
+ || /\b(manual\.nz|current page|loading)\b/.test(lower)
41
+ || /\b[a-z0-9-]+\.(com|net|org|io|dev|tv|ca)\/[a-z0-9/_?=&.#-]+/.test(lower)
42
+ || /\b[a-z]{2}\/[a-z0-9/_-]+\/[a-z0-9._-]+/.test(lower);
32
43
  }
33
44
  function joinFactPhrases(phrases) {
34
45
  if (phrases.length <= 1)
@@ -37,9 +48,3 @@ function joinFactPhrases(phrases) {
37
48
  return `${phrases[0]} and ${phrases[1]}`;
38
49
  return `${phrases.slice(0, -1).join('; ')}; and ${phrases[phrases.length - 1]}`;
39
50
  }
40
- function renderEvidencePhrase(item) {
41
- const excerpt = normalizeWhitespace(item.excerpt ?? '');
42
- if (!excerpt)
43
- return item.title;
44
- return `${item.title}: ${clampText(excerpt, 260)}`;
45
- }
@@ -0,0 +1,7 @@
1
+ import type { KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
2
+ export interface AnswerSourceRankingEvidence {
3
+ readonly score: number;
4
+ readonly source?: KnowledgeSourceRecord;
5
+ }
6
+ export declare function rankAnswerSources(evidence: readonly AnswerSourceRankingEvidence[], facts: readonly KnowledgeNodeRecord[]): KnowledgeSourceRecord[];
7
+ //# sourceMappingURL=answer-source-ranking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-source-ranking.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer-source-ranking.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAG9E,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,2BAA2B,EAAE,EAChD,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,qBAAqB,EAAE,CAwBzB"}
@@ -0,0 +1,57 @@
1
+ import { readRecord, readString } from './utils.js';
2
+ export function rankAnswerSources(evidence, facts) {
3
+ const evidenceScoreBySource = new Map();
4
+ for (const item of evidence) {
5
+ if (!item.source)
6
+ continue;
7
+ evidenceScoreBySource.set(item.source.id, Math.max(evidenceScoreBySource.get(item.source.id) ?? 0, item.score));
8
+ }
9
+ const factCountBySource = new Map();
10
+ const promotedFactCountBySource = new Map();
11
+ for (const fact of facts) {
12
+ const sourceId = readString(fact.metadata.sourceId) ?? fact.sourceId;
13
+ if (!sourceId)
14
+ continue;
15
+ factCountBySource.set(sourceId, (factCountBySource.get(sourceId) ?? 0) + 1);
16
+ if (readString(fact.metadata.extractor) === 'repair-promotion') {
17
+ promotedFactCountBySource.set(sourceId, (promotedFactCountBySource.get(sourceId) ?? 0) + 1);
18
+ }
19
+ }
20
+ const sources = uniqueSources(evidence.flatMap((item) => item.source ? [item.source] : []));
21
+ const realSources = sources.filter((source) => source.metadata.homeGraphGeneratedPage !== true);
22
+ return (realSources.length > 0 ? realSources : sources)
23
+ .sort((left, right) => (sourceAnswerQuality(right, evidenceScoreBySource, factCountBySource, promotedFactCountBySource)
24
+ - sourceAnswerQuality(left, evidenceScoreBySource, factCountBySource, promotedFactCountBySource)
25
+ || left.id.localeCompare(right.id)));
26
+ }
27
+ function sourceAnswerQuality(source, evidenceScoreBySource, factCountBySource, promotedFactCountBySource) {
28
+ const discovery = readRecord(source.metadata.sourceDiscovery);
29
+ const rank = typeof discovery.sourceRank === 'number' ? Math.max(0, 12 - discovery.sourceRank) : 0;
30
+ return Math.round((evidenceScoreBySource.get(source.id) ?? 0) / 4)
31
+ + sourceAuthorityBoost(source)
32
+ + Math.min(80, (factCountBySource.get(source.id) ?? 0) * 10)
33
+ + Math.min(90, (promotedFactCountBySource.get(source.id) ?? 0) * 18)
34
+ + rank * 4
35
+ + (source.status === 'indexed' ? 12 : source.status === 'pending' ? 2 : 0)
36
+ - (source.metadata.homeGraphGeneratedPage === true ? 90 : 0);
37
+ }
38
+ function sourceAuthorityBoost(source) {
39
+ const discovery = readRecord(source.metadata.sourceDiscovery);
40
+ const text = `${readString(discovery.trustReason) ?? ''} ${readString(discovery.sourceDomain) ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`.toLowerCase();
41
+ if (/\bofficial-vendor-domain\b/.test(text) || /(^|[/.])(?:lg|sony|samsung|apple)\.com\b/.test(text))
42
+ return 140;
43
+ if (/\bmanufacturer-domain\b/.test(text))
44
+ return 80;
45
+ return 0;
46
+ }
47
+ function uniqueSources(values) {
48
+ const seen = new Set();
49
+ const result = [];
50
+ for (const source of values) {
51
+ if (seen.has(source.id))
52
+ continue;
53
+ seen.add(source.id);
54
+ result.push(source);
55
+ }
56
+ return result;
57
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AA+BpB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAsCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CA2ExC"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAiCpB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAyCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CA+FxC"}
@@ -6,11 +6,14 @@ import { concreteAnswerGapSpaceId } from './answer-space.js';
6
6
  import { answerNeedsFeatureGap, cleanSynthesizedAnswer } from './answer-quality.js';
7
7
  import { clampTimeoutMs, withTimeoutOrNull } from './timeouts.js';
8
8
  import { renderFallbackAnswer } from './answer-fallback.js';
9
+ import { rankAnswerSources } from './answer-source-ranking.js';
10
+ import { canonicalRepairSubjectNodes } from './repair-subjects.js';
9
11
  const GENERIC_ANSWER_INTENT_TOKENS = new Set([
10
12
  'capabilities',
11
13
  'capability',
12
14
  'configuration',
13
15
  'configure',
16
+ 'device',
14
17
  'feature',
15
18
  'features',
16
19
  'function',
@@ -26,9 +29,11 @@ const GENERIC_ANSWER_INTENT_TOKENS = new Set([
26
29
  'specification',
27
30
  'specifications',
28
31
  'specs',
32
+ 'object',
29
33
  'support',
30
34
  'supported',
31
35
  'supports',
36
+ 'thing',
32
37
  ]);
33
38
  export async function answerKnowledgeQuery(context, input) {
34
39
  const spaceId = normalizeKnowledgeSpaceId(input.knowledgeSpaceId);
@@ -60,14 +65,25 @@ export async function answerKnowledgeQuery(context, input) {
60
65
  }
61
66
  const llmAnswer = await synthesizeAnswer(context.llm ?? null, input.query, mode, evidence, input.timeoutMs);
62
67
  const facts = filterFactsForQuery(input.query, uniqueNodes(evidence.flatMap((item) => item.facts))).slice(0, 24);
63
- const sources = uniqueSources(evidence.flatMap((item) => item.source ? [item.source] : []))
68
+ const sources = rankAnswerSources(evidence, facts)
64
69
  .slice(0, limit)
65
70
  .map(withAnswerSourceAliases);
66
- const linkedObjects = input.includeLinkedObjects === false
71
+ const inferredHomeAssistantLinkedObjects = input.includeLinkedObjects === false
67
72
  ? []
68
- : uniqueNodes([...(input.linkedObjects ?? []), ...evidence.flatMap((item) => item.node ? [item.node] : [])])
73
+ : inferHomeAssistantLinkedObjects(context.store, spaceId, input.query);
74
+ const evidenceLinkedObjects = shouldUseEvidenceLinkedObjects(spaceId, input, inferredHomeAssistantLinkedObjects)
75
+ ? evidence.flatMap((item) => item.node ? [item.node] : [])
76
+ : [];
77
+ const rawLinkedObjects = input.includeLinkedObjects === false
78
+ ? []
79
+ : uniqueNodes([
80
+ ...(input.linkedObjects ?? []),
81
+ ...evidenceLinkedObjects,
82
+ ...inferredHomeAssistantLinkedObjects,
83
+ ])
69
84
  .filter(isSemanticAnswerLinkedObject)
70
85
  .slice(0, 24);
86
+ const linkedObjects = filterAnswerLinkedObjects(spaceId, input.query, rawLinkedObjects);
71
87
  const gapSpaceId = concreteAnswerGapSpaceId(spaceId, evidence, sources, linkedObjects);
72
88
  const gaps = await persistAnswerGaps(context.store, gapSpaceId, input.query, llmAnswer?.gaps ?? [], {
73
89
  sources,
@@ -86,8 +102,17 @@ export async function answerKnowledgeQuery(context, input) {
86
102
  linkedObjects,
87
103
  })
88
104
  : null;
89
- const fallback = llmAnswer?.answer ? null : renderFallbackAnswer(input.query, mode, evidence, facts);
90
- const text = cleanSynthesizedAnswer(llmAnswer?.answer?.trim() || fallback?.text || '', featureIntent);
105
+ const llmText = llmAnswer?.answer?.trim();
106
+ const cleanedLlmText = llmText ? cleanSynthesizedAnswer(llmText, featureIntent) : undefined;
107
+ const dropLowValueLlmAnswer = featureIntent && Boolean(cleanedLlmText) && isLowValueFeatureOrSpecText(cleanedLlmText ?? '');
108
+ const fallback = !llmText || dropLowValueLlmAnswer
109
+ ? renderFallbackAnswer(input.query, mode, evidence, facts)
110
+ : null;
111
+ const synthesizedAnswer = dropLowValueLlmAnswer ? undefined : cleanedLlmText;
112
+ const text = synthesizedAnswer || cleanSynthesizedAnswer(fallback?.text || '', featureIntent);
113
+ const confidence = input.includeConfidence === false
114
+ ? 0
115
+ : dropLowValueLlmAnswer ? 0 : answerConfidence(llmAnswer, evidence);
91
116
  return {
92
117
  ok: true,
93
118
  spaceId,
@@ -95,12 +120,12 @@ export async function answerKnowledgeQuery(context, input) {
95
120
  answer: {
96
121
  text,
97
122
  mode,
98
- confidence: input.includeConfidence === false ? 0 : answerConfidence(llmAnswer, evidence),
123
+ confidence,
99
124
  sources: input.includeSources === false ? [] : sources,
100
125
  linkedObjects,
101
126
  facts,
102
127
  gaps: evidenceGap ? uniqueNodes([...gaps, evidenceGap]) : gaps,
103
- synthesized: Boolean(llmAnswer?.answer) || Boolean(fallback?.synthesized),
128
+ synthesized: Boolean(synthesizedAnswer) || Boolean(fallback?.synthesized),
104
129
  },
105
130
  results: evidence.map(toSearchResult),
106
131
  };
@@ -123,7 +148,7 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
123
148
  const sourceItems = store.listSources(10_000)
124
149
  .filter((source) => belongsToAnswerSpace(source, spaceId))
125
150
  .filter((source) => sourceInHomeAssistantAnswerScope(store, source, homeAssistantScope))
126
- .filter((source) => !strictCandidates || candidateSourceIds.has(source.id) || (candidateSourceIds.size === 0 && linkedSourceIds.has(source.id)))
151
+ .filter((source) => !strictCandidates || candidateSourceIds.has(source.id) || linkedSourceIds.has(source.id))
127
152
  .map((source) => {
128
153
  const extraction = store.getExtractionBySourceId(source.id);
129
154
  const facts = filterFactsForQuery(input.query, sourceFacts.get(source.id) ?? []);
@@ -198,6 +223,36 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
198
223
  .sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
199
224
  return pruneEvidence(items, limit, broadHomeAssistantAlias);
200
225
  }
226
+ function inferHomeAssistantLinkedObjects(store, spaceId, query) {
227
+ const tokens = expandQueryTokens(tokenizeSemanticQuery(query));
228
+ const subjectTokens = tokens.filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
229
+ const scope = inferHomeAssistantAnswerScope(store, spaceId, query, subjectTokens);
230
+ if (!scope || scope.anchorNodeIds.size === 0)
231
+ return [];
232
+ return store.listNodes(10_000)
233
+ .filter((node) => scope.anchorNodeIds.has(node.id))
234
+ .filter((node) => belongsToAnswerSpace(node, spaceId))
235
+ .filter(isSemanticAnswerLinkedObject);
236
+ }
237
+ function filterAnswerLinkedObjects(spaceId, query, nodes) {
238
+ const normalized = normalizeKnowledgeSpaceId(spaceId);
239
+ if (normalized !== 'homeassistant' && !isHomeAssistantKnowledgeSpace(normalized))
240
+ return nodes;
241
+ const canonical = canonicalRepairSubjectNodes({ nodes, text: query });
242
+ if (canonical.length > 0)
243
+ return canonical.slice(0, 24);
244
+ const integrationIntent = /\b(integration|platform|add-?on|addon|plugin|service|api|setup|configure|configuration|auth|credential|rate limit)\b/i.test(query);
245
+ return nodes.filter((node) => integrationIntent || node.kind !== 'ha_integration').slice(0, 24);
246
+ }
247
+ function shouldUseEvidenceLinkedObjects(spaceId, input, inferredHomeAssistantLinkedObjects) {
248
+ if (input.linkedObjects?.length)
249
+ return true;
250
+ const normalized = normalizeKnowledgeSpaceId(spaceId);
251
+ if (normalized === 'homeassistant' || isHomeAssistantKnowledgeSpace(normalized)) {
252
+ return inferredHomeAssistantLinkedObjects.length > 0;
253
+ }
254
+ return true;
255
+ }
201
256
  async function synthesizeAnswer(llm, query, mode, evidence, requestedTimeoutMs) {
202
257
  if (!llm)
203
258
  return null;
@@ -465,13 +520,25 @@ function sourceIdsLinkedToNodes(store, nodeIds, spaceId) {
465
520
  const sourceIds = new Set();
466
521
  if (nodeIds.size === 0)
467
522
  return sourceIds;
468
- for (const edge of store.listEdges()) {
523
+ const edges = store.listEdges();
524
+ const factIds = new Set();
525
+ for (const edge of edges) {
469
526
  if (!belongsToAnswerSpace(edge, spaceId))
470
527
  continue;
471
528
  if (edge.fromKind === 'source' && edge.toKind === 'node' && nodeIds.has(edge.toId))
472
529
  sourceIds.add(edge.fromId);
473
530
  if (edge.fromKind === 'node' && nodeIds.has(edge.fromId) && edge.toKind === 'source')
474
531
  sourceIds.add(edge.toId);
532
+ if (edge.fromKind === 'node' && edge.toKind === 'node' && nodeIds.has(edge.toId) && edge.relation === 'describes') {
533
+ factIds.add(edge.fromId);
534
+ }
535
+ }
536
+ for (const edge of edges) {
537
+ if (!belongsToAnswerSpace(edge, spaceId))
538
+ continue;
539
+ if (edge.fromKind === 'source' && edge.toKind === 'node' && factIds.has(edge.toId) && edge.relation === 'supports_fact') {
540
+ sourceIds.add(edge.fromId);
541
+ }
475
542
  }
476
543
  return sourceIds;
477
544
  }
@@ -3,6 +3,5 @@ export declare function isSemanticAnswerLinkedObject(node: KnowledgeNodeRecord):
3
3
  export declare function semanticFactText(fact: KnowledgeNodeRecord): string;
4
4
  export declare function isLowValueFeatureOrSpecText(text: string): boolean;
5
5
  export declare function hasConcreteFeatureSignal(text: string): boolean;
6
- export declare function isUsefulHomeGraphSourceBackedNote(text: string): boolean;
7
6
  export declare function isUsefulHomeGraphPageFact(fact: KnowledgeNodeRecord): boolean;
8
7
  //# sourceMappingURL=fact-quality.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fact-quality.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/fact-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAcvD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAK/E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAQlE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmHjE;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAcvE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAiB5E"}
1
+ {"version":3,"file":"fact-quality.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/fact-quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAcvD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAK/E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAQlE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA0HjE;AAkBD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAiB5E"}
@@ -29,6 +29,14 @@ export function semanticFactText(fact) {
29
29
  export function isLowValueFeatureOrSpecText(text) {
30
30
  const lower = text.toLowerCase();
31
31
  const magicRemoteDetail = /\bmagic remote\b/.test(lower) || /\bbluetooth\b/.test(lower);
32
+ if (/\?\s*$/.test(text.trim()))
33
+ return true;
34
+ if (/\bsemantic-gap-repair\b/.test(lower))
35
+ return true;
36
+ if (isUrlOrPathFragment(lower) && !hasConcreteFeatureSignal(lower))
37
+ return true;
38
+ if (isUrlOrPathFragment(lower) && /\b(source-backed facts identify|current page|database|manual\.nz|loading|semantic-gap-repair)\b/.test(lower))
39
+ return true;
32
40
  if (isTruncatedManualFragment(lower))
33
41
  return true;
34
42
  if (/\b(items? supplied|supplied items?|included accessories|optional extras?|sold separately|separate purchase|accessories may vary|contents? of (this )?manual|may be changed|may change|subject to change|without prior notice|available menus? and options?|certified cable|unapproved items?)\b/.test(lower)) {
@@ -42,7 +50,10 @@ export function isLowValueFeatureOrSpecText(text) {
42
50
  }
43
51
  if (/^\s*\d+\s*(yes|no)\b/.test(lower)
44
52
  || /^\s*\d+(hdmi|usb|audio|ports?|features?|smart)\b/.test(lower)
45
- || /\b\d+\s+features such as\b/.test(lower)) {
53
+ || /^\s*0\s+ports\b/.test(lower)
54
+ || /\b\d+\s+features such as\b/.test(lower)
55
+ || /\b(case color|hardware cpu cores|hardware gpu cores)\b/.test(lower)
56
+ || /^\s*\d+\s*kg\d*/.test(lower)) {
46
57
  return true;
47
58
  }
48
59
  if (/\b(button|buttons|remote control)\b/.test(lower) && /[\u25b2\u25bc\u25c4\u25ba]|[▲▼◄►]|\\u25/.test(text)) {
@@ -143,6 +154,12 @@ export function isLowValueFeatureOrSpecText(text) {
143
154
  }
144
155
  return false;
145
156
  }
157
+ function isUrlOrPathFragment(value) {
158
+ return /https?:\/\//.test(value)
159
+ || /\b[a-z0-9-]+\.(com|net|org|io|dev|tv|ca|co\.uk)\/[a-z0-9/_?=&.#-]+/.test(value)
160
+ || /\b[a-z]{2}\/[a-z0-9/_-]+\/[a-z0-9._-]+/.test(value)
161
+ || /\b[a-z0-9._-]+\/(specifications?|manuals?|products?|support|features?)\/[a-z0-9._-]+/.test(value);
162
+ }
146
163
  function isTruncatedManualFragment(value) {
147
164
  const trimmed = value.trim();
148
165
  if (trimmed.length === 0)
@@ -156,23 +173,6 @@ function isTruncatedManualFragment(value) {
156
173
  export function hasConcreteFeatureSignal(text) {
157
174
  return /\b(hdmi|usb|hdr|hdr10|dolby|vision|earc|arc|bluetooth|wi-?fi|wireless lan|ethernet|voice|remote|game|filmmaker|airplay|chromecast|resolution|4k|8k|refresh|ports?|speakers?|audio|display|screen|apps?|streaming|matter|energy monitoring|scheduling|sensor|battery|z-?wave|zigbee|thread|motion|temperature|humidity|camera|recording|lock|garage|local control|api|automation|atsc|ntsc|qam|tuner|broadcast|rs-?232c|external control)\b/.test(text.toLowerCase());
158
175
  }
159
- export function isUsefulHomeGraphSourceBackedNote(text) {
160
- if (isLowValueFeatureOrSpecText(text))
161
- return false;
162
- const lower = text.toLowerCase();
163
- if (/\b(magic remote|remote control)\b/.test(lower))
164
- return false;
165
- const signalCount = [
166
- /\b(hdmi|usb|ethernet|wi-?fi|bluetooth|airplay|miracast|chromecast)\b/,
167
- /\b(hdr|hdr10|dolby vision|dolby atmos|filmmaker|game optimizer|freesync|g-sync|allm|vrr)\b/,
168
- /\b(4k|8k|uhd|resolution|refresh rate|120\s*hz|60\s*hz|nanocell|led|lcd|display|screen)\b/,
169
- /\b(tuner|atsc|qam|ntsc|codec|mpeg|dolby digital|audio formats?|video formats?)\b/,
170
- /\b(ports?|speaker system|speaker power|channels?|arc|earc)\b/,
171
- ].filter((pattern) => pattern.test(lower)).length;
172
- return signalCount >= 2
173
- || /\b(supported external devices|supports? .* (hdr|hdmi|usb|wi-?fi|bluetooth|earc|arc|4k|120\s*hz))\b/.test(lower)
174
- || /\b(dtv audio supported codec|supported codecs?|supported audio formats?|supported video formats?|supported picture formats?)\b/.test(lower);
175
- }
176
176
  export function isUsefulHomeGraphPageFact(fact) {
177
177
  if (fact.status === 'stale')
178
178
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"homeassistant-scope.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/homeassistant-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAI9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,wBAAwB,GAAG,IAAI,CAIjC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,wBAAwB,GAAG,IAAI,CA6BjC;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAMT;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAKT"}
1
+ {"version":3,"file":"homeassistant-scope.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/homeassistant-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAI9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,wBAAwB,GAAG,IAAI,CAIjC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,wBAAwB,GAAG,IAAI,CAgCjC;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAMT;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAKT"}
@@ -1,5 +1,5 @@
1
1
  import { getKnowledgeSpaceId, isHomeAssistantKnowledgeSpace, normalizeKnowledgeSpaceId } from '../spaces.js';
2
- import { readRecord, readString, scoreSemanticText, sourceSemanticText, tokenizeSemanticQuery } from './utils.js';
2
+ import { readRecord, readString, scoreSemanticText, sourceSemanticText, tokenizeSemanticQuery, uniqueStrings } from './utils.js';
3
3
  export function inferHomeAssistantAnswerScopeForQuery(store, spaceId, query) {
4
4
  const subjectTokens = tokenizeSemanticQuery(query)
5
5
  .filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
@@ -9,19 +9,23 @@ export function inferHomeAssistantAnswerScope(store, spaceId, query, subjectToke
9
9
  const normalized = normalizeKnowledgeSpaceId(spaceId);
10
10
  if (normalized !== 'homeassistant' && !isHomeAssistantKnowledgeSpace(normalized))
11
11
  return null;
12
- if (subjectTokens.length === 0)
12
+ const queryTokens = tokenizeSemanticQuery(query);
13
+ if (subjectTokens.length === 0 && queryTokens.length === 0)
13
14
  return null;
14
- const singularObjectQuery = isSingularObjectQuery(query, subjectTokens);
15
+ const singularObjectQuery = isSingularObjectQuery(query, queryTokens);
15
16
  const strongIdentityTokens = subjectTokens.filter(isStrongIdentityToken);
16
17
  if (!singularObjectQuery && strongIdentityTokens.length === 0)
17
18
  return null;
19
+ if (singularObjectQuery && strongIdentityTokens.length === 0 && !hasSpecificObjectTypeToken(queryTokens))
20
+ return null;
18
21
  const linkedSourceQualityByNode = linkedSourceQualityByAnchor(store);
22
+ const anchorTokens = uniqueStrings([...subjectTokens, ...queryTokens]);
19
23
  const anchors = store.listNodes(10_000)
20
24
  .filter((node) => node.status !== 'stale' && isHomeAssistantObjectNode(node))
21
25
  .filter((node) => normalized === 'homeassistant' || normalizeKnowledgeSpaceId(getKnowledgeSpaceId(node)) === normalized)
22
26
  .map((node) => ({
23
27
  node,
24
- score: scoreHomeAssistantObject(node, subjectTokens)
28
+ score: scoreHomeAssistantObject(node, anchorTokens)
25
29
  + Math.min(64, linkedSourceQualityByNode.get(node.id) ?? 0),
26
30
  }))
27
31
  .filter((entry) => entry.score > 0)
@@ -58,11 +62,21 @@ function sourceIdsLinkedToAnchors(store, anchorNodeIds) {
58
62
  const sourceIds = new Set();
59
63
  if (anchorNodeIds.size === 0)
60
64
  return sourceIds;
61
- for (const edge of store.listEdges()) {
65
+ const edges = store.listEdges();
66
+ const factIds = new Set();
67
+ for (const edge of edges) {
62
68
  if (edge.fromKind === 'source' && edge.toKind === 'node' && anchorNodeIds.has(edge.toId))
63
69
  sourceIds.add(edge.fromId);
64
70
  if (edge.fromKind === 'node' && anchorNodeIds.has(edge.fromId) && edge.toKind === 'source')
65
71
  sourceIds.add(edge.toId);
72
+ if (edge.fromKind === 'node' && edge.toKind === 'node' && anchorNodeIds.has(edge.toId) && edge.relation === 'describes') {
73
+ factIds.add(edge.fromId);
74
+ }
75
+ }
76
+ for (const edge of edges) {
77
+ if (edge.fromKind === 'source' && edge.toKind === 'node' && factIds.has(edge.toId) && edge.relation === 'supports_fact') {
78
+ sourceIds.add(edge.fromId);
79
+ }
66
80
  }
67
81
  return sourceIds;
68
82
  }
@@ -85,6 +99,25 @@ function linkedSourceQualityByAnchor(store) {
85
99
  scores.set(edge.fromId, (scores.get(edge.fromId) ?? 0) + score);
86
100
  }
87
101
  }
102
+ const describingFacts = new Map();
103
+ for (const edge of store.listEdges()) {
104
+ if (edge.fromKind === 'node' && edge.toKind === 'node' && edge.relation === 'describes') {
105
+ const current = describingFacts.get(edge.fromId) ?? new Set();
106
+ current.add(edge.toId);
107
+ describingFacts.set(edge.fromId, current);
108
+ }
109
+ }
110
+ for (const edge of store.listEdges()) {
111
+ if (edge.fromKind !== 'source' || edge.toKind !== 'node' || edge.relation !== 'supports_fact')
112
+ continue;
113
+ const source = sourcesById.get(edge.fromId);
114
+ if (!source)
115
+ continue;
116
+ const score = linkedSourceQuality(source, edge.relation);
117
+ for (const anchorId of describingFacts.get(edge.toId) ?? []) {
118
+ scores.set(anchorId, (scores.get(anchorId) ?? 0) + score);
119
+ }
120
+ }
88
121
  return scores;
89
122
  }
90
123
  function linkedSourceQuality(source, relation) {
@@ -169,6 +202,31 @@ function isSingularObjectQuery(query, tokens) {
169
202
  function isStrongIdentityToken(token) {
170
203
  return token.length >= 4 && !GENERIC_ANCHOR_TOKENS.has(token);
171
204
  }
205
+ function hasSpecificObjectTypeToken(tokens) {
206
+ return tokens.some((token) => SPECIFIC_OBJECT_TYPE_TOKENS.has(token));
207
+ }
208
+ const SPECIFIC_OBJECT_TYPE_TOKENS = new Set([
209
+ 'appliance',
210
+ 'bridge',
211
+ 'camera',
212
+ 'console',
213
+ 'doorbell',
214
+ 'garage',
215
+ 'hub',
216
+ 'lock',
217
+ 'phone',
218
+ 'printer',
219
+ 'projector',
220
+ 'remote',
221
+ 'router',
222
+ 'sensor',
223
+ 'speaker',
224
+ 'switch',
225
+ 'television',
226
+ 'thermostat',
227
+ 'tv',
228
+ 'xbox',
229
+ ]);
172
230
  function sourceMatchesAnchor(sourceText, anchorText) {
173
231
  const tokens = anchorText
174
232
  .toLowerCase()
@@ -0,0 +1,16 @@
1
+ import type { KnowledgeSourceRecord } from '../types.js';
2
+ export interface RepairProfileFact {
3
+ readonly kind: 'feature' | 'capability' | 'specification' | 'compatibility' | 'configuration';
4
+ readonly title: string;
5
+ readonly value?: string;
6
+ readonly summary: string;
7
+ readonly evidence: string;
8
+ readonly labels: readonly string[];
9
+ readonly aliases: readonly string[];
10
+ }
11
+ export declare function deriveRepairProfileFacts(input: {
12
+ readonly query: string;
13
+ readonly source: KnowledgeSourceRecord;
14
+ readonly text: string;
15
+ }): readonly RepairProfileFact[];
16
+ //# sourceMappingURL=repair-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-profile.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/repair-profile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AA0HD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GAAG,SAAS,iBAAiB,EAAE,CA8B/B"}