clearai-dsh 0.2.0 → 0.2.2

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.
@@ -431,20 +431,20 @@ export function graphProjection(state) {
431
431
  }
432
432
  const terms = [...lexicon.terms].sort((a, b) => (a.id < b.id ? -1 : 1))
433
433
  const predicates = [...lexicon.predicates].sort((a, b) => (a.id < b.id ? -1 : 1))
434
- for (const term of terms) nodes.push({ id: `term:${term.id}`, kind: 'concept', ref: term.id, label: term.label ?? term.id, status: term.status ?? 'admitted', parent: text(term.parent) === '' ? null : text(term.parent), uses: uses.get(term.id) ?? 0, depth: depthOf(term) })
434
+ for (const term of terms) nodes.push({ id: `term:${term.id}`, kind: 'concept', layer: 'ontology', ref: term.id, label: term.label ?? term.id, status: term.status ?? 'admitted', parent: text(term.parent) === '' ? null : text(term.parent), uses: uses.get(term.id) ?? 0, depth: depthOf(term) })
435
435
  const formsUsed = VALUE_FORMS.filter((form) => predicates.some((item) => text(item.range?.form) === form))
436
- for (const [index, form] of formsUsed.entries()) nodes.push({ id: `form:${form}`, kind: 'value_type', ref: form, label: form, status: 'admitted', uses: 0, depth: 0, formIndex: index })
436
+ for (const [index, form] of formsUsed.entries()) nodes.push({ id: `form:${form}`, kind: 'value_type', layer: 'ontology', ref: form, label: form, status: 'admitted', uses: 0, depth: 0, formIndex: index })
437
437
  for (const term of terms) {
438
438
  if (text(term.parent) === '') continue
439
439
  if (!termById.has(text(term.parent))) continue
440
- edges.push({ id: `is_a:${term.id}`, kind: 'is_a', predicate: null, label: 'is_a', from: `term:${term.id}`, to: `term:${term.parent}`, status: term.status ?? 'admitted' })
440
+ edges.push({ id: `is_a:${term.id}`, kind: 'is_a', layer: 'ontology', predicate: null, label: 'is_a', from: `term:${term.id}`, to: `term:${term.parent}`, status: term.status ?? 'admitted' })
441
441
  }
442
442
  for (const predicate of predicates) {
443
443
  const range = isPlainObject(predicate.range) ? predicate.range : {}
444
444
  const rangeTerm = text(range.term)
445
445
  const form = text(range.form)
446
446
  const to = rangeTerm !== '' ? `term:${rangeTerm}` : form !== '' ? `form:${form}` : null
447
- if (to !== null) edges.push({ id: `predicate:${predicate.id}`, kind: 'predicate', predicate: predicate.id, label: predicate.label ?? predicate.id, from: text(predicate.domain) === '' ? null : `term:${text(predicate.domain)}`, to, status: predicate.status ?? 'admitted', functional: predicate.functional === true })
447
+ if (to !== null) edges.push({ id: `predicate:${predicate.id}`, kind: 'predicate', layer: 'ontology', predicate: predicate.id, label: predicate.label ?? predicate.id, from: text(predicate.domain) === '' ? null : `term:${text(predicate.domain)}`, to, status: predicate.status ?? 'admitted', functional: predicate.functional === true })
448
448
  }
449
449
  const instances = new Map()
450
450
  for (const fact of facts) {
@@ -454,7 +454,7 @@ export function graphProjection(state) {
454
454
  const subjectId = text(subject.id)
455
455
  if (subjectId === '') continue
456
456
  const key = `${text(subject.type)}|${subjectId}`
457
- if (!instances.has(key)) instances.set(key, { id: key, kind: 'instance', ref: subjectId, label: subjectId, type: text(subject.type) === '' ? null : text(subject.type), facts: [] })
457
+ if (!instances.has(key)) instances.set(key, { id: key, kind: 'instance', layer: 'entity', ref: subjectId, label: subjectId, type: text(subject.type) === '' ? null : text(subject.type), facts: [] })
458
458
  instances.get(key).facts.push(fact.id ?? null)
459
459
  const predicateId = text(assertion?.predicate)
460
460
  const object = isPlainObject(assertion?.object) ? assertion.object : {}
@@ -465,48 +465,66 @@ export function graphProjection(state) {
465
465
  const objectType = text(object.type) === '' ? text(predicateById.get(predicateId)?.range?.term) : text(object.type)
466
466
  const objectKeyId = `${objectType}|${objectLabel}`
467
467
  if (objectLabel !== '') {
468
- if (!instances.has(objectKeyId)) instances.set(objectKeyId, { id: objectKeyId, kind: 'instance', ref: objectLabel, label: objectLabel, type: objectType === '' ? null : objectType, facts: [] })
468
+ if (!instances.has(objectKeyId)) instances.set(objectKeyId, { id: objectKeyId, kind: 'instance', layer: 'entity', ref: objectLabel, label: objectLabel, type: objectType === '' ? null : objectType, facts: [] })
469
469
  instances.get(objectKeyId).facts.push(fact.id ?? null)
470
470
  to = objectKeyId
471
471
  }
472
472
  } else if (predicateId !== '') {
473
473
  const literalId = `${predicateId}:${objectKey(assertion.object)}`
474
- if (!instances.has(literalId)) instances.set(literalId, { id: literalId, kind: 'literal', ref: objectKey(assertion.object), label: formatObject(object), type: null, facts: [] })
474
+ if (!instances.has(literalId)) instances.set(literalId, { id: literalId, kind: 'literal', layer: 'entity', ref: objectKey(assertion.object), label: formatObject(object), type: null, facts: [] })
475
475
  instances.get(literalId).facts.push(fact.id ?? null)
476
476
  to = literalId
477
477
  }
478
- if (to !== null) edges.push({ id: `assertion:${fact.id ?? ''}:${predicateId}:${subjectKey(assertion)}`, kind: 'assertion', predicate: predicateId, label: text(lexicon.predicates.find((item) => item.id === predicateId)?.label) || predicateId, from: key, to, status, level: fact.level ?? null, fact: fact.id ?? null, scope: fact.scope ?? null })
478
+ if (to !== null) edges.push({ id: `assertion:${fact.id ?? ''}:${predicateId}:${subjectKey(assertion)}`, kind: 'assertion', layer: 'entity', predicate: predicateId, label: text(lexicon.predicates.find((item) => item.id === predicateId)?.label) || predicateId, from: key, to, status, level: fact.level ?? null, fact: fact.id ?? null, scope: fact.scope ?? null, claim: text(fact.hypothesis) === '' ? null : text(fact.hypothesis) })
479
479
  }
480
480
  }
481
481
  for (const instance of [...instances.values()].sort((a, b) => (a.id < b.id ? -1 : 1))) nodes.push(instance)
482
- const maxDepth = nodes.reduce((best, node) => (node.kind === 'concept' ? Math.max(best, node.depth) : best), 0)
482
+ /**
483
+ * **确定性布局:按层分段,层内折行。**
484
+ *
485
+ * 「层」= `is_a` 深度(概念)、值形态、实例/字面值各成一段,段与段依次向下排。
486
+ * 关键的一步是**折行**:早先每一层只铺一行,于是一堆没有父概念的根被排成一条
487
+ * 极宽极扁的带子(真数据:23 个节点铺出 3800×180),任何视口里都只能缩到看不清。
488
+ * 折行之后同一层在有限宽度内往下堆,整体接近屏幕比例,读得清。
489
+ *
490
+ * 它仍然是**纯函数、确定性**:同一份账本永远算出同一套坐标(层内按 id 排,不靠遍历顺序)。
491
+ */
483
492
  const COLUMN = 220
484
493
  const ROW = 132
494
+ const PER_ROW = 6
495
+ let cursorY = 0
496
+ const band = (list) => {
497
+ const top = cursorY
498
+ cursorY += Math.max(1, Math.ceil(list.length / PER_ROW)) * ROW
499
+ return top
500
+ }
501
+ const place = (list, top) => {
502
+ list.forEach((node, index) => {
503
+ node.x = (index % PER_ROW) * COLUMN
504
+ node.y = top + Math.floor(index / PER_ROW) * ROW
505
+ })
506
+ }
485
507
  const byDepth = new Map()
486
508
  for (const node of nodes.filter((item) => item.kind === 'concept')) {
487
509
  const list = byDepth.get(node.depth) ?? []
488
510
  list.push(node)
489
511
  byDepth.set(node.depth, list)
490
512
  }
491
- const widest = Math.max(...[...byDepth.values()].map((list) => list.length), 1)
492
- for (const [depth, list] of byDepth) {
493
- list.forEach((node, index) => {
494
- node.x = Math.round(index * COLUMN + (widest - list.length) * (COLUMN / 2))
495
- node.y = depth * ROW
496
- })
513
+ /** 深度小的在上(父在上、子在下),同深度内按 id——两条都为了确定性。 */
514
+ for (const depth of [...byDepth.keys()].sort((left, right) => left - right)) {
515
+ const list = byDepth.get(depth)
516
+ place(list, band(list))
497
517
  }
498
- nodes.filter((item) => item.kind === 'value_type').forEach((node) => {
499
- node.x = Math.round(node.formIndex * COLUMN)
500
- node.y = (maxDepth + 1) * ROW
501
- })
502
- const instanceBand = (maxDepth + 2) * ROW
503
- const perRow = 6
504
- nodes.filter((item) => item.kind === 'instance' || item.kind === 'literal').forEach((node, index) => {
505
- node.x = (index % perRow) * COLUMN
506
- node.y = instanceBand + Math.floor(index / perRow) * ROW
507
- })
518
+ const forms = nodes.filter((item) => item.kind === 'value_type')
519
+ if (forms.length > 0) place(forms, band(forms))
520
+ const leaves = nodes.filter((item) => item.kind === 'instance' || item.kind === 'literal')
521
+ if (leaves.length > 0) place(leaves, band(leaves))
508
522
  const width = Math.max(...nodes.map((node) => (typeof node.x === 'number' ? node.x : 0)), 0) + COLUMN
509
523
  const height = Math.max(...nodes.map((node) => (typeof node.y === 'number' ? node.y : 0)), 0) + ROW
524
+ /**
525
+ * 返回的是**纯语义**:节点、边、包围盒。视口该画哪一块、先画谁、怎么缩放,
526
+ * 都是渲染层的事(现在是 React Flow)——投影不再替渲染做决定。
527
+ */
510
528
  return { nodes, edges, bounds: { width, height } }
511
529
  }
512
530