fix language specs, remove bloating pseudocode

This commit is contained in:
@s.roertgen 2025-11-29 06:21:19 +01:00
parent 30aea8c65f
commit c55b4e503b

View file

@ -59,9 +59,8 @@ This is how we convert each property of the AMB:
- `description``["description", <value>]` - `description``["description", <value>]`
- `about` (array of concept objects) → Repeat for each: - `about` (array of concept objects) → Repeat for each:
- `["about:id", <uri>]` - `["about:id", <uri>]`
- `["about:prefLabel", <label>]` - `["about:prefLabel:lang", <label>]`
- `["about:type", "Concept"]` - `["about:type", "Concept"]`
- `["about:inLanguage", <language>]` (if applicable)
- `keywords``["t", <keyword>]` (repeat for each keyword, using Nostr `t` tag) - `keywords``["t", <keyword>]` (repeat for each keyword, using Nostr `t` tag)
- `inLanguage``["inLanguage", <languageCode>]` (repeat for each language) - `inLanguage``["inLanguage", <languageCode>]` (repeat for each language)
- `image``["image", <uri>]` - `image``["image", <uri>]`
@ -106,41 +105,36 @@ This is how we convert each property of the AMB:
- `["license:id", <license_uri>]` - `["license:id", <license_uri>]`
- `conditionsOfAccess` (Concept object) → - `conditionsOfAccess` (Concept object) →
- `["conditionsOfAccess:id", <uri>]` - `["conditionsOfAccess:id", <uri>]`
- `["conditionsOfAccess:prefLabel", <label>]` (optional) - `["conditionsOfAccess:prefLabel:lang", <label>]` (optional)
- `["conditionsOfAccess:type", "Concept"]` (optional) - `["conditionsOfAccess:type", "Concept"]` (optional)
- `["conditionsOfAccess:inLanguage", <language>]` (optional)
**Educational:** **Educational:**
- `learningResourceType` (array of Concept objects) → Repeat for each: - `learningResourceType` (array of Concept objects) → Repeat for each:
- `["learningResourceType:id", <uri>]` - `["learningResourceType:id", <uri>]`
- `["learningResourceType:prefLabel", <label>]` (optional) - `["learningResourceType:prefLabel:lang", <label>]` (optional)
- `["learningResourceType:type", "Concept"]` (optional) - `["learningResourceType:type", "Concept"]` (optional)
- `["learningResourceType:inLanguage", <language>]` (optional)
- `audience` (array of Concept objects) → Repeat for each: - `audience` (array of Concept objects) → Repeat for each:
- `["audience:id", <uri>]` - `["audience:id", <uri>]`
- `["audience:prefLabel", <label>]` (optional) - `["audience:prefLabel:lang", <label>]` (optional)
- `["audience:type", "Concept"]` (optional) - `["audience:type", "Concept"]` (optional)
- `["audience:inLanguage", <language>]` (optional)
- `teaches` (array of Concept objects) → Repeat for each: - `teaches` (array of Concept objects) → Repeat for each:
- `["teaches:id", <uri>]` - `["teaches:id", <uri>]`
- `["teaches:prefLabel", <label>]` (optional) - `["teaches:prefLabel:lang", <label>]` (optional)
- `assesses` (array of Concept objects) → Repeat for each: - `assesses` (array of Concept objects) → Repeat for each:
- `["assesses:id", <uri>]` - `["assesses:id", <uri>]`
- `["assesses:prefLabel", <label>]` (optional) - `["assesses:prefLabel:lang", <label>]` (optional)
- `competencyRequired` (array of Concept objects) → Repeat for each: - `competencyRequired` (array of Concept objects) → Repeat for each:
- `["competencyRequired:id", <uri>]` - `["competencyRequired:id", <uri>]`
- `["competencyRequired:prefLabel", <label>]` (optional) - `["competencyRequired:prefLabel:lang", <label>]` (optional)
- `educationalLevel` (array of Concept objects) → Repeat for each: - `educationalLevel` (array of Concept objects) → Repeat for each:
- `["educationalLevel:id", <uri>]` - `["educationalLevel:id", <uri>]`
- `["educationalLevel:prefLabel", <label>]` (optional) - `["educationalLevel:prefLabel:lang", <label>]` (optional)
- `["educationalLevel:type", "Concept"]` (optional) - `["educationalLevel:type", "Concept"]` (optional)
- `["educationalLevel:inLanguage", <language>]` (optional)
- `interactivityType` (Concept object) → - `interactivityType` (Concept object) →
- `["interactivityType:id", <uri>]` - `["interactivityType:id", <uri>]`
- `["interactivityType:prefLabel", <label>]` (optional) - `["interactivityType:prefLabel:lang", <label>]` (optional)
- `["interactivityType:type", "Concept"]` (optional) - `["interactivityType:type", "Concept"]` (optional)
- `["interactivityType:inLanguage", <language>]` (optional)
**Relations:** **Relations:**
@ -190,218 +184,6 @@ This is how we convert each property of the AMB:
- `["caption:encodingFormat", <format>]` (optional, IANA media type) - `["caption:encodingFormat", <format>]` (optional, IANA media type)
- `["caption:inLanguage", <languageCode>]` (optional) - `["caption:inLanguage", <languageCode>]` (optional)
## Conversion Guidelines
### AMB → Nostr Event (JavaScript)
```javascript
function flattenAMBToNostrTags(ambData) {
const tags = [];
function addTag(key, value) {
if (value !== null && value !== undefined && value !== '') {
tags.push([key, String(value)]);
}
}
function flattenObject(obj, prefix = '') {
for (const [key, value] of Object.entries(obj)) {
const fullKey = prefix ? `${prefix}:${key}` : key;
// Special case: id maps to 'd' tag
if (key === 'id' && !prefix) {
addTag('d', value);
continue;
}
// Special case: keywords map to 't' tags (Nostr-native)
if (key === 'keywords' && !prefix && Array.isArray(value)) {
value.forEach(keyword => {
addTag('t', keyword);
});
continue;
}
if (Array.isArray(value)) {
// Arrays: repeat the same key for each element
value.forEach(item => {
if (typeof item === 'object' && item !== null) {
flattenObject(item, fullKey);
} else {
addTag(fullKey, item);
}
});
} else if (typeof value === 'object' && value !== null) {
// Nested objects: flatten recursively
flattenObject(value, fullKey);
} else {
// Simple values
addTag(fullKey, value);
}
}
}
flattenObject(ambData);
return tags;
}
// Example usage:
const ambMetadata = {
id: "https://example.org/resource/123",
name: "Introduction to Physics",
creator: [
{
name: "Dr. Jane Smith",
type: "Person",
affiliation: {
name: "MIT",
type: "Organization"
}
}
],
keywords: ["Physics", "Education"]
};
const tags = flattenAMBToNostrTags(ambMetadata);
// Result (using Nostr-native conventions):
// [
// ["d", "https://example.org/resource/123"],
// ["name", "Introduction to Physics"],
// ["creator:name", "Dr. Jane Smith"],
// ["creator:type", "Person"],
// ["creator:affiliation:name", "MIT"],
// ["creator:affiliation:type", "Organization"],
// ["t", "Physics"],
// ["t", "Education"]
// ]
//
// Note: If creator had Nostr identity, would also include:
// ["p", "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "wss://relay.example.com", "creator"]
```
### Nostr Event → AMB Metadata (JavaScript)
```javascript
function unflattenNostrTagsToAMB(tags) {
const result = {};
// Group tags by their key
const tagGroups = {};
const pTags = []; // Collect p tags separately
tags.forEach(([key, ...values]) => {
// Special case: 'd' tag maps to 'id'
if (key === 'd') {
result.id = values[0];
return;
}
// Special case: 'p' tags with 'creator' marker map to creator.id
if (key === 'p') {
const [pubkey, relay, marker] = values;
if (marker === 'creator' || marker === 'contributor') {
pTags.push({ pubkey, relay, marker });
}
return;
}
// Special case: 't' tags map to 'keywords'
if (key === 't') {
if (!result.keywords) {
result.keywords = [];
}
result.keywords.push(values[0]);
return;
}
if (!tagGroups[key]) {
tagGroups[key] = [];
}
tagGroups[key].push(values);
});
// Process p tags (creator/contributor)
if (pTags.length > 0) {
result.creator = pTags.map(({ pubkey }) => ({
id: pubkey
}));
}
// Process each tag group
for (const [key, valuesList] of Object.entries(tagGroups)) {
const parts = key.split(':');
// Multiple occurrences of same key = array
if (valuesList.length > 1) {
// Check if this is an object array (has nested properties)
const hasNestedProps = Object.keys(tagGroups).some(k =>
k.startsWith(key + ':')
);
if (hasNestedProps) {
// Array of objects - reconstruct each object
const arrayResult = [];
valuesList.forEach(values => {
const obj = {};
setNestedValue(obj, parts, values[0]);
arrayResult.push(obj);
});
setNestedValue(result, parts, arrayResult);
} else {
// Simple array
setNestedValue(result, parts, valuesList.map(v => v[0]));
}
} else {
// Single occurrence
setNestedValue(result, parts, valuesList[0][0]);
}
}
return result;
}
function setNestedValue(obj, parts, value) {
const lastPart = parts[parts.length - 1];
let current = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[lastPart] = value;
}
// Example usage:
const nostrTags = [
["d", "https://example.org/resource/123"],
["name", "Introduction to Physics"],
["p", "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "wss://relay.example.com", "creator"],
["creator:affiliation:name", "MIT"],
["creator:affiliation:type", "Organization"],
["t", "Physics"],
["t", "Education"]
];
const ambMetadata = unflattenNostrTagsToAMB(nostrTags);
// Result:
// {
// id: "https://example.org/resource/123",
// name: "Introduction to Physics",
// creator: {
// // Note: p tag is decoded to get the pubkey
// id: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
// affiliation: {
// name: "MIT",
// type: "Organization"
// }
// },
// keywords: ["Physics", "Education"]
// }
```
## How to convert an AMB nostr-event to AMB metadata ## How to convert an AMB nostr-event to AMB metadata
To convert a Nostr event back to AMB metadata: To convert a Nostr event back to AMB metadata:
@ -416,93 +198,6 @@ To convert a Nostr event back to AMB metadata:
- Convert string booleans to actual booleans - Convert string booleans to actual booleans
- Parse ISO8601 dates if needed for validation - Parse ISO8601 dates if needed for validation
### Reconstruction Algorithm
```javascript
function reconstructAMB(nostrEvent) {
const amb = {
"@context": [
"https://w3id.org/kim/amb/context.jsonld",
{ "@language": "de" }
],
type: ["LearningResource"]
};
// Group tags by their base key (before first ':')
const tagsByBase = {};
nostrEvent.tags.forEach(tag => {
const [key, ...values] = tag;
const baseKey = key.split(':')[0];
if (!tagsByBase[baseKey]) {
tagsByBase[baseKey] = [];
}
tagsByBase[baseKey].push({ fullKey: key, values });
});
// Reconstruct each property
for (const [baseKey, tags] of Object.entries(tagsByBase)) {
if (baseKey === 'd') {
amb.id = tags[0].values[0];
continue;
}
// Check if this is a simple property or nested
const hasNested = tags.some(t => t.fullKey.includes(':'));
if (!hasNested) {
// Simple property
if (tags.length === 1) {
amb[baseKey] = tags[0].values[0];
} else {
// Multiple values = array
amb[baseKey] = tags.map(t => t.values[0]);
}
} else {
// Nested property - group by instance
amb[baseKey] = reconstructNestedProperty(tags);
}
}
return amb;
}
function reconstructNestedProperty(tags) {
// Group tags that belong to the same array instance
const instances = [];
let currentInstance = {};
let lastDepth = 0;
tags.forEach(tag => {
const parts = tag.fullKey.split(':');
const depth = parts.length;
// If depth decreased, we're starting a new instance
if (depth <= lastDepth && Object.keys(currentInstance).length > 0) {
instances.push(currentInstance);
currentInstance = {};
}
// Build nested structure
let current = currentInstance;
for (let i = 1; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = tag.values[0];
lastDepth = depth;
});
if (Object.keys(currentInstance).length > 0) {
instances.push(currentInstance);
}
return instances.length === 1 ? instances[0] : instances;
}
```
## How to query for AMB nostr-events in supporting relays ## How to query for AMB nostr-events in supporting relays
@ -532,17 +227,14 @@ Query capabilities should include:
["name", "Pythagorean Theorem Video"], ["name", "Pythagorean Theorem Video"],
["description", "An introductory video explaining the Pythagorean theorem"], ["description", "An introductory video explaining the Pythagorean theorem"],
["about:id", "http://w3id.org/kim/schulfaecher/s1017"], ["about:id", "http://w3id.org/kim/schulfaecher/s1017"],
["about:prefLabel", "Mathematik"], ["about:prefLabel:de", "Mathematik"],
["about:type", "Concept"], ["about:type", "Concept"],
["about:inLanguage", "de"],
["about:id", "http://w3id.org/kim/schulfaecher/s1005"], ["about:id", "http://w3id.org/kim/schulfaecher/s1005"],
["about:prefLabel", "Deutsch"], ["about:prefLabel:de", "Deutsch"],
["about:type", "Concept"], ["about:type", "Concept"],
["about:inLanguage", "de"],
["learningResourceType:id", "http://w3id.org/openeduhub/vocabs/new_lrt/7a6e9608-2554-4981-95dc-47ab9ba924de"], ["learningResourceType:id", "http://w3id.org/openeduhub/vocabs/new_lrt/7a6e9608-2554-4981-95dc-47ab9ba924de"],
["learningResourceType:prefLabel", "Video"], ["learningResourceType:prefLabel:de", "Video"],
["learningResourceType:type", "Concept"], ["learningResourceType:type", "Concept"],
["learningResourceType:inLanguage", "de"],
["t", "Pythagoras"], ["t", "Pythagoras"],
["t", "Geometrie"], ["t", "Geometrie"],
["t", "Mathematik"], ["t", "Mathematik"],
@ -587,15 +279,15 @@ Query capabilities should include:
["dateCreated", "2024-01-15"], ["dateCreated", "2024-01-15"],
["datePublished", "2024-02-01"], ["datePublished", "2024-02-01"],
["about:id", "https://w3id.org/kim/hochschulfaechersystematik/n079"], ["about:id", "https://w3id.org/kim/hochschulfaechersystematik/n079"],
["about:prefLabel", "Informatik"], ["about:prefLabel:de", "Informatik"],
["about:type", "Concept"], ["about:type", "Concept"],
["learningResourceType:id", "https://w3id.org/kim/hcrt/course"], ["learningResourceType:id", "https://w3id.org/kim/hcrt/course"],
["learningResourceType:prefLabel", "Course"], ["learningResourceType:prefLabel:de", "Kurs"],
["audience:id", "http://purl.org/dcx/lrmi-vocabs/educationalAudienceRole/student"], ["audience:id", "http://purl.org/dcx/lrmi-vocabs/educationalAudienceRole/student"],
["audience:prefLabel", "student"], ["audience:prefLabel:de", "Student"],
["audience:type", "Concept"], ["audience:type", "Concept"],
["educationalLevel:id", "https://w3id.org/kim/educationalLevel/level_06"], ["educationalLevel:id", "https://w3id.org/kim/educationalLevel/level_06"],
["educationalLevel:prefLabel", "Bachelor or equivalent"], ["educationalLevel:prefLabel:en", "Bachelor or equivalent"],
["inLanguage", "en"], ["inLanguage", "en"],
["license:id", "https://creativecommons.org/licenses/by-sa/4.0/"], ["license:id", "https://creativecommons.org/licenses/by-sa/4.0/"],
["isAccessibleForFree", "true"] ["isAccessibleForFree", "true"]
@ -607,17 +299,6 @@ Query capabilities should include:
## Tools ## Tools
### Decoding Nostr Identifiers with njump
[`njump`](https://njump.me/) is a helpful tool for decoding and navigating Nostr identifiers, particularly useful when working with `p` tags (pubkeys) and `a` tags (addressable events):
- **For `p` tag pubkeys**: Paste the hex pubkey or npub identifier at `https://njump.me/<pubkey-or-npub>` to view the profile and see all resources published by that user
- Example: `https://njump.me/79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798`
- Or: `https://njump.me/npub1...`
- **For `a` tag addressable events**: Use `https://njump.me/naddr1...` to view the specific event
- Example: `https://njump.me/naddr1qqqgcjsyup9qqqg6cjv` to navigate to an addressable resource
### Using `nak` to create AMB events ### Using `nak` to create AMB events
You can use [`nak`](https://github.com/fiatjaf/nak) to create AMB events with the flattened tag structure: You can use [`nak`](https://github.com/fiatjaf/nak) to create AMB events with the flattened tag structure:
@ -631,9 +312,8 @@ nak event \
--tag name="Pythagorean Theorem Video" \ --tag name="Pythagorean Theorem Video" \
--tag description="An introductory video" \ --tag description="An introductory video" \
--tag about:id="http://w3id.org/kim/schulfaecher/s1017" \ --tag about:id="http://w3id.org/kim/schulfaecher/s1017" \
--tag about:prefLabel="Mathematik" \ --tag about:prefLabel:de="Mathematik" \
--tag about:type="Concept" \ --tag about:type="Concept" \
--tag about:inLanguage="de" \
--tag t="Pythagoras" \ --tag t="Pythagoras" \
--tag t="Geometrie" \ --tag t="Geometrie" \
--tag inLanguage="de" \ --tag inLanguage="de" \
@ -648,13 +328,6 @@ nak event \
--tag creator:affiliation:name="MIT" \ --tag creator:affiliation:name="MIT" \
--tag creator:affiliation:type="Organization" --tag creator:affiliation:type="Organization"
# Example 3: Resource with relation to another AMB event (using a tag)
nak event \
--kind 30142 \
--tag d="https://example.org/resource/789" \
--tag name="Physics Module Part 1" \
--tag a="30142:79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798:physics-course;wss://relay.example.com;isPartOf"
```
## References ## References