Salta al contenuto principale

10 - Il Grafo delle Relazioni: 52 Tipi e Community Detection

Il Grafo Sociale Applicato alla Gestione Eventi Nel mondo della gestione eventi, conoscere le relazioni tra i partecipanti non è un dettaglio accessorio:...

Cosa ne pensi di questo articolo?

10 - Il Grafo delle Relazioni: 52 Tipi e Community Detection
Play the EventFoto:Brett SaylesPexels

In Breve

Il Grafo Sociale Applicato alla Gestione Eventi Nel mondo della gestione eventi, conoscere le relazioni tra i partecipanti non è un dettaglio accessorio:...

Il Grafo Sociale Applicato alla Gestione Eventi

Nel mondo della gestione eventi, conoscere le relazioni tra i partecipanti non è un dettaglio accessorio: è la chiave per organizzare esperienze memorabili. Chi è amico di chi? Quali coppie di ex-partner devono essere separate ai tavoli? Quali colleghi potrebbero voler sedere vicini?

In Play The Event, queste domande trovano risposta in un grafo sociale completo che modella oltre 52 tipi di relazione raggruppati in 10 categorie, ciascuno con un peso numerico che va da -1.0 (nemico) a 1.0 (legame familiare più forte). Questo grafo alimenta funzionalità avanzate come il seating intelligente, il rilevamento conflitti e i suggerimenti di invito.

Cosa Troverai in Questo Articolo

  • I 52 tipi di relazione organizzati in 10 categorie con peso ponderato
  • Il sistema di forza delle relazioni (RelationshipStrength) con 9 livelli
  • La bidirezionalità automatica e il calcolo del tipo inverso
  • Il workflow di stato: PENDING, ACCEPTED, REJECTED
  • La community detection per raggruppamento intelligente
  • Il conflict detection per evitare scontri ai tavoli
  • Il seating intelligente basato sul grafo sociale
  • I suggerimenti automatici di invito
  • La visualizzazione interattiva con D3.js

52 Tipi di Relazione in 10 Categorie

Il cuore del sistema è l'enum TipoRelazione, un Value Object che definisce ogni possibile legame tra due utenti. Ogni tipo ha un peso (weight) che ne indica l'importanza e un nome visualizzazione per l'interfaccia utente. Le categorie spaziano dalla famiglia nucleare ai rapporti organizzativi, fino alle relazioni negative.

Famiglia Nucleare (peso: 0.90 - 1.00)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
GENITORE                1.00    Genitore
FIGLIO                  1.00    Figlio/a
CONIUGE                 1.00    Coniuge
PARTNER                 0.95    Partner
GEMELLO                 0.95    Gemello/a
FRATELLO                0.90    Fratello
SORELLA                 0.90    Sorella
Nonni, Zii e Cugini (peso: 0.30 - 0.85)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
NONNO                   0.85    Nonno
NONNA                   0.85    Nonna
NIPOTE_DI_NONNI         0.85    Nipote (di nonni)
ZIO                     0.70    Zio
ZIA                     0.70    Zia
NIPOTE_DI_ZII           0.70    Nipote (di zii)
CUGINO_PRIMO            0.60    Cugino/a di Primo Grado
CUGINO_SECONDO          0.40    Cugino/a di Secondo Grado
CUGINO_TERZO            0.30    Cugino/a di Terzo Grado
Parenti Acquisiti (peso: 0.60 - 0.75)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
SUOCERO                 0.70    Suocero
SUOCERA                 0.70    Suocera
GENERO                  0.75    Genero
NUORA                   0.75    Nuora
COGNATO                 0.60    Cognato
COGNATA                 0.60    Cognata
Padrini e Step-family (peso: 0.55 - 0.65)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
PADRINO                 0.65    Padrino
MADRINA                 0.65    Madrina
FIGLIOCCIO              0.65    Figlioccio/a
PATRIGNO                0.60    Patrigno
MATRIGNA                0.60    Matrigna
FIGLIASTRO              0.55    Figliastro
FIGLIASTRA              0.55    Figliastra
FRATELLASTRO            0.55    Fratellastro
SORELLASTRA             0.55    Sorellastra
Adottivi e Famiglia Estesa (peso: 0.50 - 0.95)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
GENITORE_ADOTTIVO       0.95    Genitore Adottivo
FIGLIO_ADOTTIVO         0.95    Figlio/a Adottivo/a
FRATELLO_ADOTTIVO       0.80    Fratello/Sorella Adottivo/a
FRATELLO_CONSANGUINEO   0.70    Fratello Consanguineo
SORELLA_CONSANGUINEA    0.70    Sorella Consanguinea
BISNONNO                0.65    Bisnonno/a
PRONIPOTE               0.65    Pronipote
PROZIO                  0.50    Prozio/a
Relazioni Sociali (peso: 0.25 - 0.85)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
MIGLIORE_AMICO          0.85    Migliore Amico/a
AMICO_STRETTO           0.80    Amico/a Stretto/a
AMICO                   0.50    Amico/a
COMPAGNO_CLASSE         0.40    Compagno/a di Classe
CONOSCENTE              0.30    Conoscente
COLLEGA                 0.30    Collega
VICINO                  0.25    Vicino/a
Organizzazione Azienda (peso: 0.30 - 0.60)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
DIRIGENTE               0.60    Dirigente
RESPONSABILE            0.50    Responsabile
DIPENDENTE              0.40    Dipendente
COLLABORATORE           0.35    Collaboratore
CONSULENTE              0.35    Consulente
STAGISTA                0.30    Stagista
Organizzazione Associazione (peso: 0.35 - 0.70)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
PRESIDENTE              0.70    Presidente
VICEPRESIDENTE          0.65    Vicepresidente
SEGRETARIO              0.60    Segretario
TESORIERE               0.60    Tesoriere
MEMBRO_DIRETTIVO        0.55    Membro del Direttivo
CONSIGLIERE             0.50    Consigliere
ASSOCIATO               0.40    Associato/Socio
VOLONTARIO              0.35    Volontario
Relazioni Negative (peso: -1.00 - -0.20)
TIPO                    PESO    NOME VISUALIZZAZIONE
----------------------------------------------------
NEMICO                  -1.00   Nemico/a
RIVALE                  -0.50   Rivale
EX_CONIUGE              -0.30   Ex Coniuge
EX_PARTNER              -0.20   Ex Partner
SCONOSCIUTO              0.00   Sconosciuto/a

Questa granularità permette al sistema di distinguere non solo il tipo di legame, ma anche la sua intensità. Un MIGLIORE_AMICO (0.85) pesa quasi quanto un FRATELLO (0.90), mentre un CONOSCENTE (0.30) ha un impatto molto inferiore nelle decisioni algoritmiche.

Peso e Forza delle Relazioni

Ogni relazione ha un peso numerico compreso tra -1.0 e 1.0. Il sistema traduce questo valore continuo in una scala discreta di 9 livelli di forza tramite l'enum RelationshipStrength, utilizzata dall'interfaccia utente per visualizzare indicatori di colore e spessore degli archi nel grafo.

RelationshipStrength - 9 Livelli di Forza
LIVELLO                RANGE PESO       DESCRIZIONE
-----------------------------------------------------
VERY_STRONG            >= 0.80          Molto Forte
STRONG                 >= 0.60          Forte
MODERATE               >= 0.40          Moderata
WEAK                   >= 0.20          Debole
VERY_WEAK              > 0.00           Molto Debole
NEUTRAL                == 0.00          Neutrale
SLIGHTLY_NEGATIVE      >= -0.30         Leggermente Negativa
NEGATIVE               >= -0.60         Negativa
VERY_NEGATIVE          < -0.60          Molto Negativa

Il metodo getStrength() dell'enum TipoRelazione esegue la conversione automatica. Ad esempio, CONIUGE (peso 1.0) restituisce VERY_STRONG, mentre NEMICO (peso -1.0) restituisce VERY_NEGATIVE. Questo mapping è fondamentale per la visualizzazione: gli archi VERY_STRONG sono rappresentati con linee spesse e colori caldi, mentre gli archi NEGATIVE sono tratteggiati in rosso.

Il peso può anche essere regolato manualmente dall'utente tramite il metodo regolaPeso(double nuovoPeso). Ad esempio, un organizzatore potrebbe voler abbassare il peso di una relazione familiare se i due parenti non sono in buoni rapporti, oppure innalzare il peso di un collega particolarmente stretto.

L'Entità RelazioneUtente

L'Aggregate Root RelazioneUtente rappresenta una singola relazione nel grafo sociale. Ogni istanza collega due utenti con un tipo, un peso e uno stato. La tabella relazioni_utenti è indicizzata su entrambi gli ID utente, sul tipo e sul peso per garantire query performanti.

Struttura dell'Entità RelazioneUtente
RelazioneUtente (Aggregate Root)
├── id                          Long (auto-generated)
├── utenteId1                   Long (FK → users)
├── utenteId2                   Long (FK → users)
├── tipoRelazione               TipoRelazione (enum)
├── peso                        Double (-1.0 a 1.0)
├── bidirezionale               Boolean (auto-calcolato)
├── tipoRelazioneInversa        TipoRelazione (auto-calcolato)
├── stato                       StatoRelazione (PENDING/ACCEPTED/REJECTED)
├── richiestaInviataDaUtenteId  Long (chi ha inviato la richiesta)
├── richiestaInviataIl          Instant (timestamp invio)
├── rispostaDataIl              Instant (timestamp risposta)
├── note                        String (max 500 caratteri)
├── creatoIl                    Instant (audit)
├── aggiornatoIl                Instant (audit)
└── versione                    Long (optimistic locking)

VINCOLI:
├── UniqueConstraint(utenteId1, utenteId2) → no duplicati
├── utenteId1 ≠ utenteId2 → no self-relation
└── peso ∈ [-1.0, 1.0] → range validato

La creazione avviene tramite il factory method RelazioneUtente.crea(), che valida i dati, calcola automaticamente il peso dal tipo di relazione, determina la bidirezionalità e imposta lo stato iniziale a PENDING.

Bidirezionalità Automatica

Non tutte le relazioni sono simmetriche. Il sistema determina automaticamente se una relazione è bidirezionale e, in caso contrario, quale sia il tipo inverso. Questo calcolo avviene nel costruttore di RelazioneUtente tramite due metodi privati.

Relazioni Simmetriche

Alcune relazioni valgono identicamente in entrambe le direzioni: se Alice è amica di Bob, allora Bob è automaticamente amico di Alice con lo stesso tipo e peso.

Relazioni Bidirezionali (stesso tipo in entrambe le direzioni)
SIMMETRICHE (bidirezionale = true, tipo inverso = stesso tipo):

Familiari:  CONIUGE, PARTNER, GEMELLO, FRATELLO, SORELLA,
            FRATELLASTRO, SORELLASTRA, FRATELLO_CONSANGUINEO,
            SORELLA_CONSANGUINEA, FRATELLO_ADOTTIVO,
            CUGINO_PRIMO, CUGINO_SECONDO, CUGINO_TERZO,
            COGNATO, COGNATA

Sociali:    AMICO, AMICO_STRETTO, MIGLIORE_AMICO,
            CONOSCENTE, COLLEGA, VICINO, COMPAGNO_CLASSE

Negativi:   RIVALE, NEMICO, SCONOSCIUTO

Relazioni Direzionali con Tipo Inverso

Altre relazioni hanno una direzione intrinseca: se Alice è genitore di Bob, allora Bob è figlio di Alice. Il sistema calcola automaticamente il tipo inverso per navigare il grafo in entrambe le direzioni.

Mapping dei Tipi Inversi
TIPO DIRETTO              →    TIPO INVERSO
--------------------------------------------
GENITORE                  →    FIGLIO
FIGLIO                    →    GENITORE
NONNO / NONNA             →    NIPOTE_DI_NONNI
ZIO / ZIA                 →    NIPOTE_DI_ZII
SUOCERO / SUOCERA         →    GENERO (o NUORA)
PADRINO / MADRINA         →    FIGLIOCCIO
PATRIGNO                  →    FIGLIASTRO
MATRIGNA                  →    FIGLIASTRA
GENITORE_ADOTTIVO         →    FIGLIO_ADOTTIVO
FIGLIO_ADOTTIVO           →    GENITORE_ADOTTIVO
BISNONNO                  →    PRONIPOTE
PRONIPOTE                 →    BISNONNO

Questo meccanismo è cruciale per la navigazione del grafo: quando il sistema deve trovare tutti i figli di un utente, non deve cercare solo le relazioni dove tipoRelazione = GENITORE e l'utente è utenteId1, ma anche le relazioni dove tipoRelazione = FIGLIO e l'utente è utenteId2.

Stato Relazione: Il Workflow Bilaterale

Ogni relazione attraversa un workflow di conferma bilaterale, simile a una richiesta di amicizia sui social network. Questo garantisce che entrambe le parti acconsentano al collegamento prima che la relazione diventi attiva nel grafo.

Macchina a Stati della Relazione
                  ┌─────────────────────────────────┐
                  │                                 │
                  ▼                                 │
  [CREAZIONE] → PENDING ──── accetta() ──→ ACCEPTED │
                  │                                 │
                  └──── rifiuta() ──→ REJECTED ─────┘
                                         │
                                         └── resetRichiesta()
                                              (reinvia come PENDING)

REGOLE:
├── Solo il DESTINATARIO può accettare o rifiutare
├── Solo le richieste PENDING possono cambiare stato
├── Le richieste REJECTED possono essere reinviate
└── Le relazioni ACCEPTED sono visibili nel grafo
  • PENDING (In attesa di conferma): stato iniziale di ogni nuova relazione. Il richiedente ha inviato la richiesta, il destinatario non ha ancora risposto. La relazione non è ancora visibile nel grafo e non influenza algoritmi.
  • ACCEPTED (Accettata): la relazione è attiva e visibile a entrambi gli utenti. Viene utilizzata per visualizzazione nel grafo, suggerimenti inviti, rilevamento conflitti e community detection.
  • REJECTED (Rifiutata): la richiesta è stata rifiutata dal destinatario. La relazione rimane nel database per tracking, ma non è attiva. Il richiedente può inviare una nuova richiesta in futuro tramite resetRichiesta().

Distinzione Richiedente / Destinatario

Il campo richiestaInviataDaUtenteId tiene traccia di chi ha inviato la richiesta. Il destinatario è automaticamente l'altro utente nella coppia. Solo il destinatario può accettare o rifiutare la richiesta - questo vincolo è validato nel metodo validaDestinatario() che lancia un'eccezione se l'utente sbagliato tenta di rispondere.

Il Domain Service: GestoreCollegamentiDomainService

La logica di business che coinvolge più entità è coordinata dal Domain Service GestoreCollegamentiDomainService. Questo servizio gestisce la creazione di richieste con validazione dei duplicati e il filtering dei collegamenti per stato.

Regole di Business per la Creazione di un Collegamento
creaRichiestaCollegamento(richiedenteId, destinatarioId, tipo):

1. VALIDAZIONE: richiedenteId ≠ destinatarioId
   → "Non puoi creare collegamento con te stesso"

2. RICERCA DUPLICATI: cerca relazione esistente (in entrambe le direzioni)

3. SE ESISTE:
   ├── PENDING  → ERRORE "Richiesta già inviata e in attesa"
   ├── ACCEPTED → ERRORE "Collegamento già esistente"
   └── REJECTED → RESET: reinvia come nuova PENDING
                   (aggiorna tipo, richiedente, timestamp)

4. SE NON ESISTE:
   └── CREA: nuova RelazioneUtente con stato PENDING

Il servizio offre anche metodi per filtrare i collegamenti: getCollegamentiAccettati() restituisce solo le relazioni ACCEPTED, mentre getRichiesteRicevutePending() e getRichiesteInviatePending() distinguono tra richieste in entrata e in uscita. Il conteggio contaRichiesteRicevutePending() alimenta i badge di notifica nell'interfaccia utente.

Community Detection

Una delle funzionalità più potenti del grafo sociale è l'identificazione automatica di comunità - gruppi di persone densamente connesse tra loro. In Play The Event, questo si traduce nell'identificazione automatica di nuclei familiari, gruppi di amici e colleghi di lavoro.

Esempio: Community Detection su un Matrimonio
GRAFO DEGLI INVITATI (60 persone)
│
├── CLUSTER 1: Famiglia Sposo (12 persone)
│   ├── Genitori (peso 1.0)
│   ├── Fratelli e cognati (peso 0.6-0.9)
│   ├── Nonni (peso 0.85)
│   └── Zii e cugini (peso 0.3-0.7)
│
├── CLUSTER 2: Famiglia Sposa (14 persone)
│   ├── Genitori (peso 1.0)
│   ├── Sorelle e cognati (peso 0.6-0.9)
│   └── Parenti estesi (peso 0.3-0.7)
│
├── CLUSTER 3: Amici Università (8 persone)
│   ├── Amici stretti (peso 0.8)
│   ├── Compagni di classe (peso 0.4)
│   └── Collegamento inter-cluster: 2 persone
│       collegano Cluster 3 ↔ Cluster 4
│
├── CLUSTER 4: Colleghi Lavoro Sposo (10 persone)
│   ├── Dirigente (peso 0.6)
│   ├── Responsabile (peso 0.5)
│   └── Colleghi (peso 0.3)
│
├── CLUSTER 5: Amiche Sposa (8 persone)
│   └── Migliori amiche (peso 0.85)
│
└── CLUSTER 6: Vicini e Conoscenti (8 persone)
    └── Vicini (peso 0.25) + Conoscenti (peso 0.3)

L'algoritmo analizza la densità delle connessioni e i pesi degli archi per determinare i confini naturali tra i gruppi. I metodi helper dell'enum TipoRelazione facilitano la classificazione: isFamilyRelation(), isSocialRelation(), isOrganizationalRelation() e isCoreFamily() permettono di verificare rapidamente la natura di ogni legame.

La tabella di riferimento TipoRelazioneDB organizza i tipi in 8 categorie formali: FAMIGLIA_STRETTA, FAMIGLIA_ALLARGATA, ACQUISITI, SPIRITUALI, SOCIALI, PROFESSIONALI, ORGANIZZATIVI e NEGATIVI. Ogni tipo ha traduzioni multilingue (italiano e inglese) e un ordine di visualizzazione per i dropdown dell'interfaccia.

Conflict Detection

Il rilevamento dei conflitti è una funzionalità critica per la pianificazione eventi. Quando un organizzatore invita un gruppo di persone, il sistema analizza automaticamente le relazioni tra loro per identificare coppie con rapporti negativi.

Algoritmo di Conflict Detection
ENDPOINT: GET /api/v1/relationships/conflicts?userIds=1,2,3,4,5

INPUT:  Lista di ID utenti (partecipanti all'evento)
OUTPUT: Lista di ConflictAlert con severità

ALGORITMO:
1. Recupera tutte le relazioni dell'organizzatore
2. Filtra: solo relazioni tra utenti nella lista
3. Filtra: solo relazioni con peso < 0 (negative)
4. Per ogni conflitto, calcola la SEVERITA':

   SEVERITA'     RANGE PESO        ESEMPIO
   ──────────────────────────────────────────
   HIGH          peso <= -0.70     NEMICO (-1.0)
   MEDIUM        peso <= -0.30     RIVALE (-0.5)
   LOW           peso <  0.00      EX_PARTNER (-0.2)

RESPONSE:
  ConflictAlert {
    userId1, userId2,
    user1Name, user2Name,
    relationshipType,
    weight,
    severity (HIGH | MEDIUM | LOW)
  }

L'organizzatore riceve una lista di alert visualizzati con colori diversi in base alla severità: rosso per HIGH (conflitti gravi come NEMICO), arancione per MEDIUM (conflitti moderati come RIVALE o EX_CONIUGE), e giallo per LOW (conflitti lievi come EX_PARTNER). Questo permette di prendere decisioni informate sulla disposizione dei posti.

Use Case: Seating Intelligente

Il caso d'uso più concreto del grafo sociale è l'ottimizzazione della disposizione ai tavoli durante matrimoni, cene aziendali e altri eventi con posti a sedere assegnati. Il sistema combina community detection e conflict detection per proporre una disposizione ottimale.

Seating Intelligente: Esempio Matrimonio con 60 Invitati
OBIETTIVI DELL'ALGORITMO:
├── MASSIMIZZARE: relazioni positive allo stesso tavolo
├── MINIMIZZARE: conflitti allo stesso tavolo
├── RISPETTARE: capacità massima per tavolo (8-10 persone)
└── PRESERVARE: nuclei familiari e gruppi di amici

ESEMPIO OUTPUT (8 tavoli da 8 persone):

  TAVOLO 1 - "Famiglia Sposo (genitori)"
  ├── Sposo, Genitori Sposo, Fratello + Cognata
  ├── Nonni paterni
  └── Score medio relazioni: 0.87 (VERY_STRONG)

  TAVOLO 2 - "Famiglia Sposa (genitori)"
  ├── Sposa, Genitori Sposa, Sorella + Cognato
  ├── Nonni materni
  └── Score medio relazioni: 0.85 (VERY_STRONG)

  TAVOLO 5 - "Amici Università"
  ├── 8 amici con peso medio 0.65
  └── Score medio relazioni: 0.65 (STRONG)

  ⚠ CONFLITTO EVITATO:
  Marco (EX_CONIUGE, peso -0.3) e Laura
  → Assegnati a tavoli diversi (Tavolo 4 vs Tavolo 6)
  → Distanza fisica: 3 tavoli di separazione

L'algoritmo procede in fasi: prima assegna i cluster identificati dalla community detection come unità base, poi raffina la distribuzione spostando individui per massimizzare lo score complessivo e infine verifica che nessun conflitto rilevato sia rimasto nella stessa area. L'organizzatore può sempre sovrascrivere i suggerimenti e il sistema ricalcola lo score in tempo reale.

Suggerimenti di Invito

Il grafo sociale permette anche di suggerire nuovi invitati in base alle connessioni con i partecipanti già confermati. L'endpoint GET /api/v1/relationships/suggestions analizza la rete sociale e identifica le persone meglio connesse con il gruppo esistente.

Algoritmo di Suggerimento Inviti
ENDPOINT: GET /api/v1/relationships/suggestions
         ?participantIds=1,2,3,4&minConnections=2&limit=10

ALGORITMO:
1. Per ogni partecipante confermato:
   └── Recupera tutti i collegamenti ACCEPTED

2. Costruisci mappa candidati:
   └── candidatoId → [lista connessioni con partecipanti]
   └── ESCLUDI: già partecipanti
   └── ESCLUDI: relazioni negative (peso < 0)

3. Per ogni candidato, calcola SCORE:
   ├── commonConnections = numero connessioni con partecipanti
   ├── avgWeight = peso medio delle connessioni
   └── score = (commonConnections/5 * 0.40) + (avgWeight * 0.60)
              ^^^^^^^^^^^^^^^^^^^^
              normalizzato a [0,1]

4. FILTRA: commonConnections >= minConnections
5. ORDINA: per score decrescente
6. LIMITA: a N risultati

ESEMPIO SUGGERIMENTO:
  "Invita anche Marco Rossi"
  ├── Conosce 3 partecipanti (peso medio: 0.72)
  ├── Score: (0.6 * 0.40) + (0.72 * 0.60) = 0.672
  └── Motivo: "Conosce 3 partecipanti (peso medio: 0.72)"

La formula di scoring bilancia due fattori: il 40% del punteggio è basato sul numero di connessioni comuni (normalizzato a un massimo di 5), mentre il 60% è determinato dalla forza media delle relazioni. Questo significa che una persona con 2 amici stretti (peso 0.80) riceverà un punteggio più alto di una con 3 semplici conoscenti (peso 0.30).

Visualizzazione con D3.js

Il grafo delle relazioni è reso visivamente nell'interfaccia di Play The Event tramite una visualizzazione force-directed basata su D3.js. L'endpoint GET /api/v1/relationships/graph restituisce i dati nel formato nodi + archi atteso dalla libreria.

Formato Dati per D3.js Force-Directed Graph
GraphDataResponse {
  "nodes": [
    {
      "id": "1",
      "name": "Federico Calò (Tu)",
      "degree": 12,        // numero di connessioni
      "cluster": null       // community (futuro)
    },
    {
      "id": "2",
      "name": "Marco Rossi",
      "degree": 5,
      "cluster": null
    }
  ],
  "links": [
    {
      "source": "1",
      "target": "2",
      "weight": 0.80,      // forza del legame
      "type": "AMICO_STRETTO"
    },
    {
      "source": "1",
      "target": "3",
      "weight": -0.50,
      "type": "RIVALE"
    }
  ]
}

Il GetRelationshipGraphQueryHandler costruisce il grafo partendo dai collegamenti accettati dell'utente corrente. Per ogni nodo calcola il grado (degree), ovvero il numero di connessioni, che determina la dimensione del nodo nella visualizzazione. L'utente corrente è evidenziato con il suffisso "(Tu)" nel nome visualizzato.

Nella visualizzazione D3.js, le proprietà del grafo vengono mappate a elementi visivi:

  • Dimensione dei nodi: proporzionale al grado - più connessioni ha un utente, più grande appare il suo nodo
  • Colore dei nodi: basato sul cluster di appartenenza - stesso colore per la stessa comunità
  • Spessore degli archi: proporzionale al peso - relazioni forti hanno archi spessi, relazioni deboli archi sottili
  • Colore degli archi: verde per relazioni positive, rosso tratteggiato per relazioni negative
  • Distanza tra nodi: inversamente proporzionale al peso - persone con legami forti sono attratte vicine
  • Tooltip interattivi: al passaggio del mouse su un arco si visualizza il tipo di relazione e il peso

Internazionalizzazione

Il sistema delle relazioni supporta la localizzazione completa. La tabella stati_relazione fornisce descrizioni degli stati in 7 lingue (IT, EN, FR, ES, DE, SV, PT), mentre la tabella tipi_relazione offre nomi tradotti in italiano e inglese. Il metodo getDescrizioneLocalizzata(lingua) restituisce la stringa corretta in base alla lingua dell'utente.

Traduzioni degli Stati in 7 Lingue
STATO        IT                   EN                 FR
─────────────────────────────────────────────────────────
PENDING      In attesa di          Waiting for        En attente de
             conferma              confirmation       confirmation

ACCEPTED     Accettata             Accepted           Acceptée

REJECTED     Rifiutata             Rejected           Refusée

La Rubrica degli Invitati Esterni

Oltre al grafo tra utenti registrati, Play The Event gestisce anche le relazioni tra invitati esterni - persone che non hanno un account sulla piattaforma ma che l'organizzatore vuole invitare. L'entità RelazioneInvitatiEsterni modella queste connessioni con un set semplificato di 10 tipi di relazione.

Tipi di Relazione tra Invitati Esterni
TipoRelazioneTraInvitati:
├── CONIUGE     (famiglia)
├── PARTNER     (famiglia)
├── GENITORE    (famiglia)
├── FIGLIO      (famiglia)
├── FRATELLO    (famiglia)
├── PARENTE     (famiglia)
├── AMICO       (sociale)
├── COLLEGA     (sociale)
├── CONOSCENTE  (sociale)
└── ALTRO

Helper methods:
├── isFamiglia() → true per CONIUGE/PARTNER/GENITORE/FIGLIO/FRATELLO/PARENTE
└── isVicino()   → true per tutti i familiari + AMICO

L'entità garantisce un ordine canonico tra i due invitati (invitato_id_1 < invitato_id_2) per evitare duplicati in direzioni diverse. Entrambi gli invitati devono appartenere alla rubrica dello stesso utente, validato nel factory method crea().

Architettura CQRS e API REST

L'intero sistema di relazioni segue il pattern CQRS (Command Query Responsibility Segregation). I comandi modificano lo stato (creazione richiesta, accettazione, rifiuto), mentre le query leggono i dati senza effetti collaterali (grafo, conflitti, suggerimenti).

API REST del Relationship Graph Controller
RelationshipGraphController
│
├── GET /api/v1/relationships/graph
│   └── Restituisce il grafo completo dell'utente
│       Response: GraphDataResponse { nodes[], links[] }
│
├── GET /api/v1/relationships/conflicts?userIds=1,2,3
│   └── Rileva conflitti tra gli utenti specificati
│       Response: ConflictAlertResponse[] {
│           userId1, userId2, user1Name, user2Name,
│           relationshipType, weight, severity
│       }
│
└── GET /api/v1/relationships/suggestions
        ?participantIds=1,2,3&minConnections=2&limit=10
    └── Suggerisce invitati basandosi sulle connessioni
        Response: InvitationSuggestionResponse[] {
            userId, userName, profileImageUrl,
            commonConnections, averageConnectionWeight,
            score, reason
        }

Tutte le query sono eseguite in transazioni read-only per ottimizzare le performance. L'autenticazione è gestita tramite JWT, e ogni endpoint verifica l'identità dell'utente corrente tramite SecurityUtils.getCurrentUserIdOrThrow().

Perché un Grafo Sociale per gli Eventi?

La differenza tra un evento ben organizzato e uno memorabile sta nei dettagli relazionali. Il grafo sociale di Play The Event trasforma dati relazionali in decisioni concrete: dove far sedere le persone, chi invitare, quali conflitti prevenire. Con 52 tipi di relazione, 9 livelli di forza e algoritmi di community detection, il sistema offre all'organizzatore una visione completa della rete sociale dei propri invitati - il tutto attraverso un'interfaccia interattiva basata su D3.js che rende il grafo esplorabile con un semplice drag & drop.

Nel prossimo articolo esploreremo il sistema di gestione documenti e allegati, analizzando come Play The Event organizza contratti, permessi, fatture e altri file associati agli eventi con versionamento e condivisione granulare.

Domande frequenti

Come funziona il grafo sociale applicato alla gestione degli eventi?

Il grafo sociale è un modello che rappresenta le relazioni tra i partecipanti a un evento, consentendo di conoscere le amicizie, le coppie di ex-partner e le collezioni di lavoro.

Quali sono le 10 categorie di relazione nel grafo sociale?

Le 10 categorie di relazione sono: famiglia nucleare, rapporti organizzativi, amici stretti, conoscenti, parenti lontani, ex-partner, nemici, rivali, colleghi e legami familiari più forti.

Come funziona la bidirezionalità automatica nel grafo sociale?

Il sistema determina automaticamente se una relazione è bidirezionale e, in caso contrario, quale sia il tipo inverso. Questo calcolo avviene nel costruttore dell'entità RelazioneUtente.

Come funziona la visualizzazione del grafo sociale con D3.js?

La visualizzazione del grafo è resa visivamente nell'interfaccia di Play The Event tramite una visualizzazione force-directed basata su D3.js. L'endpoint restituisce i dati nel formato nodi + archi atteso dalla libreria.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti