@pellux/goodvibes-sdk 0.27.5 → 0.27.7

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 (23) hide show
  1. package/dist/_internal/contracts/artifacts/operator-contract.json +1 -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.js +1 -1
  5. package/dist/_internal/platform/knowledge/home-graph/ask.js +2 -2
  6. package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
  7. package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +2 -1
  8. package/dist/_internal/platform/knowledge/home-graph/rendering.d.ts.map +1 -1
  9. package/dist/_internal/platform/knowledge/home-graph/rendering.js +5 -4
  10. package/dist/_internal/platform/knowledge/home-graph/search.d.ts.map +1 -1
  11. package/dist/_internal/platform/knowledge/home-graph/search.js +45 -3
  12. package/dist/_internal/platform/knowledge/home-graph/state.js +1 -1
  13. package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
  14. package/dist/_internal/platform/knowledge/semantic/answer.js +100 -14
  15. package/dist/_internal/platform/knowledge/semantic/enrichment.d.ts.map +1 -1
  16. package/dist/_internal/platform/knowledge/semantic/enrichment.js +34 -1
  17. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts +7 -0
  18. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts.map +1 -0
  19. package/dist/_internal/platform/knowledge/semantic/fact-quality.js +69 -0
  20. package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
  21. package/dist/_internal/platform/knowledge/semantic/service.js +13 -1
  22. package/dist/_internal/platform/version.js +1 -1
  23. package/package.json +1 -1
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.27.5"
6
+ "version": "0.27.7"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -1,6 +1,6 @@
1
1
  export declare const FOUNDATION_METADATA: {
2
2
  readonly productId: "goodvibes";
3
- readonly productVersion: "0.27.5";
3
+ readonly productVersion: "0.27.7";
4
4
  readonly operatorMethodCount: 256;
5
5
  readonly operatorEventCount: 30;
6
6
  readonly peerEndpointCount: 6;
@@ -1,7 +1,7 @@
1
1
  // Synced from packages/contracts/src/generated/foundation-metadata.ts
2
2
  export const FOUNDATION_METADATA = {
3
3
  "productId": "goodvibes",
4
- "productVersion": "0.27.5",
4
+ "productVersion": "0.27.7",
5
5
  "operatorMethodCount": 256,
6
6
  "operatorEventCount": 30,
7
7
  "peerEndpointCount": 6
@@ -3,7 +3,7 @@ export const OPERATOR_CONTRACT = {
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.27.5"
6
+ "version": "0.27.7"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -3,10 +3,10 @@ export async function answerHomeGraphQuery(input) {
3
3
  const sources = input.results.flatMap((result) => result.source ? [result.source] : []);
4
4
  const linkedObjects = collectLinkedObjects(input.results, input.state);
5
5
  if (input.semanticService) {
6
- await input.semanticService.enrichSources(uniqueSources(sources), {
6
+ void input.semanticService.enrichSources(uniqueSources(sources), {
7
7
  knowledgeSpaceId: input.spaceId,
8
8
  limit: Math.min(3, Math.max(1, sources.length)),
9
- });
9
+ }).catch(() => { });
10
10
  const answer = await input.semanticService.answer({
11
11
  query: input.query.query,
12
12
  knowledgeSpaceId: input.spaceId,
@@ -1 +1 @@
1
- {"version":3,"file":"generated-pages.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/generated-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8BlD,OAAO,KAAK,EACV,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzE,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AA2ED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,6BAA6B,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CA2EhF;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAsC5E;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB5E"}
1
+ {"version":3,"file":"generated-pages.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/generated-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BlD,OAAO,KAAK,EACV,6BAA6B,EAC7B,8BAA8B,EAC9B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAA;CAAE,GACzE,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AAED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,8BAA8B,CAAC,CAEzC;AA2ED,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,6BAA6B,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CA2EhF;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAsC5E;AAED,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,oBAAoB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAA;CAAE,GAC3E,OAAO,CAAC,yBAAyB,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAC,CAyB5E"}
@@ -2,6 +2,7 @@ import { materializeGeneratedKnowledgeProjection, } from '../generated-projectio
2
2
  import { HOME_GRAPH_CONNECTOR_ID, buildHomeGraphMetadata, edgeIsActive, homeGraphNodeId, homeGraphSourceId, isGeneratedPageSource, namespacedCanonicalUri, readRecord, uniqueStrings, } from './helpers.js';
3
3
  import { findHomeAssistantNode, missingDevicePassportFields, readHomeGraphState, renderHomeGraphState, safeHomeGraphFilename, sourcesLinkedToNode, } from './state.js';
4
4
  import { renderDevicePassportPage, renderPacketPage, renderRoomPage, } from './rendering.js';
5
+ import { isUsefulHomeGraphPageFact } from '../semantic/fact-quality.js';
5
6
  export async function generateAutomaticHomeGraphPages(context) {
6
7
  return generateHomeGraphPagesForCurrentState(context, context.input.pageAutomation ?? {});
7
8
  }
@@ -286,7 +287,7 @@ function semanticFactsLinkedToSources(sources, nodes, edges) {
286
287
  && sourceIds.has(edge.fromId)
287
288
  && edge.toKind === 'node'
288
289
  && edge.relation === 'supports_fact')).map((edge) => edge.toId));
289
- return nodes.filter((node) => factIds.has(node.id));
290
+ return nodes.filter((node) => factIds.has(node.id) && isUsefulHomeGraphPageFact(node));
290
291
  }
291
292
  function limitRecords(records, limit) {
292
293
  if (typeof limit !== 'number')
@@ -1 +1 @@
1
- {"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/rendering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EAEpB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAKrB,OAAO,KAAK,EAA6B,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEnG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAC;CAC7D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAmDnF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACzD,GAAG,MAAM,CA+BT;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,EAAE,OAAO,GAAE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,GAAG,MAAM,CAkBd;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,EAAE,OAAO,GAAE,iBAAsB,GAAG,kBAAkB,CAcnH"}
1
+ {"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/rendering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EAEpB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAKrB,OAAO,KAAK,EAA6B,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGnG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAC;CAC7D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAmDnF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAC5D,QAAQ,CAAC,MAAM,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACjD,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACzD,GAAG,MAAM,CA+BT;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,EAAE,OAAO,GAAE;IACrE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC,GAAG,MAAM,CAkBd;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,oBAAoB,EAAE,OAAO,GAAE,iBAAsB,GAAG,kBAAkB,CAcnH"}
@@ -2,6 +2,7 @@ import { renderKnowledgeMap } from '../map.js';
2
2
  import { countFacet, normalizeStringArray, readString } from '../map-filters.js';
3
3
  import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
4
4
  import { edgeIsActive, isGeneratedPageSource, readRecord, uniqueStrings } from './helpers.js';
5
+ import { isLowValueFeatureOrSpecText, isUsefulHomeGraphPageFact } from '../semantic/fact-quality.js';
5
6
  export function renderRoomPage(state, areaId) {
6
7
  const area = areaId
7
8
  ? findNodeByHaId(state.nodes, 'ha_area', areaId) ?? findNodeByHaId(state.nodes, 'ha_room', areaId)
@@ -295,7 +296,7 @@ function sourceEvidenceSnippets(source, extraction, tokens, limit) {
295
296
  ...featureSentences(searchText),
296
297
  source.summary,
297
298
  source.description,
298
- ]).filter((entry) => !isUnusableHomeGraphExtractionText(entry)).slice(0, 80);
299
+ ]).filter((entry) => !isUnusableHomeGraphExtractionText(entry) && !isLowValueFeatureOrSpecText(entry)).slice(0, 80);
299
300
  const scored = candidates
300
301
  .map((text) => ({ text: clampEvidence(text), score: evidenceScore(text, tokens) }))
301
302
  .filter((entry) => entry.text.length > 0)
@@ -327,7 +328,7 @@ function featureSentences(value) {
327
328
  const keywords = /\b(feature|features|support|supports|capability|capabilities|specification|specifications|mode|modes|hdmi|hdr|dolby|battery|reset|warranty|firmware|voice|remote)\b/i;
328
329
  return (text.match(/[^.!?\n]+[.!?]?/g) ?? [])
329
330
  .map((entry) => entry.trim())
330
- .filter((entry) => keywords.test(entry))
331
+ .filter((entry) => keywords.test(entry) && !isLowValueFeatureOrSpecText(entry))
331
332
  .slice(0, 24);
332
333
  }
333
334
  function evidenceScore(value, tokens) {
@@ -390,7 +391,7 @@ function renderSourceList(title, sources) {
390
391
  }
391
392
  function renderSemanticFacts(title, facts) {
392
393
  const entries = facts
393
- .filter((node) => node.metadata.semanticKind === 'fact')
394
+ .filter(isUsefulHomeGraphPageFact)
394
395
  .sort((left, right) => semanticFactSortKey(left).localeCompare(semanticFactSortKey(right)) || left.title.localeCompare(right.title))
395
396
  .slice(0, 80);
396
397
  if (entries.length === 0)
@@ -423,7 +424,7 @@ function semanticFactsLinkedToSources(sources, nodes, edges) {
423
424
  && sourceIds.has(edge.fromId)
424
425
  && edge.toKind === 'node'
425
426
  && edge.relation === 'supports_fact')).map((edge) => edge.toId));
426
- return nodes.filter((node) => factIds.has(node.id));
427
+ return nodes.filter((node) => factIds.has(node.id) && isUsefulHomeGraphPageFact(node));
427
428
  }
428
429
  function semanticFactSortKey(node) {
429
430
  const order = {
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqGxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAmFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAwMD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoIxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAmND,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
@@ -91,10 +91,39 @@ const SOURCE_EVIDENCE_TOKENS = new Set([
91
91
  'supports',
92
92
  'warranty',
93
93
  ]);
94
+ const ANCHOR_INTENT_TOKENS = new Set([
95
+ ...SOURCE_EVIDENCE_TOKENS,
96
+ 'function',
97
+ 'functions',
98
+ 'mode',
99
+ 'modes',
100
+ 'setup',
101
+ 'install',
102
+ 'configure',
103
+ 'documentation',
104
+ 'manual',
105
+ ]);
106
+ const HOME_GRAPH_ANCHOR_KINDS = new Set([
107
+ 'ha_home',
108
+ 'ha_entity',
109
+ 'ha_device',
110
+ 'ha_area',
111
+ 'ha_automation',
112
+ 'ha_script',
113
+ 'ha_scene',
114
+ 'ha_label',
115
+ 'ha_integration',
116
+ 'ha_room',
117
+ 'ha_device_passport',
118
+ 'ha_maintenance_item',
119
+ 'ha_troubleshooting_case',
120
+ 'ha_purchase',
121
+ 'ha_network_node',
122
+ ]);
94
123
  const INTEGRATION_QUERY_TOKENS = new Set(['automation', 'automations', 'homeassistant', 'integration', 'integrations', 'webostv']);
95
124
  export function readHomeGraphSearchState(store, spaceId) {
96
125
  const sources = store.listSources(10_000).filter((source) => (belongsToSpace(source, spaceId) && !isGeneratedPageSource(source)));
97
- const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId));
126
+ const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId) && node.status !== 'stale');
98
127
  const sourceIds = new Set(sources.map((source) => source.id));
99
128
  const nodeIds = new Set(nodes.map((node) => node.id));
100
129
  const edges = store.listEdges().filter((edge) => (edgeIsActive(edge)
@@ -118,8 +147,9 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
118
147
  if (tokens.length === 0)
119
148
  return [];
120
149
  const expandedTokens = expandTokens(tokens);
121
- const anchors = selectAnchorNodes(tokens, nodes);
122
- const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
150
+ const anchorTokens = selectAnchorQueryTokens(tokens);
151
+ const anchors = selectAnchorNodes(anchorTokens, nodes);
152
+ const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node));
123
153
  const anchorIds = new Set(sourceAnchors.map((node) => node.id));
124
154
  const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
125
155
  const sourceLinks = buildSourceLinkIndex(edges);
@@ -319,7 +349,11 @@ function sourceAnchorIdentityScore(anchorTokens, source, extraction) {
319
349
  ]);
320
350
  }
321
351
  function selectAnchorNodes(tokens, nodes) {
352
+ if (tokens.length === 0)
353
+ return [];
322
354
  return nodes.map((node) => {
355
+ if (!isHomeGraphAnchorNode(node))
356
+ return { node, score: 0 };
323
357
  const baseScore = scoreFields(tokens, nodeIdentityFields(node));
324
358
  const intentBoost = sourceAnchorIntentBoost(tokens, node);
325
359
  return {
@@ -331,6 +365,14 @@ function selectAnchorNodes(tokens, nodes) {
331
365
  .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
332
366
  .slice(0, 12);
333
367
  }
368
+ function selectAnchorQueryTokens(tokens) {
369
+ return tokens.filter((token) => !ANCHOR_INTENT_TOKENS.has(token));
370
+ }
371
+ function isHomeGraphAnchorNode(node) {
372
+ if (typeof node.metadata.semanticKind === 'string')
373
+ return false;
374
+ return HOME_GRAPH_ANCHOR_KINDS.has(node.kind);
375
+ }
334
376
  function selectSourceAnchors(tokens, nodes) {
335
377
  const preferred = nodes.filter((node) => sourceAnchorIntentBoost(tokens, node) >= 0);
336
378
  if (preferred.length > 0)
@@ -1,7 +1,7 @@
1
1
  import { belongsToSpace, edgeIsActive, readRecord } from './helpers.js';
2
2
  export function readHomeGraphState(store, spaceId) {
3
3
  const sources = store.listSources(10_000).filter((source) => belongsToSpace(source, spaceId));
4
- const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId));
4
+ const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId) && node.status !== 'stale');
5
5
  const sourceIds = new Set(sources.map((source) => source.id));
6
6
  const nodeIds = new Set(nodes.map((node) => node.id));
7
7
  const edges = store.listEdges().filter((edge) => (edgeIsActive(edge)
@@ -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;AAOlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAepB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAaD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CAiDxC"}
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;AAOlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAqBpB,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,CAmDxC"}
@@ -1,5 +1,30 @@
1
1
  import { getKnowledgeSpaceId, isInKnowledgeSpace, normalizeKnowledgeSpaceId } from '../spaces.js';
2
2
  import { MAX_ANSWER_EVIDENCE_CHARS, clampText, normalizeWhitespace, readRecord, readString, scoreSemanticText, semanticHash, semanticMetadata, sourceSemanticText, tokenizeSemanticQuery, uniqueStrings, } from './utils.js';
3
+ import { hasConcreteFeatureSignal, isLowValueFeatureOrSpecText, isSemanticAnswerLinkedObject, semanticFactText, } from './fact-quality.js';
4
+ const GENERIC_ANSWER_INTENT_TOKENS = new Set([
5
+ 'capabilities',
6
+ 'capability',
7
+ 'configuration',
8
+ 'configure',
9
+ 'feature',
10
+ 'features',
11
+ 'function',
12
+ 'functions',
13
+ 'install',
14
+ 'mode',
15
+ 'modes',
16
+ 'procedure',
17
+ 'setting',
18
+ 'settings',
19
+ 'setup',
20
+ 'spec',
21
+ 'specification',
22
+ 'specifications',
23
+ 'specs',
24
+ 'support',
25
+ 'supported',
26
+ 'supports',
27
+ ]);
3
28
  export async function answerKnowledgeQuery(context, input) {
4
29
  const spaceId = normalizeKnowledgeSpaceId(input.knowledgeSpaceId);
5
30
  const mode = input.mode ?? 'standard';
@@ -29,7 +54,9 @@ export async function answerKnowledgeQuery(context, input) {
29
54
  const sources = uniqueSources(evidence.flatMap((item) => item.source ? [item.source] : [])).slice(0, limit);
30
55
  const linkedObjects = input.includeLinkedObjects === false
31
56
  ? []
32
- : uniqueNodes([...(input.linkedObjects ?? []), ...evidence.flatMap((item) => item.node ? [item.node] : [])]).slice(0, 24);
57
+ : uniqueNodes([...(input.linkedObjects ?? []), ...evidence.flatMap((item) => item.node ? [item.node] : [])])
58
+ .filter(isSemanticAnswerLinkedObject)
59
+ .slice(0, 24);
33
60
  const gaps = await persistAnswerGaps(context.store, spaceId, input.query, llmAnswer?.gaps ?? []);
34
61
  const text = llmAnswer?.answer?.trim() || renderFallbackAnswer(input.query, mode, evidence);
35
62
  return {
@@ -53,6 +80,7 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
53
80
  const tokens = expandQueryTokens(tokenizeSemanticQuery(input.query));
54
81
  if (tokens.length === 0)
55
82
  return [];
83
+ const subjectTokens = tokens.filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
56
84
  const candidateSourceIds = new Set(input.candidateSourceIds ?? []);
57
85
  const candidateNodeIds = new Set(input.candidateNodeIds ?? []);
58
86
  const linkedObjectIds = new Set((input.linkedObjects ?? []).map((node) => node.id));
@@ -66,17 +94,28 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
66
94
  const extraction = store.getExtractionBySourceId(source.id);
67
95
  const facts = filterFactsForQuery(input.query, sourceFacts.get(source.id) ?? []);
68
96
  const text = sourceSemanticText(source, extraction);
69
- const baseScore = scoreSemanticText([
97
+ const scoringText = [
70
98
  source.title,
71
99
  source.summary,
72
100
  source.description,
73
101
  source.tags.join(' '),
74
102
  text,
75
103
  facts.map(renderFactForScoring).join(' '),
76
- ].join('\n'), tokens);
104
+ ].join('\n');
105
+ const baseScore = scoreSemanticText(scoringText, tokens);
106
+ const subjectScore = subjectTokens.length > 0 ? scoreSemanticText(scoringText, subjectTokens) : 0;
77
107
  const candidateBoost = candidateSourceIds.has(source.id) ? 220 : 0;
78
108
  const linkedBoost = linkedSourceIds.has(source.id) ? 160 : 0;
79
- const score = baseScore + candidateBoost + linkedBoost + Math.min(60, facts.length * 6);
109
+ const genericOnly = subjectTokens.length > 0 && subjectScore === 0;
110
+ const weakStrictCandidate = strictCandidates
111
+ && candidateSourceIds.size > 1
112
+ && candidateSourceIds.has(source.id)
113
+ && !linkedSourceIds.has(source.id)
114
+ && genericOnly;
115
+ const weakBroadMatch = !strictCandidates && genericOnly;
116
+ const score = weakStrictCandidate || weakBroadMatch
117
+ ? 0
118
+ : baseScore + candidateBoost + linkedBoost + Math.min(60, facts.length * 6);
80
119
  return {
81
120
  kind: 'source',
82
121
  id: source.id,
@@ -88,18 +127,25 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
88
127
  };
89
128
  });
90
129
  const nodeItems = store.listNodes(10_000)
91
- .filter((node) => belongsToAnswerSpace(node, spaceId))
130
+ .filter((node) => belongsToAnswerSpace(node, spaceId) && node.status !== 'stale')
92
131
  .filter((node) => !strictCandidates
93
132
  || candidateNodeIds.has(node.id)
94
133
  || linkedObjectIds.has(node.id)
95
134
  || (typeof node.sourceId === 'string' && candidateSourceIds.has(node.sourceId)))
96
135
  .map((node) => {
97
- const score = scoreSemanticText([
136
+ const scoringText = [
98
137
  node.title,
99
138
  node.summary,
100
139
  node.aliases.join(' '),
101
140
  JSON.stringify(node.metadata),
102
- ].join('\n'), tokens) + (candidateNodeIds.has(node.id) || linkedObjectIds.has(node.id) ? 120 : 0) + semanticKindBoost(node);
141
+ ].join('\n');
142
+ const baseScore = scoreSemanticText(scoringText, tokens);
143
+ const subjectScore = subjectTokens.length > 0 ? scoreSemanticText(scoringText, subjectTokens) : 0;
144
+ const genericOnly = subjectTokens.length > 0 && subjectScore === 0;
145
+ const candidateOrLinked = candidateNodeIds.has(node.id) || linkedObjectIds.has(node.id);
146
+ const score = genericOnly && !candidateOrLinked
147
+ ? 0
148
+ : baseScore + (candidateOrLinked ? 120 : 0) + semanticKindBoost(node);
103
149
  return {
104
150
  kind: 'node',
105
151
  id: node.id,
@@ -184,10 +230,14 @@ function renderFallbackAnswer(query, mode, evidence) {
184
230
  return lines.length > 0 ? lines.join('\n') : `No knowledge matched "${query}".`;
185
231
  }
186
232
  function filterFactsForQuery(query, facts) {
187
- const intent = factIntent(tokenizeSemanticQuery(query));
188
- if (!intent)
189
- return [...facts];
190
- return facts.filter((fact) => intent.has(readString(fact.metadata.factKind) ?? 'note'));
233
+ const tokens = tokenizeSemanticQuery(query);
234
+ const intent = factIntent(tokens);
235
+ const matching = intent
236
+ ? facts.filter((fact) => intent.has(readString(fact.metadata.factKind) ?? 'note'))
237
+ : [...facts];
238
+ return matching
239
+ .filter((fact) => fact.status !== 'stale' && !isLowValueFactForQuery(tokens, intent, fact))
240
+ .sort(compareFactQuality);
191
241
  }
192
242
  function factIntent(tokens) {
193
243
  const tokenSet = new Set(tokens);
@@ -207,6 +257,37 @@ function factIntent(tokens) {
207
257
  function hasAny(values, candidates) {
208
258
  return candidates.some((candidate) => values.has(candidate));
209
259
  }
260
+ function isLowValueFactForQuery(tokens, intent, fact) {
261
+ if (!intent || !hasFeatureIntent(intent))
262
+ return false;
263
+ const kind = readString(fact.metadata.factKind) ?? 'note';
264
+ if (!['feature', 'capability', 'specification', 'compatibility', 'configuration', 'identity'].includes(kind))
265
+ return false;
266
+ const text = semanticFactText(fact);
267
+ if (isLowValueFeatureOrSpecText(text))
268
+ return true;
269
+ const extractor = readString(fact.metadata.extractor);
270
+ const confidence = typeof fact.confidence === 'number' ? fact.confidence : 0;
271
+ if (extractor !== 'deterministic' || confidence > 60)
272
+ return false;
273
+ const subjectTokens = tokens.filter((token) => !GENERIC_ANSWER_INTENT_TOKENS.has(token));
274
+ return subjectTokens.length > 0 && !hasConcreteFeatureSignal(text);
275
+ }
276
+ function hasFeatureIntent(intent) {
277
+ return intent.has('feature') || intent.has('capability') || intent.has('specification') || intent.has('compatibility');
278
+ }
279
+ function compareFactQuality(left, right) {
280
+ return factQuality(right) - factQuality(left) || left.title.localeCompare(right.title);
281
+ }
282
+ function factQuality(fact) {
283
+ const extractor = readString(fact.metadata.extractor);
284
+ const kind = readString(fact.metadata.factKind);
285
+ const value = readString(fact.metadata.value);
286
+ return (extractor === 'llm' ? 40 : 0)
287
+ + (value ? 12 : 0)
288
+ + (kind === 'capability' || kind === 'feature' ? 8 : kind === 'specification' ? 6 : 0)
289
+ + Math.round(fact.confidence / 10);
290
+ }
210
291
  async function persistAnswerGap(store, spaceId, query, reason) {
211
292
  const node = await store.upsertNode({
212
293
  id: `sem-answer-gap-${semanticHash(spaceId, query)}`,
@@ -245,7 +326,7 @@ async function persistAnswerGaps(store, spaceId, query, gaps) {
245
326
  return nodes;
246
327
  }
247
328
  function buildSourceFactIndex(store, spaceId) {
248
- const facts = store.listNodes(10_000).filter((node) => (node.metadata.semanticKind === 'fact' && belongsToAnswerSpace(node, spaceId)));
329
+ const facts = store.listNodes(10_000).filter((node) => (node.status !== 'stale' && node.metadata.semanticKind === 'fact' && belongsToAnswerSpace(node, spaceId)));
249
330
  const bySource = new Map();
250
331
  for (const fact of facts) {
251
332
  const sourceId = readString(fact.metadata.sourceId) ?? fact.sourceId;
@@ -271,12 +352,17 @@ function sourceIdsLinkedToNodes(store, nodeIds, spaceId) {
271
352
  }
272
353
  function selectEvidenceExcerpt(query, text, facts) {
273
354
  const tokens = expandQueryTokens(tokenizeSemanticQuery(query));
355
+ const intent = factIntent(tokenizeSemanticQuery(query));
356
+ const featureIntent = Boolean(intent && hasFeatureIntent(intent));
274
357
  const factLines = facts
275
358
  .map(renderFactForPrompt)
276
359
  .filter((line) => scoreSemanticText(line, tokens) > 0)
277
360
  .slice(0, 12);
278
- const windows = evidenceWindows(text, tokens).slice(0, 4);
279
- return uniqueStrings([...factLines, ...windows, clampText(text, 720)]).join('\n');
361
+ const windows = evidenceWindows(text, tokens)
362
+ .filter((line) => !featureIntent || !isLowValueFeatureOrSpecText(line))
363
+ .slice(0, 4);
364
+ const fallback = featureIntent && (factLines.length > 0 || windows.length > 0) ? [] : [clampText(text, 720)];
365
+ return uniqueStrings([...factLines, ...windows, ...fallback]).join('\n');
280
366
  }
281
367
  function evidenceWindows(text, tokens) {
282
368
  const normalized = normalizeWhitespace(text);
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA6BtC"}
1
+ {"version":3,"file":"enrichment.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/enrichment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAKV,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAkBpB,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,GAAG,eAAe,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC/C;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,kCAAkC,EAC3C,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CA+BtC"}
@@ -4,7 +4,9 @@ export async function enrichKnowledgeSource(context, source, options = {}) {
4
4
  const text = sourceSemanticText(source, extraction);
5
5
  const textHash = sourceSemanticHash(source, extraction);
6
6
  const existingSemantic = readRecord(source.metadata.semanticEnrichment);
7
- if (!options.force && existingSemantic.textHash === textHash) {
7
+ const currentExtractor = readString(existingSemantic.extractor);
8
+ const shouldUpgradeDeterministic = Boolean(context.llm && existingSemantic.textHash === textHash && currentExtractor !== 'llm');
9
+ if (!options.force && existingSemantic.textHash === textHash && !shouldUpgradeDeterministic) {
8
10
  return emptyResult(source, true, 'semantic enrichment is current');
9
11
  }
10
12
  if (text.length < 40) {
@@ -257,8 +259,39 @@ async function persistSemanticExtraction(store, source, extraction, semantic, op
257
259
  });
258
260
  }
259
261
  const wikiPage = await persistWikiPage(store, source, semantic, spaceId, options.textHash);
262
+ await markPreviousSemanticNodesStale(store, source.id, spaceId, new Set([
263
+ ...entities.map((node) => node.id),
264
+ ...facts.map((node) => node.id),
265
+ ...gaps.map((node) => node.id),
266
+ ...(wikiPage ? [wikiPage.id] : []),
267
+ ]));
260
268
  return { source, skipped: false, extractor: semantic.extractor, facts, entities, gaps, ...(wikiPage ? { wikiPage } : {}) };
261
269
  }
270
+ async function markPreviousSemanticNodesStale(store, sourceId, spaceId, activeIds) {
271
+ const supersededAt = Date.now();
272
+ const semanticNodes = store.listNodes(10_000).filter((node) => (node.sourceId === sourceId
273
+ && typeof node.metadata.semanticKind === 'string'
274
+ && node.status !== 'stale'
275
+ && !activeIds.has(node.id)));
276
+ for (const node of semanticNodes) {
277
+ await store.upsertNode({
278
+ id: node.id,
279
+ kind: node.kind,
280
+ slug: node.slug,
281
+ title: node.title,
282
+ summary: node.summary,
283
+ aliases: node.aliases,
284
+ status: 'stale',
285
+ confidence: node.confidence,
286
+ ...(node.sourceId ? { sourceId: node.sourceId } : {}),
287
+ metadata: {
288
+ ...node.metadata,
289
+ supersededAt,
290
+ supersededInSpaceId: spaceId,
291
+ },
292
+ });
293
+ }
294
+ }
262
295
  async function persistWikiPage(store, source, semantic, spaceId, textHash) {
263
296
  const markdown = semantic.wikiPage?.markdown ?? renderDeterministicWikiPage(source, semantic.facts);
264
297
  if (!markdown.trim())
@@ -0,0 +1,7 @@
1
+ import type { KnowledgeNodeRecord } from '../types.js';
2
+ export declare function isSemanticAnswerLinkedObject(node: KnowledgeNodeRecord): boolean;
3
+ export declare function semanticFactText(fact: KnowledgeNodeRecord): string;
4
+ export declare function isLowValueFeatureOrSpecText(text: string): boolean;
5
+ export declare function hasConcreteFeatureSignal(text: string): boolean;
6
+ export declare function isUsefulHomeGraphPageFact(fact: KnowledgeNodeRecord): boolean;
7
+ //# sourceMappingURL=fact-quality.d.ts.map
@@ -0,0 +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,CAmBjE;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAa5E"}
@@ -0,0 +1,69 @@
1
+ import { readString } from './utils.js';
2
+ const USEFUL_PAGE_FACT_KINDS = new Set([
3
+ 'feature',
4
+ 'capability',
5
+ 'specification',
6
+ 'identity',
7
+ 'maintenance',
8
+ 'compatibility',
9
+ 'configuration',
10
+ 'troubleshooting',
11
+ ]);
12
+ export function isSemanticAnswerLinkedObject(node) {
13
+ if (node.status === 'stale')
14
+ return false;
15
+ const semanticKind = readString(node.metadata.semanticKind);
16
+ if (semanticKind)
17
+ return false;
18
+ return node.kind !== 'fact' && node.kind !== 'wiki_page' && node.kind !== 'knowledge_gap';
19
+ }
20
+ export function semanticFactText(fact) {
21
+ return [
22
+ fact.title,
23
+ fact.summary,
24
+ readString(fact.metadata.value),
25
+ readString(fact.metadata.evidence),
26
+ Array.isArray(fact.metadata.labels) ? fact.metadata.labels.join(' ') : '',
27
+ ].filter(Boolean).join(' ').toLowerCase();
28
+ }
29
+ export function isLowValueFeatureOrSpecText(text) {
30
+ const lower = text.toLowerCase();
31
+ const magicRemoteDetail = /\bmagic remote\b/.test(lower) || /\bbluetooth\b/.test(lower);
32
+ if (/\b(items? supplied|supplied items?|included accessories|optional extras?|sold separately|separate purchase|accessories may vary|contents? of (this )?manual|may be changed|without prior notice|available menus? and options?|certified cable|unapproved items?)\b/.test(lower)) {
33
+ return true;
34
+ }
35
+ if (!magicRemoteDetail && /\b(may vary|depending (upon|on) (the )?model|depending on country|depending on region)\b/.test(lower)) {
36
+ return true;
37
+ }
38
+ if (/\b(fasten|screws?|stand|tip over|overturn|fall over|transporting|move the tv|moving the tv|oils?|lubricants?|cleaning cloth|power cord|electric shock|fire hazard|near water|ventilation|antenna grounding)\b/.test(lower)) {
39
+ return true;
40
+ }
41
+ if (/\b(bezel|less than \d+(?:\.\d+)?\s*(mm|cm|inches?)|does not fit|will not fit|fit your tv'?s usb port|usb port may not fit)\b/.test(lower)) {
42
+ return true;
43
+ }
44
+ if (/\b(warning|caution|risk|hazard|do not|never)\b/.test(lower) && !/\b(feature|supports?|hdmi|usb|hdr|remote|bluetooth)\b/.test(lower)) {
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ export function hasConcreteFeatureSignal(text) {
50
+ return /\b(hdmi|usb|hdr|hdr10|dolby|vision|earc|arc|bluetooth|wi-?fi|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)\b/.test(text.toLowerCase());
51
+ }
52
+ export function isUsefulHomeGraphPageFact(fact) {
53
+ if (fact.status === 'stale')
54
+ return false;
55
+ if (fact.metadata.semanticKind !== 'fact')
56
+ return false;
57
+ const kind = readString(fact.metadata.factKind) ?? 'note';
58
+ if (!USEFUL_PAGE_FACT_KINDS.has(kind))
59
+ return false;
60
+ const text = semanticFactText(fact);
61
+ if (isLowValueFeatureOrSpecText(text))
62
+ return false;
63
+ const extractor = readString(fact.metadata.extractor);
64
+ const confidence = typeof fact.confidence === 'number' ? fact.confidence : 0;
65
+ if (extractor === 'deterministic' && confidence <= 60 && ['feature', 'capability', 'specification', 'compatibility', 'configuration'].includes(kind)) {
66
+ return hasConcreteFeatureSignal(text);
67
+ }
68
+ return true;
69
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,+BAAoC;IAG1D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACnF,CAAC;IA2BI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAI1F"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,+BAAoC;IAG1D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACnF,CAAC;IA6BI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAI1F"}
@@ -1,5 +1,6 @@
1
1
  import { answerKnowledgeQuery } from './answer.js';
2
2
  import { enrichKnowledgeSource } from './enrichment.js';
3
+ import { readRecord, readString, sourceSemanticHash, sourceSemanticText } from './utils.js';
3
4
  export class KnowledgeSemanticService {
4
5
  store;
5
6
  options;
@@ -37,7 +38,9 @@ export class KnowledgeSemanticService {
37
38
  const errors = [];
38
39
  for (const source of sources) {
39
40
  try {
40
- const llm = this.options.llm && llmAttempts < maxLlmSources ? this.options.llm : null;
41
+ const llm = this.options.llm && llmAttempts < maxLlmSources && sourceCanUseLlmUpgrade(this.store, source)
42
+ ? this.options.llm
43
+ : null;
41
44
  if (llm)
42
45
  llmAttempts += 1;
43
46
  const result = await enrichKnowledgeSource({ store: this.store, llm }, source, input);
@@ -58,3 +61,12 @@ export class KnowledgeSemanticService {
58
61
  return answerKnowledgeQuery({ store: this.store, llm: this.options.llm }, input);
59
62
  }
60
63
  }
64
+ function sourceCanUseLlmUpgrade(store, source) {
65
+ const extraction = store.getExtractionBySourceId(source.id);
66
+ const text = sourceSemanticText(source, extraction);
67
+ if (text.length < 40)
68
+ return false;
69
+ const existingSemantic = readRecord(source.metadata.semanticEnrichment);
70
+ return existingSemantic.textHash !== sourceSemanticHash(source, extraction)
71
+ || readString(existingSemantic.extractor) !== 'llm';
72
+ }
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.27.5';
3
+ let version = '0.27.7';
4
4
  try {
5
5
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
6
6
  version = pkg.version ?? version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-sdk",
3
- "version": "0.27.5",
3
+ "version": "0.27.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"