Files
visualizador_instanciados/backend_go/graph_snapshot.go
2026-03-06 15:35:04 -03:00

209 lines
4.3 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
graphqueries "visualizador_instanciados/backend_go/graph_queries"
)
const rdfsLabelIRI = "http://www.w3.org/2000/01/rdf-schema#label"
func fetchGraphSnapshot(
ctx context.Context,
sparql *AnzoGraphClient,
cfg Config,
nodeLimit int,
edgeLimit int,
graphQueryID string,
) (GraphResponse, error) {
def, ok := graphqueries.Get(graphQueryID)
if !ok {
return GraphResponse{}, fmt.Errorf("unknown graph_query_id: %s", graphQueryID)
}
edgesQ := def.EdgeQuery(edgeLimit, cfg.IncludeBNodes)
raw, err := sparql.Query(ctx, edgesQ)
if err != nil {
return GraphResponse{}, err
}
var res sparqlResponse
if err := json.Unmarshal(raw, &res); err != nil {
return GraphResponse{}, fmt.Errorf("failed to parse SPARQL JSON: %w", err)
}
nodes, edges := graphFromSparqlBindings(res.Results.Bindings, nodeLimit, cfg.IncludeBNodes)
// Layout: invert edges for hierarchy (target -> source).
hierEdges := make([][2]int, 0, len(edges))
for _, e := range edges {
hierEdges = append(hierEdges, [2]int{e.Target, e.Source})
}
layers, cycleErr := levelSynchronousKahnLayers(len(nodes), hierEdges)
if cycleErr != nil {
sample := make([]string, 0, 20)
for _, nid := range cycleErr.RemainingNodeIDs {
if len(sample) >= 20 {
break
}
if nid >= 0 && nid < len(nodes) {
sample = append(sample, nodes[nid].IRI)
}
}
cycleErr.RemainingIRISample = sample
return GraphResponse{}, cycleErr
}
idToIRI := make([]string, len(nodes))
for i := range nodes {
idToIRI[i] = nodes[i].IRI
}
for _, layer := range layers {
sortLayerByIRI(layer, idToIRI)
}
xs, ys := radialPositionsFromLayers(len(nodes), layers, 5000.0)
for i := range nodes {
nodes[i].X = xs[i]
nodes[i].Y = ys[i]
}
// Attach labels for URI nodes.
iris := make([]string, 0)
for _, n := range nodes {
if n.TermType == "uri" && n.IRI != "" {
iris = append(iris, n.IRI)
}
}
if len(iris) > 0 {
labelByIRI, err := fetchRDFSLabels(ctx, sparql, iris, 500)
if err != nil {
return GraphResponse{}, err
}
for i := range nodes {
if nodes[i].TermType != "uri" {
continue
}
lbl, ok := labelByIRI[nodes[i].IRI]
if !ok {
continue
}
val := lbl
nodes[i].Label = &val
}
}
meta := &GraphMeta{
Backend: "anzograph",
TTLPath: nil,
SparqlEndpoint: cfg.EffectiveSparqlEndpoint(),
IncludeBNodes: cfg.IncludeBNodes,
GraphQueryID: graphQueryID,
NodeLimit: nodeLimit,
EdgeLimit: edgeLimit,
Nodes: len(nodes),
Edges: len(edges),
}
return GraphResponse{Nodes: nodes, Edges: edges, Meta: meta}, nil
}
type bestLabel struct {
score int
value string
}
func fetchRDFSLabels(
ctx context.Context,
sparql *AnzoGraphClient,
iris []string,
batchSize int,
) (map[string]string, error) {
best := make(map[string]bestLabel)
for i := 0; i < len(iris); i += batchSize {
end := i + batchSize
if end > len(iris) {
end = len(iris)
}
batch := iris[i:end]
values := make([]string, 0, len(batch))
for _, u := range batch {
values = append(values, "<"+u+">")
}
q := fmt.Sprintf(`
SELECT ?s ?label
WHERE {
VALUES ?s { %s }
?s <%s> ?label .
}
`, strings.Join(values, " "), rdfsLabelIRI)
raw, err := sparql.Query(ctx, q)
if err != nil {
return nil, err
}
var res sparqlResponse
if err := json.Unmarshal(raw, &res); err != nil {
return nil, fmt.Errorf("failed to parse SPARQL JSON: %w", err)
}
for _, b := range res.Results.Bindings {
sTerm, ok := b["s"]
if !ok || sTerm.Value == "" {
continue
}
lblTerm, ok := b["label"]
if !ok || lblTerm.Type != "literal" || lblTerm.Value == "" {
continue
}
score := labelScore(lblTerm.Lang)
prev, ok := best[sTerm.Value]
if !ok || score > prev.score {
best[sTerm.Value] = bestLabel{score: score, value: lblTerm.Value}
}
}
}
out := make(map[string]string, len(best))
for iri, v := range best {
out[iri] = v.value
}
return out, nil
}
func labelScore(lang string) int {
lang = strings.ToLower(strings.TrimSpace(lang))
if lang == "en" {
return 3
}
if lang == "" {
return 2
}
return 1
}
func sortIntsUnique(xs []int) []int {
if len(xs) == 0 {
return xs
}
sort.Ints(xs)
out := xs[:0]
var last int
for i, v := range xs {
if i == 0 || v != last {
out = append(out, v)
}
last = v
}
return out
}