Docs/Skills and code/src/

src/concept-store.js

Code148 lines2.9 KBsrc/

import { ensureAnalysisSchema, db } from "./analysis-schema.js";
import { normalizeText } from "./metadata-utils.js";

ensureAnalysisSchema();

function findConcept(ecosystem, normalizedName, conceptType) {
  return db.prepare(`
    SELECT id, canonical_name, concept_type
    FROM concepts
    WHERE ecosystem = ?
      AND normalized_name = ?
      AND concept_type = ?
  `).get(ecosystem, normalizedName, conceptType);
}

function findAlias(ecosystem, normalizedAlias) {
  return db.prepare(`
    SELECT c.id, c.canonical_name, c.concept_type
    FROM concept_aliases a
    JOIN concepts c ON c.id = a.concept_id
    WHERE c.ecosystem = ?
      AND a.normalized_alias = ?
    LIMIT 1
  `).get(ecosystem, normalizedAlias);
}

export function upsertConcept({
  ecosystem,
  name,
  type = "theme",
  description = null,
  source = "extraction"
}) {
  const canonicalName = String(name ?? "").trim();
  const normalizedName = normalizeText(canonicalName);

  if (!canonicalName || !normalizedName) return null;

  const existing = findConcept(ecosystem, normalizedName, type) ??
    findAlias(ecosystem, normalizedName);

  const now = new Date().toISOString();
  let concept = existing;

  if (!concept) {
    const result = db.prepare(`
      INSERT INTO concepts (
        ecosystem,
        canonical_name,
        normalized_name,
        concept_type,
        description,
        created_at,
        updated_at
      )
      VALUES (?, ?, ?, ?, ?, ?, ?)
    `).run(
      ecosystem,
      canonicalName,
      normalizedName,
      type,
      description,
      now,
      now
    );

    concept = {
      id: Number(result.lastInsertRowid),
      canonical_name: canonicalName,
      concept_type: type
    };
  } else {
    db.prepare(`
      UPDATE concepts
      SET updated_at = ?,
          description = COALESCE(description, ?)
      WHERE id = ?
    `).run(now, description, concept.id);
  }

  db.prepare(`
    INSERT OR IGNORE INTO concept_aliases (
      concept_id,
      alias,
      normalized_alias,
      source,
      created_at
    )
    VALUES (?, ?, ?, ?, ?)
  `).run(
    concept.id,
    canonicalName,
    normalizedName,
    source,
    now
  );

  return concept;
}

export function attachConceptToChannel({
  channelUrl,
  conceptId,
  source,
  confidence = null
}) {
  db.prepare(`
    INSERT OR REPLACE INTO channel_concepts (
      channel_url,
      concept_id,
      source,
      confidence,
      created_at
    )
    VALUES (?, ?, ?, ?, ?)
  `).run(
    channelUrl,
    conceptId,
    source,
    confidence,
    new Date().toISOString()
  );
}

export function attachConceptToVideo({
  videoId,
  conceptId,
  source,
  confidence = null
}) {
  db.prepare(`
    INSERT OR REPLACE INTO video_concepts (
      video_id,
      concept_id,
      source,
      confidence,
      created_at
    )
    VALUES (?, ?, ?, ?, ?)
  `).run(
    videoId,
    conceptId,
    source,
    confidence,
    new Date().toISOString()
  );
}

Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.