Progetto:BridgeShape API

Questa è una versione controllata, approvata il 27 ott 2026. Potrebbero essere state apportate nuove modifiche.

Documentazione BridgeShape: Motore e frontend · Ricette DSL · Smazzate di gioco

BridgeShape — API per Generatori di Smazzate

Documentazione tecnica per chi sviluppa un generatore di smazzate di test che legge i vincoli di distribuzione (shape constraints) dalle convenzioni di BridgePedia.

Panoramica

Ogni pagina wiki di convenzione su BridgePedia può definire nel template {{Convenzione}} un campo shape_constraints che descrive, in un formato DSL compatto, i vincoli di distribuzione delle mani dei 4 giocatori al tavolo.

L'estensione BridgeShape espone un'API REST/JSON per leggere questi vincoli in modo strutturato.


API Endpoint

<syntaxhighlight lang="text"> GET /api.php?action=bridgeshape&shapeaction=ACTION&format=json </syntaxhighlight>

Azioni disponibili

Azione Descrizione Parametri
get Vincoli per una singola convenzione pageid oppure title
list Elenco convenzioni con vincoli definiti —
listall Elenco tutte le convenzioni (con/senza vincoli) —
validate Valida una stringa DSL senza bisogno di una pagina wiki dsl

Esempi di chiamata

<syntaxhighlight lang="bash">

  1. Per titolo pagina

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&title=Splinter&format=json"

  1. Per page ID

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=get&pageid=42&format=json"

  1. Elenco pagine con vincoli

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=list&format=json"

  1. Validare una stringa DSL

curl "https://bridgepedia.it/api.php?action=bridgeshape&shapeaction=validate&dsl=opener+H:5%2B+hcp:12-21&format=json" </syntaxhighlight>


Formato risposta JSON

shapeaction=get

<syntaxhighlight lang="json"> {

 "shapeconstraints": {
   "pageId": 42,
   "raw": "1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // ...",
   "scenarios": [
     {
       "label": "1H-4C",
       "positions": {
         "opener": {
           "suits": {
             "H": { "min": 5, "max": null }
           },
           "hcp": { "min": 12, "max": 21 }
         },
         "responder": {
           "suits": {
             "H": { "min": 4, "max": null },
             "C": { "min": 0, "max": 1 }
           },
           "suit_hcp": {
             "C": { "min": 0, "max": 0 }
           },
           "hcp": { "min": 10, "max": 15 }
         }
       },
       "fit": []
     }
   ]
 }

} </syntaxhighlight>

Descrizioni leggibili. Insieme a scenarios, get e validate restituiscono descriptions (stesso ordine): per ogni scenario label, positions (per posizione, elenco di frasi già tradotte, es. "♠: ha K J 10 (e non A Q 9)"), positionLabels (nome del ruolo, es. "Attaccante" se la pagina ha shape_roles; altrimenti "Apertore (chi usa la convenzione)", "Compagno", ...) e fit. Parametro opzionale lang (it, en, ...; default lingua dell'API, di solito it). Serve a mostrare nell'interfaccia di Play perché la mano è fatta così. Nella posizione compare anche quiet: 1 se è stata usata la macro quiet.

shapeaction=list

<syntaxhighlight lang="json"> {

 "pages": [
   {
     "pageId": 42,
     "title": "Splinter",
     "scenarioCount": 6,
     "labels": ["1H-4C", "1H-4D", "1H-4S", "1S-4C", "1S-4D", "1S-4H"]
   },
   {
     "pageId": 15,
     "title": "Stayman",
     "scenarioCount": 2,
     "labels": ["fit a cuori", "fit a picche"]
   }
 ]

} </syntaxhighlight>


Formato DSL

Il DSL (Domain Specific Language) è la stringa scritta nel campo shape_constraints del template wiki.

Struttura

<syntaxhighlight lang="text"> etichetta: vincoli posizione; vincoli posizione // etichetta: vincoli // ... </syntaxhighlight>

  • // separa gli scenari (il generatore ne sceglie uno random o li presenta all'utente)
  • ; separa le posizioni dentro uno scenario
  • Ogni scenario ha un'etichetta opzionale seguita da :
  • Ogni posizione inizia col nome del ruolo, seguito da vincoli separati da spazi

Posizioni

Nome Ruolo
opener Chi apre o fa la prima licita convenzionale
responder Il compagno dell'opener
opp1 Avversario a sinistra dell'opener
opp2 Avversario a destra dell'opener (compagno di opp1)

Vincoli

Lunghezza seme (specifico)

<syntaxhighlight lang="text"> S:5+ — almeno 5 picche H:0-1 — 0 o 1 cuori D:3 — esattamente 3 quadri C:2-5 — da 2 a 5 fiori </syntaxhighlight>

Semi: S = Spades/Picche, H = Hearts/Cuori, D = Diamonds/Quadri, C = Clubs/Fiori

Lunghezza seme (wildcard)

<syntaxhighlight lang="text"> x:0-1 — un seme qualsiasi (tra quelli non specificati) con 0-1 carte x:6+ — un seme non specificato con almeno 6 carte </syntaxhighlight>

Il generatore sceglie casualmente quale seme assegnare a x tra quelli non già vincolati esplicitamente (S/H/D/C).

HCP totali

<syntaxhighlight lang="text"> hcp:15-17 — da 15 a 17 punti onore hcp:12+ — almeno 12 punti hcp:0-7 — da 0 a 7 punti </syntaxhighlight>

HCP nel seme (specifico)

<syntaxhighlight lang="text"> Shcp:3+ — almeno 3 HCP a picche Chcp:0 — 0 HCP a fiori (nessun onore) Hhcp:0-2 — da 0 a 2 HCP a cuori </syntaxhighlight>

HCP nel seme (wildcard)

<syntaxhighlight lang="text"> xhcp:0 — 0 HCP nel seme wildcard (x) xhcp:0-1 — 0-1 HCP nel seme x </syntaxhighlight>

Shape class

<syntaxhighlight lang="text"> shape:balanced — distribuzione bilanciata (4333, 4432, 5332) shape:semi_balanced — semibilanciata (5422, 6322) shape:one_suiter — monocolore shape:two_suiter — bicolore shape:three_suiter — tricolore shape:any — qualsiasi (default) </syntaxhighlight>

Onori esatti in un seme (S=KJT)

<syntaxhighlight lang="text"> S=KJT — in picche ha K, J e 10; NON ha A, Q, 9 (le carte basse 8-2 completano) S=KT9 — sequenza interna: K, 10, 9; NON ha A, Q, J H=Q — a cuori ha la Q come unico onore tra A K Q J 10 D=x — nessun onore a quadri (solo carte basse) </syntaxhighlight>

Le lettere ammesse sono A K Q J T 9 (anche 10). La regola è: le carte elencate sono possedute, le altre tra A K Q J 10 9 NON lo sono; le carte dall'8 in giù sono libere e riempiono fino alla lunghezza del seme. Serve a descrivere attacchi (sequenza interrotta KJT, interna KT, sotto onore K o Q) e le carte del compagno. Si combina con la lunghezza (S:5 S=KJT).

Nel JSON compare suit_cards nella posizione, es. {"S": "KJT", "H": ""} (stringa vuota = nessuna carta alta). Il generatore deve:

  • assegnare esattamente quegli onori alla mano e escluderli dalle altre mani del seme; gli onori non nominati da nessuna posizione sono distribuiti a caso, ma mai a una posizione con suit_cards per quel seme che non li elenca;
  • rispettare comunque suits e suit_hcp (la validazione segnala le contraddizioni, es. S=KQ Shcp:0-2 o lo stesso onore a due mani).

Pattern di mano in un seme (S~..., stile regex)

Descrive l'intera mano in quel seme, dall'alto in basso, come una piccola espressione regolare ancorata (inizio e fine):

<syntaxhighlight lang="text"> S~AKQ. — A, K, Q + una carta bassa (4 carte) S~A... — A + tre carte basse (4 carte) S~AJT9. — A, J, 10, 9 + una bassa S~AKQ.|A... — alternative: una OPPURE l'altra S~[KQ]x{2,3} — K o Q (come unico onore) + 2 o 3 carte basse S~KJT.* — K, J, 10 + qualunque numero di basse (anche zero) </syntaxhighlight>

Elementi:

  • Carte alte A K Q J T 9 (anche 10), sempre in ordine decrescente, ciascuna posseduta; quelle non nominate tra A K Q J 10 9 NON sono possedute.
  • Classe [KQ] = una carta tra quelle (si espande in alternative).
  • Carta bassa . (o x) = una carta qualsiasi tra 8 e 2; compare solo in fondo. Quantificatori: ? (0-1), * (0 o più), + (1 o più), {n}, {n,m}. Più punti si sommano (... = tre basse).
  • Alternative |: il generatore ne sceglie una a caso per ogni mano.

È la forma più espressiva di S=KJT (che resta valida): il pattern fissa anche la lunghezza (S~A... = 4 carte). Se c'è anche S:N i due vincoli devono essere compatibili (la validazione API segnala le incompatibilità).

Nel JSON compare suit_patterns nella posizione, già normalizzato (classi espanse, quantificatori risolti), così il generatore non deve interpretare la regex:

<syntaxhighlight lang="json"> "suit_patterns": {

 "S": [
   {"cards": "AKQ", "low_min": 1, "low_max": 1},
   {"cards": "A",   "low_min": 3, "low_max": 3}
 ]

} </syntaxhighlight>

cards = carte alte possedute (ordine A K Q J T 9); low_min/low_max = numero di carte basse (2-8), low_max: null = nessun limite. La lunghezza del seme è len(cards) + numero di basse. Il generatore sceglie una alternativa e rispetta anche suits e suit_hcp. Gli onori di cards vanno esclusi dalle altre mani; quelli non nominati nella mano vanno ad altri a caso.

Avversari "quiet" (nessun intervento)

<syntaxhighlight lang="text"> opp1 quiet — solo per opp1/opp2 opp2 quiet:0-7 — come quiet, ma con HCP personalizzati </syntaxhighlight>

quiet è una macro espansa dall'API: nel JSON non compare mai la parola quiet, ma i vincoli già risolti. Chi legge l'API (il generatore) non deve implementare nulla di nuovo. Si espande in:

<syntaxhighlight lang="text"> S:0-4 H:0-4 D:0-5 C:0-5 hcp:0-9 Shcp:0-3 Hhcp:0-3 Dhcp:0-4 Chcp:0-4 (con quiet:0-7 → hcp:0-7) </syntaxhighlight>

Significato: gli avversari possono avere colori lunghi (così le mani sono varie), ma poveri di onori e con pochi punti: nessun colore da 6+ carte (niente apertura di sbarramento), nessun contro informativo, nessun intervento naturale credibile. Le distribuzioni ammesse sono quindi molte, ma con punti ridotti: il generatore ha meno margine sugli HCP della coppia dichiarante (40 punti totali: apertore+rispondente restano sopra ~22). I token espliciti scritti sulla stessa posizione sostituiscono i default, in qualsiasi ordine: opp1 quiet S:0-4 lascia le picche 0-4 e tiene il resto. Su opener/responder la macro viene ignorata.

Fit (vincolo cross-posizione)

<syntaxhighlight lang="text"> fit:H8+ — almeno 8 cuori totali tra i compagni fit:S9+ — almeno 9 picche totali tra i compagni fit:M8+ — almeno 8 carte in un seme maggiore (H o S) fit:m8+ — almeno 8 carte in un seme minore (D o C) fit:8+ — almeno 8 carte in un seme qualsiasi </syntaxhighlight>

Il fit si applica tra compagni:

  • Se appare su opener o responder, o a livello di scenario → coppia dichiarante
  • Se appare su opp1 o opp2 → coppia difensiva

Formato range

Sintassi Significato
5+ Almeno 5 (min=5, max=null)
0-1 Da 0 a 1 (min=0, max=1)
3 Esattamente 3 (min=3, max=3)
12-21 Da 12 a 21 (min=12, max=21)

Nella risposta JSON, max: null significa "nessun limite superiore".

Semi non menzionati

I semi non vincolati e non coperti da x: sono distribuiti casualmente dal generatore, rispettando il vincolo che il totale delle carte per mano sia 13.


DSL versione 2 (dslVersion: 2)

Tutto ciò che sopra resta valido e invariato: i campi v1 hanno lo stesso significato di prima. La v2 aggiunge sintassi nuova; ogni scenario ha nel JSON il campo requires (elenco di tag, vuoto se usa solo la v1; vedi sotto) così un motore parziale può rifiutare uno scenario con un errore esplicito invece di generare mani sbagliate. descriptions[].context è sempre presente (array, eventualmente vuoto). Le definizioni normative (cosa è una fermata, i punti totali, le perdenti...) e l'architettura del motore sono in DEAL_ENGINE_SPEC.md.

Macro di scrittura (@NOME=v1,v2) — risolte dal parser, il motore non le vede

<syntaxhighlight lang="text"> @M=H,S 1${M}: opener ${M}:5+ hcp:12-21; responder ${M}:3+ </syntaxhighlight> Genera due scenari (1H, 1S) sostituendo ${M} ovunque, etichetta compresa. Più prefissi fanno il prodotto cartesiano (max 64 scenari): @M=H,S @N=15-17,12-14. I valori possono essere intervalli (hcp:${N}): servono anche come parametri del sistema (SA 15-17 contro 14-16).

Semi variabili $a $b $c $d

Un seme "da scegliere" condiviso da più posizioni dello stesso scenario. Ogni variabile è un seme diverso dalle altre. Dominio dichiarato a livello di scenario (prima delle posizioni):

<syntaxhighlight lang="text"> Landy: $a=M $b=any opener hcp:12-21 ; opp1 $a:5+ $b:4+ ; responder $a:0-2 </syntaxhighlight> $a=M nobili, $a=m minori, $a=any (default) qualunque, $a=SH insieme esplicito. Una variabile non dichiarata vale any (tutti i semi). Si usano al posto di S H D C in: $a:5+ (lunghezza), $ahcp:, $a=KQ, $a~..., stop:$a, has:$aQ, kc:$a3, confronti ($a>$b), longest:$a. Differisce da x: (che vale per una sola posizione). JSON: lo stesso dizionario suits, suit_hcp, ... ma con chiave "$a"; a livello di scenario "vars": {"$a": ["S","H"]}.

Controlli, carte chiave, carte precise

Token Significato
aces:2 kings:1-2 queens:0 numero di assi / re / dame nella mano
controls:6+ controlli (A=2, K=1)
kc:H3 kc:H2-3 kc:$a3 carte chiave con atout H: i 4 assi + il Re di H (0-5)
has:HQ has:S10 possiede quella carta precisa
lacks:HQ NON possiede quella carta
stop:S nostop:S ha / non ha una fermata nel seme (definizione in SPEC)

JSON: aces, kings, queens, controls (Range); keycards: {"H": Range}; cards_held: ["HQ"], cards_not_held: ["SK"]; stoppers: {"S": 1, "H": 0} (1 = deve averla, 0 = non deve averla).

Punti e perdenti

tp: punti totali (PO + 1 per ogni carta oltre la quarta in ogni seme), dp: punti distribuzione (PO + 3 chicane + 2 singolo + 1 doubleton), ltc: perdenti (Losing Trick Count). A livello di scenario: pairhcp:25+ (PO della coppia dichiarante), defhcp:0-14 (coppia avversaria), pairtp:26+. JSON: tp, dp, ltc (Range) nella posizione; pair_hcp: {"declarer": R, "defense": R}, pair_tp: {"declarer": R} nello scenario.

Confronti tra semi e forme precise

<syntaxhighlight lang="text"> H>S H>=S H<S H<=S H==S $a>$b — confronto di lunghezze nella stessa mano longest:H — H è tra i semi più lunghi (pari merito ammesso) shape:5-4-x-x shape:5422 shape:54xx — lunghezze ordinate in modo decrescente, x = qualsiasi shape:semi_balanced — 5422 e 6322 (si aggiunge alle classi esistenti) </syntaxhighlight> JSON: compare: [{"left":"H","op":">=","right":"S"}], longest: ["H"], shape_pattern: "54xx".

Alternative e negazioni nella stessa mano: any(...) e not(...)

<syntaxhighlight lang="text"> opener any(S:4+ hcp:12+, H:4+) not(D:6+) </syntaxhighlight> any(A, B): la mano soddisfa almeno una delle alternative (separate da virgola; ogni alternativa è una congiunzione di token). not(A, B): la mano non soddisfa nessuna delle alternative. Si possono scrivere più gruppi (si combinano in AND); niente annidamenti. JSON: any: [[altA, altB], ...] (lista di gruppi; ogni alternativa è un oggetto con gli stessi campi di una posizione), not: [altA, altB].

Contesto dello scenario

Token prima delle posizioni: weight:3 (frequenza relativa, 1-100, default 1), dealer:opener, vul:none|us|them|both (us = apertore+rispondente), auction:1SA,P,2C,P (licite, notazione BridgeCards; SA = NT), contract:4H, declarer:responder, lead:SK. JSON: weight (int) e context: {"dealer":..,"vul":..,"auction":[..],"contract":"4H","declarer":..,"lead":"SK"}. Il motore deve usare dealer/vul quando li espone (per la numerazione del tavolo) e, se c'è lead, la carta deve stare nella mano dell'attaccante (a sinistra del dichiarante) e rispettare i vincoli di quella mano.

Tag requires

suit_cards, suit_patterns, vars, stoppers, card_presence (has/lacks), controls (aces/kings/queens/controls/kc), points (tp/dp/ltc), pair_points, compare (confronti, longest), shape_pattern, any_not, context, weight. Le macro @ e quiet non compaiono: sono già espanse.

Avvisi di parsing (warnings)

get e validate restituiscono warnings quando un frammento del DSL non è stato capito e quindi scartato: nome di posizione sbagliato, token sconosciuto o con valore non valido (aces:5, S=Kx, shape:4-5-x-x), token di scenario dopo le posizioni, posizione definita due volte, quiet su opener/responder, gruppo any()/not() con alternative non valide (in tal caso l'intero gruppo è ignorato, mai applicato a metà). validate.valid è 1 solo se non ci sono né errors né warnings. Un motore non deve generare se ci sono warnings o errors.

Limiti (oltre i quali il parser avvisa e tronca): 60.000 caratteri di input, 200 scenari, 6 prefissi @, 64 valori per macro, 64 scenari per espansione, 8 gruppi any/not e 16 alternative per posizione. Le etichette non possono contenere :.


Struttura JSON di uno scenario

<syntaxhighlight lang="typescript"> interface Scenario {

 label: string;           // Etichetta (es. "1H-4C"), vuota se non specificata
 positions: {
   [posName: string]: Position;  // "opener", "responder", "opp1", "opp2"
 };
 fit: FitConstraint[];    // Vincoli di fit (array, può essere vuoto)

}

interface Position {

 suit_patterns?: {         // Pattern regex-like normalizzati: {"S":[{"cards":"AKQ","low_min":1,"low_max":1}]}
   [suit: string]: { cards: string; low_min: number; low_max: number | null }[];
 };
 suit_cards?: { [suit: string]: string };  // Onori esatti, es. {"S":"KJT"}; "" = nessuna carta alta
 suits?: {                // Vincoli lunghezza seme (solo semi esplicitati)
   [suit: string]: Range; // "S", "H", "D", "C"
 };
 hcp?: Range;             // HCP totali
 suit_hcp?: {             // HCP per seme specifico
   [suit: string]: Range;
 };
 x?: Range;               // Lunghezza seme wildcard
 x_hcp?: Range;           // HCP seme wildcard
 shape?: string;          // Shape class: "balanced", "one_suiter", etc.

}

interface Range {

 min: number;
 max: number | null;      // null = nessun limite superiore

}

interface FitConstraint {

 suit: string | null;      // "S","H","D","C" o null
 suit_class: string | null; // "major", "minor" o null
 min: number;              // Numero minimo di carte (es. 8)
 pair: string;             // "declarer" o "defense"

} </syntaxhighlight>


Logica per il generatore

Algoritmo base

Per ogni scenario:

  1. Scegli lo scenario: random tra quelli disponibili, oppure lascia scegliere all'utente
  2. Risolvi wildcard:
    • Se una posizione ha x:, scegli un seme casuale tra quelli non esplicitamente vincolati
    • xhcp: si applica allo stesso seme scelto per x:
  3. Risolvi fit:
    • Se fit.suit è specificato, entrambi i compagni devono avere un totale >= fit.min in quel seme
    • Se fit.suit_class è "major", scegli tra H e S; se "minor", tra D e C
    • Se entrambi sono null, scegli un seme qualsiasi
    • fit.pair indica quale coppia: "declarer" (opener+responder) o "defense" (opp1+opp2)
  4. Genera le mani:
    • Per ogni posizione, genera una mano che rispetti tutti i vincoli
    • I semi senza vincoli vengono riempiti casualmente
    • Il totale delle carte per mano deve essere esattamente 13
    • Il totale delle carte per seme (tutte e 4 le mani) deve essere esattamente 13
  5. Posizioni non menzionate: se uno scenario non menziona opp1/opp2, distribuisci le carte rimanenti casualmente tra gli avversari

Priorità dei vincoli

  1. Vincoli di fit (cross-posizione) — determinano la distribuzione di base
  2. Vincoli di seme specifico (S:, H:, D:, C:) — limitano i singoli semi
  3. Vincoli wildcard (x:) — dopo aver fissato i semi specifici
  4. Vincoli HCP (hcp:, Shcp:, xhcp:) — filtro finale
  5. Shape class (shape:) — validazione della forma complessiva

Gestione di max: null

Quando max è null, non c'è limite superiore esplicito. I limiti fisici del bridge si applicano comunque:

  • Massimo 13 carte per seme (tutte e 4 le mani)
  • Massimo 13 carte per mano
  • Massimo 10 HCP per seme (A+K+Q+J = 4+3+2+1)
  • Massimo 37 HCP per mano (raro ma teoricamente possibile con tutti gli onori)

Esempi completi

Splinter (tutti gli scenari specifici)

DSL nel template wiki: <syntaxhighlight lang="text"> 1H-4C: opener H:5+ hcp:12-21; responder H:4+ C:0-1 Chcp:0 hcp:10-15 // 1H-4D: opener H:5+ hcp:12-21; responder H:4+ D:0-1 Dhcp:0 hcp:10-15 // 1H-4S: opener H:5+ hcp:12-21; responder H:4+ S:0-1 Shcp:0 hcp:10-15 // 1S-4C: opener S:5+ hcp:12-21; responder S:4+ C:0-1 Chcp:0 hcp:10-15 // 1S-4D: opener S:5+ hcp:12-21; responder S:4+ D:0-1 Dhcp:0 hcp:10-15 // 1S-4H: opener S:5+ hcp:12-21; responder S:4+ H:0-1 Hhcp:0 hcp:10-15 </syntaxhighlight>

Splinter (compatto con fit e wildcard)

<syntaxhighlight lang="text"> dopo 1H: fit:H8+ opener H:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 // dopo 1S: fit:S8+ opener S:5+ hcp:12-21; responder x:0-1 xhcp:0 hcp:10-15 </syntaxhighlight>

Stayman

<syntaxhighlight lang="text"> fit a cuori: fit:H8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder H:4+ hcp:8+ // fit a picche: fit:S8+ opener hcp:15-17 S:2-4 H:2-4 D:2-5 C:2-5; responder S:4+ hcp:8+ </syntaxhighlight>

Contro informativo

<syntaxhighlight lang="text"> base: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; responder hcp:0-8 // con intervento: opener hcp:12-21 x:5+; opp1 hcp:12+ S:3+ H:3+ D:3+ C:3+; opp2 hcp:8+ x:5+ </syntaxhighlight>

Preempt 3-a-livello

<syntaxhighlight lang="text"> opener H:7+ x:0-1 hcp:5-10 </syntaxhighlight>

Overcall con fit avversari

<syntaxhighlight lang="text"> overcall semplice: opener hcp:12-21 x:5+; opp1 hcp:10+ x:5+; opp2 fit:8+ // con barrage: opener hcp:12-21 x:5+; opp1 hcp:6-10 x:6+; opp2 fit:9+ </syntaxhighlight>


Note implementative

  • L'API è read-only, non richiede token CSRF
  • L'accesso anonimo è abilitato di default (BridgeShapeAllowAnonymous: true) perché i dati sono pubblici e i tool esterni ne hanno bisogno
  • Il campo raw nella risposta get contiene la stringa DSL originale, utile per debug
  • L'azione validate permette di testare stringhe DSL senza creare pagine wiki
  • I calcoli HCP usano la scala standard: A=4, K=3, Q=2, J=1