diff --git a/src/front/static/ontology/js/ontology-axioms.js b/src/front/static/ontology/js/ontology-axioms.js index abeadec3..55d3aaf1 100644 --- a/src/front/static/ontology/js/ontology-axioms.js +++ b/src/front/static/ontology/js/ontology-axioms.js @@ -69,7 +69,15 @@ window.AxiomsModule = { select.innerHTML = ''; items.forEach(item => { - select.innerHTML += ``; + // Prefer the plain name over the class/property's stored `uri`. + // That `uri` field can be stale (e.g. left over from before the + // ontology's base URI was renamed) — sending just the name lets + // the backend (OntologyGenerator._resolve_uri) always rebuild + // the correct, current URI from `base_uri + name`, the same way + // every other part of the ontology already resolves class/ + // property identities. Falls back to `uri` only if a name is + // somehow missing. + select.innerHTML += ``; }); }, @@ -131,7 +139,9 @@ window.AxiomsModule = { newSelect.innerHTML = ''; items.forEach(item => { - newSelect.innerHTML += ``; + // See populateSelect() above — prefer the plain name so a stale + // stored `uri` never gets baked into a saved axiom/expression. + newSelect.innerHTML += ``; }); container.appendChild(newSelect); @@ -151,7 +161,9 @@ window.AxiomsModule = { let selectHtml = ''; diff --git a/src/front/static/ontology/js/ontology-swrl.js b/src/front/static/ontology/js/ontology-swrl.js index 27563266..87c97d6d 100644 --- a/src/front/static/ontology/js/ontology-swrl.js +++ b/src/front/static/ontology/js/ontology-swrl.js @@ -329,7 +329,7 @@ window.SwrlModule = { if (typeof d3 !== 'undefined') return Promise.resolve(); return new Promise((resolve, reject) => { const script = document.createElement('script'); - script.src = 'https://d3js.org/d3.v7.min.js'; + script.src = 'https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js'; script.onload = resolve; script.onerror = () => reject(new Error('Failed to load D3.js')); document.head.appendChild(script); @@ -415,15 +415,23 @@ window.SwrlModule = { }); // Only display entities that participate in at least one business - // relationship (object property). Entities with no relationships — or - // with inheritance links only — are hidden, and their inheritance edges - // are dropped so no orphan nodes remain. + // relationship (object property) — plus their direct subclasses, so a + // rule can still reference/classify into a specialized subtype (e.g. + // EstudioIndicadorLEB under EstudioPais) even though the subtype itself + // has no relationships of its own. Entities that are neither connected + // nor a subclass of something connected stay hidden. const connectedIds = new Set(); links.forEach(l => { if (l.type !== 'relationship') return; connectedIds.add(typeof l.source === 'object' ? l.source.id : l.source); connectedIds.add(typeof l.target === 'object' ? l.target.id : l.target); }); + links.forEach(l => { + if (l.type !== 'inheritance') return; + const parent = typeof l.source === 'object' ? l.source.id : l.source; + const child = typeof l.target === 'object' ? l.target.id : l.target; + if (connectedIds.has(parent)) connectedIds.add(child); + }); nodes = nodes.filter(n => connectedIds.has(n.id)); this._graphNodes = nodes; links = links.filter(l => { @@ -916,11 +924,15 @@ window.SwrlModule = { } } - // Raw editor sync - const rawAnt = document.getElementById('swrlRawAntecedent'); - const rawCon = document.getElementById('swrlRawConsequent'); - if (rawAnt) rawAnt.value = antStr; - if (rawCon) rawCon.value = conStr; + // Raw editor sync — solo si NO estamos en modo raw; en modo raw el texto + // crudo es la fuente de verdad y puede incluir átomos (p.ej. clases THEN + // derivadas) que no existen como nodos del grafo. + if (!this.rawMode) { + const rawAnt = document.getElementById('swrlRawAntecedent'); + const rawCon = document.getElementById('swrlRawConsequent'); + if (rawAnt) rawAnt.value = antStr; + if (rawCon) rawCon.value = conStr; + } }, _buildAtomsFromSelection() { diff --git a/src/front/static/query/js/query-cohorts.js b/src/front/static/query/js/query-cohorts.js index 1ffeba94..0b7c5717 100644 --- a/src/front/static/query/js/query-cohorts.js +++ b/src/front/static/query/js/query-cohorts.js @@ -11,6 +11,7 @@ const CohortModule = { rules: [], // saved rules list activeRuleId: null, // id of the loaded saved rule, if any classes: [], // ontology classes loaded once + baseUri: '', // current ontology base_uri (see _currentUri) properties: [], // ontology properties loaded once objectProperties: [], // ObjectProperty subset lastPreview: null, // last DetectionResult JSON @@ -42,10 +43,12 @@ const CohortModule = { const ont = data?.ontology || data || {}; this.classes = ont.classes || []; this.properties = ont.properties || []; + this.baseUri = ont.base_uri || ont.baseUri || ''; } } catch { this.classes = []; this.properties = []; + this.baseUri = ''; } // fall back: read from document if injected if (!this.classes.length) { @@ -53,6 +56,7 @@ const CohortModule = { const ont = window.__ontology__ || {}; this.classes = ont.classes || []; this.properties = ont.properties || []; + this.baseUri = this.baseUri || ont.base_uri || ont.baseUri || ''; } catch { /* noop */ } } this.objectProperties = (this.properties || []).filter(p => @@ -61,6 +65,38 @@ const CohortModule = { this._populateClassSelect(); }, + /** + * Reconstruct the CURRENT, correct ontology-form URI for a class or + * property from its local name, using the ``base_uri`` of the + * ontology that's actually loaded right now — instead of trusting + * whatever ``.uri``/``.iri``/``.id`` field the object itself carries. + * + * OntoBricks has repeatedly hit the same namespace-drift bug this + * session (see ``ontology-axioms.js``'s populateSelect/ + * addObjectSelect/addChainSelect, and + * ``OntologyGenerator._resolve_uri``): classes/properties can carry + * a stale ``.uri`` minted under a previous namespace (e.g. a + * pre-rebrand ``databricks-ontology.com``) that no longer matches + * the live ``base_uri``. Unlike the OWL generator, nothing on the + * cohort side re-resolves a saved rule's ``class_uri`` / hop + * ``target_class`` against the current base_uri — + * ``CohortBuilder._class_uri_variants`` only tries data-namespace / + * ontology-namespace *rewrites* of whatever URI it's handed, it + * never rebuilds one from a bare name. So a stale ``.uri`` picked + * here gets saved verbatim into the rule and silently breaks class + * membership / target_class matching (0 members, 0 edges) even + * though the entity is otherwise picked correctly. Falls back to + * the object's own uri/iri/id fields only when no base_uri or no + * name is available (e.g. ontology not loaded yet). + */ + _currentUri(item) { + if (!item) return ''; + const name = item.name || item.label || ''; + const base = (this.baseUri || '').replace(/[#/]+$/, ''); + if (base && name) return `${base}#${name}`; + return item.uri || item.iri || item.id || ''; + }, + _populateClassSelect() { const sel = document.getElementById('cohortClassUri'); if (!sel) return; @@ -69,7 +105,7 @@ const CohortModule = { (a.label || a.name || a.uri || '').localeCompare(b.label || b.name || b.uri || '') ); for (const c of classes) { - const uri = c.uri || c.iri || c.id || ''; + const uri = this._currentUri(c); const label = c.label || c.name || uri; if (!uri) continue; const opt = document.createElement('option'); @@ -86,9 +122,11 @@ const CohortModule = { _classByUri(uri) { if (!uri) return null; - return (this.classes || []).find(cl => - (cl.uri || cl.iri || cl.id || '') === uri - ) || null; + // Match against the same reconstructed current-namespace URI + // used to populate the select (see _currentUri) — not each + // class's own possibly-stale .uri field — so lookups stay + // consistent with what's actually in ${viaProps.map(p => { - const uri = p.uri || p.iri || p.id || ''; + const uri = this._currentUri(p); const lbl = p.label || p.name || uri; return ``; }).join('')} @@ -794,7 +872,7 @@ const CohortModule = { ? 'pick predicate first' : (targetClasses.length ? '— entity —' : 'no compatible target')} ${targetClasses.map(c => { - const uri = c.uri || c.iri || c.id || ''; + const uri = this._currentUri(c); const lbl = c.label || c.name || uri; return ``; }).join('')} @@ -816,7 +894,7 @@ const CohortModule = { wrap.querySelector('.cohort-hop-via').onchange = (e) => { hop.via = e.target.value; const stillValid = this._compatibleTargetClasses(sourceUri, hop.via) - .some(c => (c.uri || c.iri || c.id || '') === hop.target_class); + .some(c => this._currentUri(c) === hop.target_class); if (!stillValid) hop.target_class = ''; this._renderLinks(); this.markDirty(); diff --git a/src/front/templates/partials/ontology/_ontology_cohorts.html b/src/front/templates/partials/ontology/_ontology_cohorts.html index 74caa1ad..43e3de3c 100644 --- a/src/front/templates/partials/ontology/_ontology_cohorts.html +++ b/src/front/templates/partials/ontology/_ontology_cohorts.html @@ -26,6 +26,11 @@

Cohorts

onclick="CohortModule.save()"> Save rule +