Skip to content

Looking up grades by IPTC URI

A third-party module that reads a C2PA manifest off an uploaded file ends up with an IPTC digital source type URI, such as http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia. AiDisclosureGrade::getIptcDigitalSourceType() reads a URI FROM a grade, not the other way around, so ai_disclosure.grade_lookup is the service for the opposite direction: given a URI, which grades carry it.

A URI does not identify a single grade

Several grades can share the same URI, at different severities. This is the shipped mapping:

IPTC digital source type URI Grades that carry it
.../trainedAlgorithmicMedia ai_generated_autonomous (severity 70), ai_deepfake (severity 80)
.../compositeWithTrainedAlgorithmicMedia ai_summarized (severity 40), ai_partly_assisted (severity 50), ai_assisted_hitl (severity 60)
.../algorithmicallyEnhanced ai_translated (severity 30)

Every URI above is prefixed with http://cv.iptc.org/newscodes/digitalsourcetype/. The remaining shipped grades, ai_metadata, ai_rated and human_only, carry no IPTC URI at all.

Two formats, one method

use Drupal\ai_disclosure\GradeLookup;
use Drupal\ai_disclosure\GradeLookupFormat;

/** @var \Drupal\ai_disclosure\GradeLookup $lookup */
$lookup = \Drupal::service('ai_disclosure.grade_lookup');

// Entities, the default: the AiDisclosureGrade objects themselves.
$grades = $lookup->gradesForIptcUri('http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia');
foreach ($grades as $id => $grade) {
  // $id is the grade's machine name; $grade is the AiDisclosureGrade entity.
}

// Data: flat associative arrays, safe to serialize.
$data = $lookup->gradesForIptcUri(
  'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia',
  GradeLookupFormat::Data,
);
foreach ($data as $id => $grade) {
  // $grade is ['id' => ..., 'severity' => ..., 'label' => ..., 'label_required' => ...].
}

gradesForIptcUri() takes the URI exactly as given: no trimming, no case folding, no other normalization. The URI is an identifier, not free text.

Every case, both formats

Input Entities Data
URI on several grades, e.g. .../trainedAlgorithmicMedia 2 entities, keyed by ID, ordered by severity 2 arrays, same keys, same order
URI on a single grade, e.g. .../algorithmicallyEnhanced 1 entity, still inside an array 1 array, still inside an array
URI no shipped grade carries, e.g. .../digitalCapture Empty array Empty array
Empty string Empty array, nothing is loaded Empty array, nothing is loaded
URI with different spacing or case No match: the comparison is exact No match: the comparison is exact

A single match is still an array, not a shortcut value: a site can add a second grade on the same URI at any time.

The Data format's exact shape

Each value in a Data result is:

[
  'id' => string,             // the grade's machine name, redundant with the outer key
  'severity' => int,
  'label' => string,
  'label_required' => bool,
]

Outer keys are grade machine names, a stable API in both formats. Order in both formats is by severity ascending, then by grade ID: stable across calls, not a ranking. Picking which matching grade applies to a given file is the caller's job, with context the service does not have.

An unknown URI, an empty string, or a malformed URI all return an empty array, with no log entry and no exception.

Entities do not survive json_encode() or a queue

AiDisclosureGrade has no JsonSerializable implementation and its properties are protected. json_encode() on an Entities result gives empty objects, not grade data. Observed on a two-grade match:

{"ai_generated_autonomous":{},"ai_deepfake":{}}

A queue has the same problem twice over: serialization mangles the entity, and even a working entity captured on a queue item is stale by the time the worker runs, since the grade list can change in between. Put grade IDs, or the Data format, on the queue item, and have the worker re-read from storage rather than trust an entity that traveled through the queue.

Cacheability

$cache_tags = $lookup->getCacheTags();

getCacheTags() returns the grade list's own cache tags. The lookup itself is read-only and keeps no state across requests, but its answer depends on the grade configuration: a site that adds, edits or removes a grade changes which grades a URI maps to, without a module release. Attach these tags to whatever the caller builds from the lookup's result, so a cached page picks up that change.