PortfolioFingerprintService.java
package com.taxonomy.portfolio.service;
import com.taxonomy.catalog.service.TaxonomyService;
import com.taxonomy.dto.TaxonomyDataFingerprint;
import com.taxonomy.shared.service.PromptTemplateService;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/** Creates stable fingerprints needed to interpret and reproduce analysis snapshots. */
@Service
public class PortfolioFingerprintService {
private final TaxonomyService taxonomyService;
private final PromptTemplateService promptTemplateService;
public PortfolioFingerprintService(TaxonomyService taxonomyService,
PromptTemplateService promptTemplateService) {
this.taxonomyService = taxonomyService;
this.promptTemplateService = promptTemplateService;
}
/**
* Fingerprints the same semantic fields persisted with an analysis, without mapping relations.
* Parent identity and analysis role are therefore part of the reproducibility contract.
*/
public String taxonomyFingerprint() {
return TaxonomyDataFingerprint.sha256(taxonomyService.getFingerprintTree());
}
public String promptFingerprint() {
StringBuilder canonical = new StringBuilder();
promptTemplateService.getAllTemplateCodes().stream()
.sorted()
.forEach(code -> canonical
.append(code).append('\u001f')
.append(promptTemplateService.getTemplate(code))
.append('\n'));
return sha256(canonical.toString());
}
public String contentFingerprint(String content) {
return sha256(content != null ? content : "");
}
private static String sha256(String value) {
try {
byte[] digest = MessageDigest.getInstance("SHA-256")
.digest(value.getBytes(StandardCharsets.UTF_8));
return java.util.HexFormat.of().formatHex(digest);
} catch (NoSuchAlgorithmException impossible) {
throw new IllegalStateException("SHA-256 is required by the Java platform", impossible);
}
}
}