Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_dispatch:
pull_request:
push:
branches: [main]
Expand All @@ -16,8 +17,8 @@ jobs:
browser-contracts:
runs-on: ubuntu-latest
steps:
# actions/checkout v7.0.0, verified upstream 2026-06-18.
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
# actions/checkout v7.0.1, verified upstream 2026-07-31.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- run: npm run check

php:
Expand All @@ -27,7 +28,7 @@ jobs:
matrix:
php: ['8.2', '8.3', '8.4', '8.5']
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
# shivammathur/setup-php 2.37.2, verified upstream 2026-06-08.
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240
with:
Expand All @@ -52,7 +53,7 @@ jobs:
module-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240
with:
php-version: '8.4'
Expand All @@ -66,3 +67,20 @@ jobs:
unzip -q "$RUNNER_TEMP/omeka-s.zip" -d "$RUNNER_TEMP"
- name: Module classes declare against Omeka S
run: php scripts/check-module-contract.php "$RUNNER_TEMP/omeka-s"

embeddings:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
# actions/setup-python v6.3.0, verified upstream 2026-07-31.
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: '3.12'
cache: pip
cache-dependency-path: tools/embeddings/requirements.txt
- name: Install pinned embedding dependencies
run: python -m pip install -r tools/embeddings/requirements.txt
- name: Validate profile and embedding contracts
run: |
python tools/embeddings/build_embeddings.py --validate-profile
python -m unittest discover -s tools/embeddings/tests -v
117 changes: 117 additions & 0 deletions .github/workflows/embeddings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Build semantic embeddings

# Manual because the Gemini call is a paid, secret-backed precompute. Ordinary
# pull-request CI tests every pure contract without a key; this workflow fetches
# only the unauthenticated public Omeka API, incrementally refreshes vectors,
# opens a bot PR for the compact artifacts, and publishes full vectors as a
# versioned GitHub Release rather than committing them.
on:
workflow_dispatch:
inputs:
scope:
description: Embedding scope
required: true
type: choice
default: missing
options:
- missing
- all
publish_vectors:
description: Publish the full-vector GitHub Release
required: true
type: boolean
default: true

permissions:
contents: write
pull-requests: write

concurrency:
group: semantic-embeddings
cancel-in-progress: false

env:
GEMINI_EMBEDDING_MODEL: gemini-embedding-2
GEMINI_EMBEDDING_DIMS: '768'

jobs:
build:
runs-on: ubuntu-latest
steps:
# actions/checkout v7.0.1, verified upstream 2026-07-31.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

# actions/setup-python v6.3.0, verified upstream 2026-07-31.
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: '3.12'
cache: pip
cache-dependency-path: tools/embeddings/requirements.txt

# actions/cache v6.1.0, verified upstream 2026-07-31. A unique write key
# restores the newest compatible prefix and persists the refreshed cache.
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: asset/data/embeddings/cache.json
key: embeddings-${{ runner.os }}-${{ env.GEMINI_EMBEDDING_MODEL }}-${{ env.GEMINI_EMBEDDING_DIMS }}-${{ github.run_id }}
restore-keys: |
embeddings-${{ runner.os }}-${{ env.GEMINI_EMBEDDING_MODEL }}-${{ env.GEMINI_EMBEDDING_DIMS }}-

- name: Install pinned dependencies
run: python -m pip install -r tools/embeddings/requirements.txt

- name: Run embedding contract tests
run: |
python tools/embeddings/build_embeddings.py --validate-profile
python -m unittest discover -s tools/embeddings/tests -v

- name: Build public semantic artifacts
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
run: python tools/embeddings/build_embeddings.py --scope "${{ inputs.scope }}"

- name: Validate generated artifact set
run: python tools/embeddings/build_embeddings.py --validate-artifacts

# Save even when a later API batch fails: build_embeddings.py flushes each
# paid successful batch, so the next run can resume instead of rebilling it.
- name: Persist the incremental embedding cache
if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
path: asset/data/embeddings/cache.json
key: embeddings-${{ runner.os }}-${{ env.GEMINI_EMBEDDING_MODEL }}-${{ env.GEMINI_EMBEDDING_DIMS }}-${{ github.run_id }}

- name: Open bot PR for derived map and recommendations
env:
GH_TOKEN: ${{ github.token }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add asset/data/embeddings/map.json asset/data/embeddings/similar.json asset/data/embeddings/report.json
if git diff --staged --quiet; then
echo "No semantic artifact changes."
else
branch="automation/embeddings-${GITHUB_RUN_ID}"
git switch -c "$branch"
git commit -m "Semantic embeddings: refresh public derived artifacts"
git push --set-upstream origin "$branch"
gh pr create --base main --head "$branch" \
--title "Semantic embeddings: refresh public artifacts" \
--body "Automated public-only semantic-map and recommendation refresh from workflow run ${GITHUB_RUN_ID}."
fi

- name: Publish versioned full-vector release
if: ${{ inputs.publish_vectors }}
env:
GH_TOKEN: ${{ github.token }}
run: |
tag="semantic-embeddings-v1-${GITHUB_RUN_NUMBER}"
gh release create "$tag" \
asset/data/embeddings/release/vectors.f32 \
asset/data/embeddings/release/ids.json \
asset/data/embeddings/release/manifest.json \
--target "$GITHUB_SHA" \
--title "AMIRA semantic embeddings v1 · build ${GITHUB_RUN_NUMBER}" \
--notes "Public AMIRA vectors. Read manifest.json before indexing; embedding spaces are model-specific." \
--latest=false
3 changes: 2 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
# actions/checkout v7.0.1, verified upstream 2026-07-31.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/wordclouds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
# actions/checkout v7.0.0, verified upstream 2026-06-18.
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
# actions/checkout v7.0.1, verified upstream 2026-07-31.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

# actions/setup-python v6.3.0, verified upstream 2026-06-24.
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules/
.idea/
.vscode/
__pycache__/
.codex/

# Per-item knowledge-graph JSON (~6,000 files, ~188 MB) is regenerated in-Omeka
# via the admin "Regenerate now" job — not committed. The front-end falls back to
Expand Down Expand Up @@ -33,3 +34,8 @@ asset/data/communities/
# Additional install-specific outputs from the same precompute job.
asset/data/network-explorer.json
asset/data/featured-collections/

# Semantic embeddings: compact public map/recommendation/report JSON is
# committed, while the full-vector cache and release payload stay out of Git.
asset/data/embeddings/cache.json
asset/data/embeddings/release/
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

All notable changes are documented here. Versions follow Semantic Versioning.

## 2.24.0 — 2026-07-31

### Added

- **One multilingual semantic space across six public corpora.** The manual,
secret-backed embedding workflow creates uniformly bounded cards for podcasts,
YouTube videos, publications, projects, research sections, and research items;
incrementally embeds changed cards with `gemini-embedding-2`; projects them with
deterministic UMAP; reports quality/low-signal coverage; commits the compact
map and recommendation contracts; and publishes normalized 768-dimensional
float32 vectors as a versioned GitHub Release.
- **Semantic Map** site-page block and **Similar Items** resource-page block.
The map supports title search, resource-type/cluster colouring, accessible
controls, lazy ECharts loading, and embedding. Recommendations are progressive
enhancement and never surface low-signal records.
- CI now validates the six-corpus profile, card construction, public filtering,
incremental cache behaviour, recommendation eligibility, and vector release
schema alongside the PHP and browser-contract matrices.

### Changed

- Podcasts now include linked subjects, subject trends/co-occurrence, locations,
and an items-by-country choropleth. Transcript word clouds expose translated,
accessible language controls, while the PHP fallback performs a curated layer
of English/French inflection folding when the spaCy-built input is unavailable.
- Project and installation metadata use the canonical **DRE-Visualizations**
repository name throughout.

## 2.23.0 — 2026-07-30

### Fixed
Expand Down
15 changes: 10 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ the point; a static network is cheaper as an ECharts series.
npm run check # design-token contract + JS syntax sweep + registry/layout/embed contracts
docker run --rm -v "$PWD:/m" php:8.4-cli php /m/tests/AggregatorsTest.php # aggregator regressions
docker run --rm -v "$PWD:/m" php:8.4-cli php /m/tests/KnowledgeGraphsTest.php # graph builder regressions
python -m unittest discover -s tools/embeddings/tests -v # embedding contracts
```

The aggregators are dependency-free and unit-tested — add a mock-data case for
Expand Down Expand Up @@ -139,24 +140,28 @@ All visualizations load from precomputed JSON under `asset/data/`:
asset/data/
├── geo/countries.geojson # Natural Earth boundaries (choropleth) — committed INPUT
├── wordclouds/ # Lemmatised frequencies from the CI Action — committed INPUT
├── embeddings/ # Committed map/recommendations/report; vectors are release-only
├── communities/ # Multi-entity co-occurrence network (baked FA2 positions)
├── knowledge-graphs/ # One per item — gitignored, regenerated in-Omeka
├── photo-galleries/ # One per image-bearing item set — gitignored
└── item-dashboards/ # Dashboards + {type}-index.json + publications/podcasts/youtube/…
```

**Everything** regenerates inside Omeka via the admin **"Regenerate now"** button —
Installation-specific dashboard data regenerates inside Omeka via the admin **"Regenerate now"** button —
a pure-PHP engine under `src/Precompute/` (`DataLoader` → `Aggregators` /
`KnowledgeGraphs` → `Runner`) that reuses Omeka's own database connection. No
Python, shell access, or extra credentials — the module ships **zero** Python. The
`KnowledgeGraphs` → `Runner`) that reuses Omeka's own database connection. It needs no
Python, shell access, or extra credentials at runtime. The
knowledge-graph JSON (~6,000 files) is **not committed**; until the first run the
front-end falls back to a lighter live REST-API graph.

Two static **inputs** are the exception, produced outside Omeka and committed like
Static **inputs** are the exception, produced outside Omeka and committed like
`countries.geojson`: the `wordclouds/` frequencies come from the **Build word
clouds** GitHub Action (`tools/wordclouds/build_wordclouds.py`, spaCy
lemmatisation — PHP can't do it), read back by `Runner::wordCloudInput()` with an
in-PHP tokeniser fallback.
in-PHP tokeniser fallback. The **Build semantic embeddings** Action runs the
secret-backed Gemini precompute: only `map.json`, `similar.json`, and `report.json`
are committed, while full vectors and their manifest are published as a versioned
release. Its consumer contract is documented in `docs/SEMANTIC_EMBEDDINGS.md`.

The JS is modular — one vanilla-JS IIFE per concern (chart builders, controllers,
the `window.RV` core, registry, layouts). The authoritative per-file tree lives in
Expand Down
22 changes: 22 additions & 0 deletions Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,28 @@ public static function clientTranslations($view): array
'fullscreen' => $view->translate('Fullscreen'),
'exitFullscreen' => $view->translate('Exit fullscreen'),
'item' => $view->translate('item'),
'language' => $view->translate('Language'),
'words' => $view->translate('Words'),
'langAll' => $view->translate('All'),
'langEnglish' => $view->translate('English'),
'langFrench' => $view->translate('French'),
'langGerman' => $view->translate('German'),
'langPortuguese' => $view->translate('Portuguese'),
'semanticColorBy' => $view->translate('Colour by'),
'semanticType' => $view->translate('Resource type'),
'semanticCluster' => $view->translate('Semantic cluster'),
'semanticSearch' => $view->translate('Find a record on the map'),
'semanticSearchPlaceholder' => $view->translate('Search titles'),
'semanticMapAria' => $view->translate('Semantic map of public collection records. Nearby points have similar metadata and descriptions.'),
'semanticLowSignal' => $view->translate('Faint points have too little descriptive metadata for recommendations.'),
'semanticLoadError' => $view->translate('The semantic map is not available yet. Run the embeddings workflow and try again.'),
'semanticNoSearchResults' => $view->translate('No matching records.'),
'semanticSimilarity' => $view->translate('similar'),
'semanticMapTitle' => $view->translate('Semantic map'),
'semanticMapIntro' => $view->translate('Nearby records use similar language, subjects, places, and descriptions. The map joins every resource type in one multilingual space.'),
'semanticSharedSpace' => $view->translate('Shared semantic space'),
'semanticRecords' => $view->translate('records'),
'semanticLowSignalCount' => $view->translate('low-signal'),
];
}

Expand Down
Loading