ArchitectureSummaryService.java
package com.taxonomy.architecture.service;
import com.taxonomy.dto.ArchitectureSummary;
import com.taxonomy.dto.ArchitectureSummary.NextStep;
import com.taxonomy.dto.ArchitectureSummary.SummaryEntry;
import com.taxonomy.catalog.model.TaxonomyNode;
import com.taxonomy.relations.repository.RelationProposalRepository;
import com.taxonomy.relations.repository.RelationHypothesisRepository;
import com.taxonomy.relations.repository.RequirementCoverageRepository;
import com.taxonomy.catalog.repository.TaxonomyNodeRepository;
import com.taxonomy.catalog.repository.TaxonomyRelationRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;
/**
* Produces a compact, actionable architecture summary.
*
* <p>The summary aggregates data from nodes, relations, proposals,
* hypotheses, and coverage to provide an at-a-glance view of the
* current architecture state, plus recommended next steps.
*/
@Service
public class ArchitectureSummaryService {
private static final int TOP_N = 5;
private final TaxonomyNodeRepository nodeRepository;
private final TaxonomyRelationRepository relationRepository;
private final RelationProposalRepository proposalRepository;
private final RelationHypothesisRepository hypothesisRepository;
private final RequirementCoverageRepository coverageRepository;
public ArchitectureSummaryService(TaxonomyNodeRepository nodeRepository,
TaxonomyRelationRepository relationRepository,
RelationProposalRepository proposalRepository,
RelationHypothesisRepository hypothesisRepository,
RequirementCoverageRepository coverageRepository) {
this.nodeRepository = nodeRepository;
this.relationRepository = relationRepository;
this.proposalRepository = proposalRepository;
this.hypothesisRepository = hypothesisRepository;
this.coverageRepository = coverageRepository;
}
/**
* Build the architecture summary from current data.
*/
@Transactional(readOnly = true)
public ArchitectureSummary buildSummary() {
List<TaxonomyNode> allNodes = nodeRepository.findAll();
int totalRelations = (int) relationRepository.count();
int totalProposals = (int) proposalRepository.count();
int totalHypotheses = (int) hypothesisRepository.count();
// Coverage stats
Map<String, Integer> coverageCounts = new HashMap<>();
coverageRepository.findNodeCodeCountPairs().forEach(pair -> {
String code = (String) pair[0];
long count = (Long) pair[1];
coverageCounts.put(code, (int) count);
});
int coveredNodes = coverageCounts.size();
// Classify nodes by taxonomy root
Map<String, List<TaxonomyNode>> byRoot = allNodes.stream()
.filter(n -> n.getTaxonomyRoot() != null)
.collect(Collectors.groupingBy(TaxonomyNode::getTaxonomyRoot));
List<SummaryEntry> topCapabilities = topByRelations(byRoot.getOrDefault("CP", List.of()), coverageCounts);
List<SummaryEntry> topProcesses = topByRelations(byRoot.getOrDefault("BP", List.of()), coverageCounts);
List<SummaryEntry> topServices = topByRelations(byRoot.getOrDefault("CR", List.of()), coverageCounts);
// Hub nodes
List<SummaryEntry> hubNodes = allNodes.stream()
.filter(n -> "hub".equals(n.getGraphRole()))
.sorted(Comparator.comparingInt(TaxonomyNode::getTotalRelationCount).reversed())
.limit(TOP_N)
.map(n -> new SummaryEntry(n.getCode(), n.getNameEn(), n.getTotalRelationCount()))
.toList();
// Gaps: nodes with no coverage
List<String> gaps = allNodes.stream()
.filter(n -> n.getLevel() > 1)
.filter(n -> !coverageCounts.containsKey(n.getCode()))
.filter(n -> n.getTotalRelationCount() > 0)
.sorted(Comparator.comparingInt(TaxonomyNode::getTotalRelationCount).reversed())
.limit(TOP_N)
.map(n -> n.getCode() + " (" + n.getNameEn() + ")")
.toList();
// Next steps
List<NextStep> nextSteps = buildNextSteps(totalRelations, totalProposals, totalHypotheses, coveredNodes);
return new ArchitectureSummary(
Instant.now(),
allNodes.size(),
totalRelations,
totalProposals,
totalHypotheses,
coveredNodes,
topCapabilities,
topProcesses,
topServices,
gaps,
hubNodes,
nextSteps
);
}
// ── Helpers ──────────────────────────────────────────────────────
private List<SummaryEntry> topByRelations(List<TaxonomyNode> nodes, Map<String, Integer> coverageCounts) {
return nodes.stream()
.sorted(Comparator.comparingInt((TaxonomyNode n) ->
n.getTotalRelationCount() + coverageCounts.getOrDefault(n.getCode(), 0)).reversed())
.limit(TOP_N)
.map(n -> new SummaryEntry(
n.getCode(),
n.getNameEn(),
n.getTotalRelationCount() + coverageCounts.getOrDefault(n.getCode(), 0)))
.toList();
}
private List<NextStep> buildNextSteps(int relations, int proposals, int hypotheses, int covered) {
List<NextStep> steps = new ArrayList<>();
if (relations == 0) {
steps.add(new NextStep("Import Relations",
"No relations found. Import or create relations to enable graph analysis.",
"/api/relations"));
}
if (proposals > 0) {
steps.add(new NextStep("Review Proposals",
proposals + " pending relation proposal(s). Accept or reject them to improve the architecture graph.",
"/api/proposals"));
}
if (hypotheses > 0) {
String noun = hypotheses == 1 ? "hypothesis" : "hypotheses";
steps.add(new NextStep("Evaluate Hypotheses",
hypotheses + " " + noun + " waiting for review.",
"/api/dsl/hypotheses"));
}
if (covered == 0) {
steps.add(new NextStep("Analyze Requirements",
"No requirement coverage found. Analyze a business requirement to map it against the taxonomy.",
"/api/analyze"));
}
steps.add(new NextStep("Explore Graph",
"Use the graph explorer to visualize upstream/downstream dependencies.",
"/api/graph"));
steps.add(new NextStep("Export Architecture",
"Export the current architecture as ArchiMate, Mermaid, or Visio diagram.",
"/api/report"));
return steps;
}
}