Skip to content

feat: implement RDFVocabularyMapper for ExoRDF to RDF/RDFS mapping#406

Merged
kitelev merged 1 commit intomainfrom
feature/rdf-rdfs-mapping
Nov 12, 2025
Merged

feat: implement RDFVocabularyMapper for ExoRDF to RDF/RDFS mapping#406
kitelev merged 1 commit intomainfrom
feature/rdf-rdfs-mapping

Conversation

@kitelev
Copy link
Owner

@kitelev kitelev commented Nov 12, 2025

Summary

Implements RDFVocabularyMapper for semantic interoperability between ExoRDF and W3C RDF/RDFS standards.

Changes

New Classes

  • RDFVocabularyMapper: Maps ExoRDF concepts to RDF/RDFS standards
    • generateClassHierarchyTriples(): exo:Asset → rdfs:Resource, ems:Task → exo:Asset, etc.
    • generatePropertyHierarchyTriples(): exo:Instance_class → rdf:type, etc.
    • generateMappedTriple(): Generate RDF/RDFS triple for individual property
    • hasMappingFor(): Check if property has mapping

Testing

  • 17 comprehensive test cases covering:
    • Class hierarchy generation (6 classes mapped)
    • Property hierarchy generation (6 properties mapped)
    • Individual property mapping with string/IRI values
    • Unmapped properties returning null

Design Patterns

  • Storage-agnostic implementation
  • Follows ExoRDF-Mapping.md specification
  • TDD methodology (tests written first)
  • Exported from core package for reusability

Test Coverage

All tests passing:

  • ✅ Unit tests: 803 tests
  • ✅ Component tests: 333 tests
  • ✅ UI tests: All passing
  • ✅ BDD coverage: ≥80%

Related Issues

Closes #367

Next Steps

Implements class and property hierarchy mapping between ExoRDF and W3C RDF/RDFS standards.

**Changes:**
- Add RDFVocabularyMapper with class hierarchy generation (exo:Asset → rdfs:Resource)
- Add property hierarchy generation (exo:Instance_class → rdf:type)
- Add individual property mapping with generateMappedTriple()
- Add mapping detection utility hasMappingFor()
- Export RDFVocabularyMapper from core package

**Testing:**
- 17 comprehensive test cases covering all mapping scenarios
- Tests for unmapped properties returning null
- Tests for string and IRI value handling
- All tests passing locally

**Implementation follows:**
- ExoRDF-Mapping.md specification
- TDD methodology (tests written first)
- Storage-agnostic design pattern

Related: #367
Copilot AI review requested due to automatic review settings November 12, 2025 15:50
@kitelev kitelev enabled auto-merge (squash) November 12, 2025 15:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements RDFVocabularyMapper to provide semantic interoperability between Exocortex's ExoRDF framework and W3C RDF/RDFS standards. The mapper generates triples that express class and property hierarchies, enabling standard SPARQL queries and semantic web tool compatibility.

  • Core functionality includes mapping 6 ExoRDF classes and 6 properties to their RDF/RDFS counterparts
  • Follows the ExoRDF-Mapping.md specification for subclass/subproperty relationships
  • Storage-agnostic implementation with comprehensive test coverage

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
packages/core/src/infrastructure/rdf/RDFVocabularyMapper.ts Implements RDFVocabularyMapper with methods for generating class/property hierarchy triples and mapping individual properties
packages/core/tests/unit/infrastructure/rdf/RDFVocabularyMapper.test.ts Adds 17 comprehensive test cases covering all mapping functionality
packages/core/src/index.ts Exports RDFVocabularyMapper from core package for reusability

import { Triple } from "../../domain/models/rdf/Triple";
import { IRI } from "../../domain/models/rdf/IRI";
import { Namespace } from "../../domain/models/rdf/Namespace";

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RDFVocabularyMapper class lacks JSDoc documentation. According to the project's documentation standards (see NoteToRDFConverter as an example), public classes and methods should include JSDoc comments with descriptions, parameters, return types, and usage examples. This is especially important for a core infrastructure component that will be exported and used by other packages.

Consider adding documentation like:

/**
 * Maps ExoRDF vocabulary to W3C RDF/RDFS standards for semantic interoperability.
 * 
 * Generates triples that express the relationship between ExoRDF classes/properties
 * and their RDF/RDFS superclasses/superproperties according to the ExoRDF-Mapping specification.
 * 
 * @example
 * ```typescript
 * const mapper = new RDFVocabularyMapper();
 * const classTriples = mapper.generateClassHierarchyTriples();
 * const propertyTriples = mapper.generatePropertyHierarchyTriples();
 * ```
 */
export class RDFVocabularyMapper {
  // ...
}
Suggested change
/**
* Maps ExoRDF vocabulary to W3C RDF/RDFS standards for semantic interoperability.
*
* Generates triples that express the relationship between ExoRDF classes/properties
* and their RDF/RDFS superclasses/superproperties according to the ExoRDF-Mapping specification.
*
* @example
* ```typescript
* const mapper = new RDFVocabularyMapper();
* const classTriples = mapper.generateClassHierarchyTriples();
* const propertyTriples = mapper.generatePropertyHierarchyTriples();
* ```
*/

Copilot uses AI. Check for mistakes.

return triples;
}

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generatePropertyHierarchyTriples() method lacks JSDoc documentation. Public methods should be documented with descriptions, return types, and examples.

Consider adding:

/**
 * Generates RDF triples that define the property hierarchy mappings between
 * ExoRDF properties and their RDF/RDFS superproperties.
 * 
 * @returns Array of triples expressing rdfs:subPropertyOf relationships
 * 
 * @example
 * ```typescript
 * const triples = mapper.generatePropertyHierarchyTriples();
 * // Returns triples like: exo:Instance_class rdfs:subPropertyOf rdf:type
 * ```
 */
Suggested change
/**
* Generates RDF triples that define the property hierarchy mappings between
* ExoRDF properties and their RDF/RDFS superproperties.
*
* @returns {Triple[]} Array of triples expressing rdfs:subPropertyOf relationships
*
* @example
* ```typescript
* const mapper = new RDFVocabularyMapper();
* const triples = mapper.generatePropertyHierarchyTriples();
* // Returns triples like: exo:Instance_class rdfs:subPropertyOf rdf:type
* ```
*/

Copilot uses AI. Check for mistakes.

return triples;
}

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateMappedTriple() method lacks JSDoc documentation. This is a key public API method that requires clear documentation explaining its purpose, parameters, return value, and edge cases.

Consider adding:

/**
 * Generates an RDF/RDFS triple for an individual ExoRDF property value.
 * 
 * Maps ExoRDF property names to their RDF/RDFS equivalents and creates
 * a triple using the mapped predicate. Returns null if no mapping exists.
 * 
 * @param subject - The subject IRI for the triple
 * @param exoProperty - ExoRDF property name (e.g., "exo__Instance_class")
 * @param value - Property value as string or IRI
 * @returns Triple with mapped RDF/RDFS predicate, or null if unmapped
 * 
 * @example
 * ```typescript
 * const triple = mapper.generateMappedTriple(
 *   new IRI("https://example.org/asset"),
 *   "exo__Instance_class",
 *   "ems__Task"
 * );
 * // Returns: <asset> rdf:type ems:Task
 * ```
 */
Suggested change
/**
* Generates an RDF/RDFS triple for an individual ExoRDF property value.
*
* Maps ExoRDF property names to their RDF/RDFS equivalents and creates
* a triple using the mapped predicate. Returns null if no mapping exists.
*
* @param subject - The subject IRI for the triple
* @param exoProperty - ExoRDF property name (e.g., "exo__Instance_class")
* @param value - Property value as string or IRI
* @returns Triple with mapped RDF/RDFS predicate, or null if unmapped
*
* @example
* ```typescript
* const triple = mapper.generateMappedTriple(
* new IRI("https://example.org/asset"),
* "exo__Instance_class",
* "ems__Task"
* );
* // Returns: <asset> rdf:type ems:Task
* ```
*/

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +70
const triples: Triple[] = [];

triples.push(
new Triple(
Namespace.EXO.term("Asset"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDFS.term("Resource"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Class"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDFS.term("Class"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Property"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDF.term("Property"),
),
);

triples.push(
new Triple(
Namespace.EMS.term("Task"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);

triples.push(
new Triple(
Namespace.EMS.term("Project"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);

triples.push(
new Triple(
Namespace.EMS.term("Area"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);

return triples;
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The repetitive triples.push(new Triple(...)) pattern in generateClassHierarchyTriples() could be refactored to improve maintainability. Consider using a data-driven approach:

generateClassHierarchyTriples(): Triple[] {
  const mappings = [
    { subject: "Asset", object: Namespace.RDFS.term("Resource") },
    { subject: "Class", object: Namespace.RDFS.term("Class") },
    { subject: "Property", object: Namespace.RDF.term("Property"), namespace: Namespace.EXO },
    { subject: "Task", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
    { subject: "Project", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
    { subject: "Area", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
  ];
  
  return mappings.map(({ subject, object, namespace = Namespace.EXO }) =>
    new Triple(
      namespace.term(subject),
      Namespace.RDFS.term("subClassOf"),
      object
    )
  );
}

This reduces code duplication and makes it easier to add or modify mappings.

Suggested change
const triples: Triple[] = [];
triples.push(
new Triple(
Namespace.EXO.term("Asset"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDFS.term("Resource"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Class"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDFS.term("Class"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Property"),
Namespace.RDFS.term("subClassOf"),
Namespace.RDF.term("Property"),
),
);
triples.push(
new Triple(
Namespace.EMS.term("Task"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);
triples.push(
new Triple(
Namespace.EMS.term("Project"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);
triples.push(
new Triple(
Namespace.EMS.term("Area"),
Namespace.RDFS.term("subClassOf"),
Namespace.EXO.term("Asset"),
),
);
return triples;
const mappings = [
{ subject: "Asset", object: Namespace.RDFS.term("Resource"), namespace: Namespace.EXO },
{ subject: "Class", object: Namespace.RDFS.term("Class"), namespace: Namespace.EXO },
{ subject: "Property", object: Namespace.RDF.term("Property"), namespace: Namespace.EXO },
{ subject: "Task", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
{ subject: "Project", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
{ subject: "Area", object: Namespace.EXO.term("Asset"), namespace: Namespace.EMS },
];
return mappings.map(({ subject, object, namespace }) =>
new Triple(
namespace.term(subject),
Namespace.RDFS.term("subClassOf"),
object
)
);

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +124
const triples: Triple[] = [];

triples.push(
new Triple(
Namespace.EXO.term("Instance_class"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDF.term("type"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Asset_isDefinedBy"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("isDefinedBy"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Class_superClass"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("subClassOf"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Property_range"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("range"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Property_domain"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("domain"),
),
);

triples.push(
new Triple(
Namespace.EXO.term("Property_superProperty"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("subPropertyOf"),
),
);

return triples;
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The repetitive triples.push(new Triple(...)) pattern in generatePropertyHierarchyTriples() could be refactored to improve maintainability. Consider using a data-driven approach:

generatePropertyHierarchyTriples(): Triple[] {
  const mappings = [
    { subject: "Instance_class", object: Namespace.RDF.term("type") },
    { subject: "Asset_isDefinedBy", object: Namespace.RDFS.term("isDefinedBy") },
    { subject: "Class_superClass", object: Namespace.RDFS.term("subClassOf") },
    { subject: "Property_range", object: Namespace.RDFS.term("range") },
    { subject: "Property_domain", object: Namespace.RDFS.term("domain") },
    { subject: "Property_superProperty", object: Namespace.RDFS.term("subPropertyOf") },
  ];
  
  return mappings.map(({ subject, object }) =>
    new Triple(
      Namespace.EXO.term(subject),
      Namespace.RDFS.term("subPropertyOf"),
      object
    )
  );
}

This reduces code duplication, aligns with the pattern used in the propertyMappings Map, and makes it easier to maintain consistency between the two.

Suggested change
const triples: Triple[] = [];
triples.push(
new Triple(
Namespace.EXO.term("Instance_class"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDF.term("type"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Asset_isDefinedBy"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("isDefinedBy"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Class_superClass"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("subClassOf"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Property_range"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("range"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Property_domain"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("domain"),
),
);
triples.push(
new Triple(
Namespace.EXO.term("Property_superProperty"),
Namespace.RDFS.term("subPropertyOf"),
Namespace.RDFS.term("subPropertyOf"),
),
);
return triples;
const mappings = [
{ subject: "Instance_class", object: Namespace.RDF.term("type") },
{ subject: "Asset_isDefinedBy", object: Namespace.RDFS.term("isDefinedBy") },
{ subject: "Class_superClass", object: Namespace.RDFS.term("subClassOf") },
{ subject: "Property_range", object: Namespace.RDFS.term("range") },
{ subject: "Property_domain", object: Namespace.RDFS.term("domain") },
{ subject: "Property_superProperty", object: Namespace.RDFS.term("subPropertyOf") },
];
return mappings.map(({ subject, object }) =>
new Triple(
Namespace.EXO.term(subject),
Namespace.RDFS.term("subPropertyOf"),
object
)
);

Copilot uses AI. Check for mistakes.
["exo__Property_superProperty", Namespace.RDFS.term("subPropertyOf")],
]);
}

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateClassHierarchyTriples() method lacks JSDoc documentation. Public methods should be documented with descriptions, return types, and examples.

Consider adding:

/**
 * Generates RDF triples that define the class hierarchy mappings between
 * ExoRDF classes and their RDF/RDFS superclasses.
 * 
 * @returns Array of triples expressing rdfs:subClassOf relationships
 * 
 * @example
 * ```typescript
 * const triples = mapper.generateClassHierarchyTriples();
 * // Returns triples like: ems:Task rdfs:subClassOf exo:Asset
 * ```
 */
Suggested change
/**
* Generates RDF triples that define the class hierarchy mappings between
* ExoRDF classes and their RDF/RDFS superclasses.
*
* @returns {Triple[]} Array of triples expressing rdfs:subClassOf relationships
*
* @example
* const mapper = new RDFVocabularyMapper();
* const triples = mapper.generateClassHierarchyTriples();
* // Returns triples like: ems:Task rdfs:subClassOf exo:Asset
*/

Copilot uses AI. Check for mistakes.

return new Triple(subject, rdfPredicate, objectIRI);
}

Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hasMappingFor() method lacks JSDoc documentation. Public methods should be documented with descriptions, parameters, return types, and examples.

Consider adding:

/**
 * Checks if a mapping exists for the given ExoRDF property.
 * 
 * @param property - ExoRDF property name (e.g., "exo__Instance_class")
 * @returns true if mapping exists, false otherwise
 * 
 * @example
 * ```typescript
 * if (mapper.hasMappingFor("exo__Instance_class")) {
 *   // Property can be mapped to RDF/RDFS
 * }
 * ```
 */
Suggested change
/**
* Checks if a mapping exists for the given ExoRDF property.
*
* @param property - ExoRDF property name (e.g., "exo__Instance_class")
* @returns true if mapping exists, false otherwise
*
* @example
* ```typescript
* if (mapper.hasMappingFor("exo__Instance_class")) {
* // Property can be mapped to RDF/RDFS
* }
* ```
*/

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,261 @@
import { RDFVocabularyMapper } from "../../../../src/infrastructure/rdf/RDFVocabularyMapper";
import { Namespace } from "../../../../src/domain/models/rdf/Namespace";
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import Namespace.

Suggested change
import { Namespace } from "../../../../src/domain/models/rdf/Namespace";

Copilot uses AI. Check for mistakes.
@kitelev kitelev merged commit f0893f2 into main Nov 12, 2025
14 checks passed
@kitelev kitelev deleted the feature/rdf-rdfs-mapping branch November 12, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Integrate ExoRDF to RDF/RDFS Mapping into SPARQL Triple Store

2 participants