Configuration LibreOffice / OpenOffice Calc — chaîne eval, mode colonne
Julien Béranger
+ Claude Opus 5
Variante « par lots » de la configuration cellule par cellule
(RUKH_EVAL_OPEN_CALC_CONFIG.md). Même grille,
même colonne de sortie, même bouton — mais un appel traite une vingtaine de
critères au lieu d'un seul. La macro construit un CSV à partir de la feuille,
le colle dans le message, reçoit une ligne par enregistrement et les réécrit
dans la colonne de sortie.
Contexte dédié : eval-oc-full.
C'est la transposition du contexte eval — CSV brut envoyé en une fois — à un
tableur qui sait le fabriquer lui-même. Ce que Calc apporte par rapport au
copier-coller manuel : le CSV est propre par construction, et le calage est
vérifié automatiquement avant écriture.
Quel mode choisir
eval-oc (cellule par cellule) | eval-oc-full (mode colonne) | |
|---|---|---|
| Appels pour un chapitre de 57 critères | 57 | 3 |
| Quota 50/h | atteint au bout d'un chapitre | ~16 chapitres possibles par heure |
| Coût d'entrée par chapitre | ~0,46 $ | ~0,04 $ |
| Cohérence entre cellules | verbes seulement, via Débuts déjà utilisés | le modèle voit tout le lot |
| Acronymes | redéveloppés dans chaque cellule | développés une fois par lot |
| Granularité de reprise | la cellule | le lot : un lot rejeté est à refaire en entier |
| Durée d'un appel | ~6 s | ~1 à 2 min (estimation, à mesurer) |
| Risque de dépassement de délai | négligeable | réel — c'est la contrainte du mode |
| Refaire une cellule isolée | naturel | possible, mais l'autre mode est plus adapté |
En pratique : eval-oc-full pour traiter un chapitre, eval-oc pour
reprendre les quelques cellules dont la réécriture ne convient pas. Les deux
écrivent dans la même colonne Commentaires réécrits et se relaient sans
précaution particulière.
Ce que la macro envoie
Un message par lot, construit à partir de la feuille — jamais un fichier joint, jamais un export manuel :
Enregistrements : 20
Débuts déjà utilisés : La personne indique qu'elle est ac | Les professionnels précisent
Chapitre;Critère;Cotations;Commentaires
"Chapitre 1";"1.1.1";"4;3;4";"AT2 : la personne explique qu'elle est accompagnée depuis 2021…"
"Chapitre 1";"1.1.2";"3";"les professionnels disent qu'il n'y a pas eu de formation"Trois écarts délibérés par rapport au CSV que traite le contexte eval :
- Seules les lignes de critère sont envoyées. Les lignes
E.E.ne portent aucun commentaire à réécrire ; dansevalelles n'existent dans la réponse que pour produire un-de calage, parce qu'un humain colle la colonne entière dans la feuille. Ici la macro écrit cellule par cellule à des positions qu'elle connaît : ces lignes de remplissage n'ont plus de raison d'être. Sur le chapitre de référence, cela fait 57 lignes de sortie au lieu de 180, donc autant de risque de décalage et de jetons de sortie en moins. - Les cotations des lignes
E.E.sont regroupées dans une colonneCotationssur la ligne du critère. Elles restent nécessaires à la règle « cependant », elles ne coûtent plus une ligne chacune. - Un enregistrement = une ligne physique. La macro remplace les retours à
la ligne des cellules par des espaces avant d'écrire le CSV. La « première
cause de décalage » signalée dans les instructions du contexte
eval— une cellule entre guillemets qui s'étale sur plusieurs lignes — disparaît à la source.
La ligne Enregistrements : N annonce le nombre de lignes attendues en retour.
Débuts déjà utilisés : transmet les deux derniers débuts du lot précédent,
pour que la variation des verbes franchisse les bornes de lot.
Le garde-fou de calage
C'est ce qui rend le mode colonne utilisable sans relecture ligne à ligne.
À la réception, la macro retire l'encadrement ```, ignore les lignes
vides, et compte les lignes. Si le compte ne correspond pas exactement au
nombre d'enregistrements envoyés, rien n'est écrit pour ce lot : une
relance automatique est tentée (RETRIES), puis la macro s'arrête en indiquant
le lot en cause. Les lots précédents restent écrits.
Autrement dit, un décalage ne peut pas s'installer silencieusement dans la
colonne. C'est le contrôle que eval-verif effectuait a posteriori sur une
colonne collée à la main, ramené à l'intérieur de la boucle et exécuté avant
toute écriture.
Ce que le garde-fou ne vérifie pas : que la ligne n corresponde bien au critère n. Un modèle qui produirait le bon nombre de lignes dans le mauvais ordre passerait le contrôle. C'est peu probable — l'ordre du CSV est explicite et les identifiants de critère sont transmis — mais cela reste la raison de relire un chapitre avant de le rendre.
Prérequis
Identiques à ceux du mode cellule par cellule, à un point près : c'est le
contexte eval-oc-full qui doit être déployé.
- LibreOffice 7.x ou plus récent, avec le fournisseur de scripts Python.
Inclus par défaut sous Windows et macOS ; sous Linux, installer au besoin
libreoffice-script-provider-python(Debian/Ubuntu) oulibreoffice-pyuno(Fedora). - Accès réseau direct vers
rukh.w3hc.org.urlliblithttp_proxy/https_proxy, pas le proxy configuré dans LibreOffice. - Aucune authentification, aucun jeton.
- Le contexte
eval-oc-fulldéployé sur l'API (voir « Déploiement du contexteeval-oc-full», en fin de page). - Apache OpenOffice : voir « Variante Apache OpenOffice », plus bas.
Mise en place (une fois)
1. Le pont Python
Déposer rukh.py dans Scripts/python du profil utilisateur :
| Système | Chemin |
|---|---|
| macOS | ~/Library/Application Support/LibreOffice/4/user/Scripts/python/ |
| Linux | ~/.config/libreoffice/4/user/Scripts/python/ |
| Windows | %APPDATA%\LibreOffice\4\user\Scripts\python\ |
import json
import urllib.request
import urllib.error
RUKH_URL = "https://rukh.w3hc.org/ask"
TIMEOUT = 300 # secondes ; ~6 s en mode cellule, 1 à 2 min en mode colonne
def ask(message, context):
"""Renvoie "OK:<texte>" ou "ERR:<code>". Les exceptions Python ne
traversent pas proprement le pont UNO : le code d'erreur voyage donc dans
la chaîne de retour, ce qui permet à Basic de distinguer un 429 d'une
vraie panne."""
body = json.dumps(
{"message": message, "model": "anthropic", "context": context}
).encode("utf-8")
req = urllib.request.Request(
RUKH_URL, data=body, headers={"Content-Type": "application/json"}
)
try:
with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
return "OK:" + json.loads(r.read().decode("utf-8"))["output"]
except urllib.error.HTTPError as e:
return "ERR:%d" % e.code
except Exception as e:
return "ERR:0 %s" % e
g_exportedScripts = (ask,)Le nom du contexte n'est pas figé dans ce fichier : il arrive en paramètre
de ask. La valeur, elle, est bien une constante — mais côté Basic, une par
macro : CONTEXT_FULL = "eval-oc-full" ici, CONTEXT_CELL = "eval-oc" dans la
macro cellule par cellule. Un seul pont Python sert donc les deux chaînes. Si
le rukh.py de la configuration cellule par cellule est déjà en place, le
remplacer par celui-ci.
Redémarrer LibreOffice après le dépôt.
2. La macro Basic
Outils → Macros → Modifier les macros, puis Mes macros → Standard →
Module1 — impérativement là, et non dans le classeur, pour éviter l'invite de
confiance à chaque ouverture du .ods.
Les deux macros peuvent cohabiter dans le même module : elles partagent
RukhAsk, Norm, StartsWith, JoinOpenings, StripFence et OneLine,
strictement identiques dans les deux documents.
Si la macro cellule par cellule est déjà installée, ne coller ci-dessous
que FormaliserColonne, Csv et SplitLines, plus les constantes
CONTEXT_FULL, CHUNK et RETRIES. Coller le bloc entier provoquerait une
erreur de compilation : Option Explicit, RUKH_SCRIPT, TARGET_HEADER,
MAX_CALLS, OPENING_CHARS, oRukhScript et les six fonctions partagées
seraient déclarés deux fois.
3. Le bouton
Affichage → Barres d'outils → Contrôles de formulaire, dessiner un bouton,
propriétés → Événements → Exécuter l'action → FormaliserColonne.
La macro
Option Explicit
Const RUKH_SCRIPT = "vnd.sun.star.script:rukh.py$ask?language=Python&location=user"
Const CONTEXT_FULL = "eval-oc-full"
Const TARGET_HEADER = "Commentaires réécrits"
Const CHUNK = 20 ' enregistrements par appel — le seul réglage à connaître
Const RETRIES = 1 ' relances automatiques sur écart de compte
Const MAX_CALLS = 45 ' garde-fou par exécution, marge sous le quota de 50/heure
Const OPENING_CHARS = 32 ' longueur des débuts transmis au lot suivant
Dim oRukhScript As Object ' résolu une fois, réutilisé pour toute l'exécution
Sub FormaliserColonne
Dim oDoc As Object, oSheet As Object, oCur As Object, oSel As Object, oAddr As Object
Dim oInd As Object
Dim lastRow As Long, lastCol As Long, headerRow As Long
Dim cChapitre As Long, cCritere As Long, cCotation As Long, cComm As Long
Dim cQuestion As Long, cTarget As Long
Dim selStart As Long, selEnd As Long, bSingle As Boolean
Dim aTodo() As Long, nTodo As Long, nDone As Long, nCalls As Long
Dim aOpen(1) As String
Dim aLines() As String, nOut As Long
Dim nPos As Long, nIn As Long, nTry As Long
Dim r As Long, c As Long, j As Long, k As Long
Dim h As String, s As String, sCot As String, sPayload As String, sRes As String
oDoc = ThisComponent
oSheet = oDoc.CurrentController.ActiveSheet
oCur = oSheet.createCursor()
oCur.gotoEndOfUsedArea(False)
lastRow = oCur.RangeAddress.EndRow
lastCol = oCur.RangeAddress.EndColumn
' --- ligne d'en-tête : colonne A = « Chapitre » ---
headerRow = -1
For r = 0 To lastRow
If Norm(oSheet.getCellByPosition(0, r).getString()) = "chapitre" Then
headerRow = r
Exit For
End If
Next r
If headerRow < 0 Then
MsgBox "Ligne d'en-tête introuvable (colonne A = « Chapitre »).", 16, "Rukh"
Exit Sub
End If
' --- repérage des colonnes par intitulé, jamais par position ---
cChapitre = -1 : cCritere = -1 : cCotation = -1 : cComm = -1 : cQuestion = -1 : cTarget = -1
For c = 0 To lastCol
h = Norm(oSheet.getCellByPosition(c, headerRow).getString())
If h = "chapitre" And cChapitre < 0 Then cChapitre = c
If h = "critère" And cCritere < 0 Then cCritere = c ' « Critère ID » exclu : identifiant interne
If StartsWith(h, "cotation") And cCotation < 0 Then cCotation = c
If h = Norm(TARGET_HEADER) Then
cTarget = c
ElseIf StartsWith(h, "commentaires") And cComm < 0 Then
cComm = c ' la colonne de sortie commence aussi par « Commentaires » : à exclure
End If
If StartsWith(h, "is question") And cQuestion < 0 Then cQuestion = c
Next c
If cChapitre < 0 Or cComm < 0 Or cQuestion < 0 Then
MsgBox "Colonnes « Chapitre », « Commentaires » ou « Is Question? » introuvables.", 16, "Rukh"
Exit Sub
End If
If cTarget < 0 Then
cTarget = lastCol + 1
oSheet.getCellByPosition(cTarget, headerRow).setString(TARGET_HEADER)
End If
' --- périmètre : c'est la sélection qui dit quoi traiter ---
oSel = oDoc.CurrentSelection
If IsNull(oSel) Then
MsgBox "Sélectionner d'abord une cellule ou une plage.", 16, "Rukh"
Exit Sub
End If
If oSel.supportsService("com.sun.star.sheet.SheetCellRanges") Then
oAddr = oSel.getRangeAddresses()(0) ' sélection multiple : première plage seulement
ElseIf oSel.supportsService("com.sun.star.sheet.SheetCellRange") Then
oAddr = oSel.RangeAddress
Else
MsgBox "Sélectionner d'abord une cellule ou une plage.", 16, "Rukh"
Exit Sub
End If
selStart = oAddr.StartRow
selEnd = oAddr.EndRow
bSingle = (selStart = selEnd)
' --- enregistrements à traiter : lignes de critère non encore réécrites ---
ReDim aTodo(lastRow)
nTodo = 0
For r = headerRow + 1 To lastRow
If r >= selStart And r <= selEnd Then
If StartsWith(oSheet.getCellByPosition(cChapitre, r).getString(), "Chapitre ") Then
If Trim(oSheet.getCellByPosition(cQuestion, r).getString()) = "" Then
If Trim(oSheet.getCellByPosition(cComm, r).getString()) <> "" Then
If bSingle Or Trim(oSheet.getCellByPosition(cTarget, r).getString()) = "" Then
aTodo(nTodo) = r
nTodo = nTodo + 1
End If
End If
End If
End If
End If
Next r
If nTodo = 0 Then
MsgBox "Rien à faire : toutes les cellules de la sélection sont déjà réécrites.", 64, "Rukh"
Exit Sub
End If
' --- mémoire des débuts, réamorcée depuis la colonne de sortie ---
aOpen(0) = "" : aOpen(1) = ""
For r = aTodo(0) - 1 To headerRow + 1 Step -1
s = Trim(oSheet.getCellByPosition(cTarget, r).getString())
If s <> "" Then
If aOpen(1) = "" Then
aOpen(1) = Left(s, OPENING_CHARS)
Else
aOpen(0) = Left(s, OPENING_CHARS)
Exit For
End If
End If
Next r
oInd = oDoc.CurrentController.Frame.createStatusIndicator()
oInd.start("Rukh : réécriture par lots…", nTodo)
nPos = 0 : nDone = 0 : nCalls = 0
Do While nPos < nTodo
If nCalls >= MAX_CALLS Then Exit Do
nIn = nTodo - nPos
If nIn > CHUNK Then nIn = CHUNK
' --- construction du CSV du lot ---
sPayload = "Enregistrements : " & nIn & Chr(10)
If aOpen(0) <> "" Or aOpen(1) <> "" Then
sPayload = sPayload & "Débuts déjà utilisés : " & JoinOpenings(aOpen) & Chr(10)
End If
sPayload = sPayload & Chr(10) & "Chapitre;Critère;Cotations;Commentaires" & Chr(10)
For k = 0 To nIn - 1
r = aTodo(nPos + k)
' cotations des lignes E.E. rattachées à ce critère, pour la règle « cependant »
sCot = ""
For j = r + 1 To lastRow
If Trim(oSheet.getCellByPosition(cQuestion, j).getString()) = "" Then Exit For
If cCotation >= 0 Then
s = Trim(oSheet.getCellByPosition(cCotation, j).getString())
If s <> "" Then
If sCot <> "" Then sCot = sCot & ";"
sCot = sCot & s
End If
End If
Next j
s = ""
If cCritere >= 0 Then s = oSheet.getCellByPosition(cCritere, r).getString()
' OneLine() sur le commentaire : un enregistrement tient sur une ligne physique,
' ce qui supprime à la source la principale cause de décalage.
sPayload = sPayload & _
Csv(oSheet.getCellByPosition(cChapitre, r).getString()) & ";" & _
Csv(s) & ";" & Csv(sCot) & ";" & _
Csv(OneLine(oSheet.getCellByPosition(cComm, r).getString())) & Chr(10)
Next k
' --- appel, avec relance sur écart de compte ---
nOut = -1
For nTry = 0 To RETRIES
sRes = RukhAsk(sPayload, CONTEXT_FULL)
nCalls = nCalls + 1
If Left(sRes, 7) = "ERR:429" Then
oInd.end()
MsgBox "Quota atteint après " & nDone & " cellule(s). Reste " & (nTodo - nDone) & _
". Relancer dans une heure.", 48, "Rukh"
Exit Sub
ElseIf Left(sRes, 4) = "ERR:" Then
oInd.end()
MsgBox "Erreur " & Mid(sRes, 5) & " sur le lot commençant ligne " & (aTodo(nPos) + 1) & _
". " & nDone & " cellule(s) écrite(s), conservées.", 16, "Rukh"
Exit Sub
End If
nOut = SplitLines(StripFence(Mid(sRes, 4)), aLines())
If nOut = nIn Then Exit For
Next nTry
' --- garde-fou de calage : sur écart, ce lot n'est pas écrit du tout ---
If nOut <> nIn Then
oInd.end()
MsgBox nOut & " ligne(s) reçue(s) pour " & nIn & " enregistrement(s) envoyé(s) : " & _
"ce lot n'a pas été écrit, pour ne pas décaler la colonne." & Chr(10) & _
"Lot commençant ligne " & (aTodo(nPos) + 1) & ". " & nDone & _
" cellule(s) écrite(s) avant, conservées." & Chr(10) & _
"Réduire CHUNK puis relancer : la reprise repartira de ce lot.", 16, "Rukh"
Exit Sub
End If
' --- écriture du lot ---
For k = 0 To nIn - 1
oSheet.getCellByPosition(cTarget, aTodo(nPos + k)).setString(OneLine(aLines(k)))
nDone = nDone + 1
Next k
oInd.setValue(nDone)
' --- débuts transmis au lot suivant ---
If nIn >= 2 Then
aOpen(0) = Left(OneLine(aLines(nIn - 2)), OPENING_CHARS)
Else
aOpen(0) = aOpen(1)
End If
aOpen(1) = Left(OneLine(aLines(nIn - 1)), OPENING_CHARS)
nPos = nPos + nIn
Loop
oInd.end()
If nTodo - nDone > 0 Then
MsgBox nDone & " cellule(s) réécrite(s) en " & nCalls & " appel(s). Reste " & _
(nTodo - nDone) & " : relancer.", 64, "Rukh"
Else
MsgBox nDone & " cellule(s) réécrite(s) en " & nCalls & " appel(s). Sélection terminée.", 64, "Rukh"
End If
End Sub
' --- pont vers le script Python ---------------------------------------------
Function RukhAsk(sMessage As String, sContext As String) As String
Dim oFactory As Object
Dim aArgs(1) As Variant
Dim aOutIdx(), aOutArgs()
If IsNull(oRukhScript) Then
oFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory")
oRukhScript = oFactory.createScriptProvider("").getScript(RUKH_SCRIPT)
End If
aArgs(0) = sMessage
aArgs(1) = sContext
RukhAsk = oRukhScript.invoke(aArgs(), aOutIdx(), aOutArgs())
End Function
' --- utilitaires propres au mode colonne -------------------------------------
' Champ CSV : toujours entre guillemets, guillemet interne doublé.
Function Csv(sText As String) As String
Csv = """" & Join(Split(sText, """"), """""") & """"
End Function
' Découpe la réponse en lignes non vides. Les lignes vides sont ignorées plutôt
' que comptées : les instructions les interdisent, et les ignorer évite de
' rejeter un lot par ailleurs correct.
Function SplitLines(sText As String, aOut() As String) As Long
Dim a() As String, k As Long, n As Long, s As String
a = Split(Join(Split(sText, Chr(13) & Chr(10)), Chr(10)), Chr(10))
ReDim aOut(UBound(a))
n = 0
For k = 0 To UBound(a)
s = Trim(a(k))
If s <> "" Then
aOut(n) = s
n = n + 1
End If
Next k
SplitLines = n
End Function
' --- utilitaires partagés avec la macro cellule par cellule -------------------
Function Norm(v As String) As String
Dim s As String
s = LCase(Trim(v))
s = Join(Split(s, Chr(10)), " ")
s = Join(Split(s, Chr(13)), " ")
s = Join(Split(s, Chr(9)), " ")
Do While InStr(s, " ") > 0
s = Join(Split(s, " "), " ")
Loop
Norm = Trim(s)
End Function
Function StartsWith(sText As String, sPrefix As String) As Boolean
StartsWith = (Left(sText, Len(sPrefix)) = sPrefix)
End Function
Function JoinOpenings(aOpen() As String) As String
If aOpen(0) <> "" And aOpen(1) <> "" Then
JoinOpenings = aOpen(0) & " | " & aOpen(1)
Else
JoinOpenings = aOpen(0) & aOpen(1)
End If
End Function
Function StripFence(sText As String) As String
Dim a() As String, i0 As Long, i1 As Long, k As Long, sOut As String
a = Split(Join(Split(sText, Chr(13) & Chr(10)), Chr(10)), Chr(10))
i0 = LBound(a)
i1 = UBound(a)
Do While i0 <= i1 And Trim(a(i0)) = ""
i0 = i0 + 1
Loop
Do While i1 >= i0 And Trim(a(i1)) = ""
i1 = i1 - 1
Loop
If i0 <= i1 And Left(Trim(a(i0)), 3) = "```" Then i0 = i0 + 1
If i0 <= i1 And Trim(a(i1)) = "```" Then i1 = i1 - 1
For k = i0 To i1
If sOut <> "" Then sOut = sOut & Chr(10)
sOut = sOut & a(k)
Next k
StripFence = sOut
End Function
Function OneLine(sText As String) As String
Dim s As String
s = Join(Split(sText, Chr(13)), " ")
s = Join(Split(s, Chr(10)), " ")
Do While InStr(s, " ") > 0
s = Join(Split(s, " "), " ")
Loop
OneLine = Trim(s)
End FunctionUtilisation
Comme en mode cellule par cellule, c'est la sélection qui décide du
périmètre — la différence est qu'elle est ensuite découpée en lots de
CHUNK enregistrements.
- Tout le chapitre : cliquer sur l'en-tête de la colonne, lancer. Trois appels environ, aucune intervention entre les lots.
- Un lot précis : sélectionner les lignes voulues.
- Reprendre : relancer sur la même sélection. Les cellules déjà écrites sont sautées, la reprise repart au premier lot non écrit.
- Refaire une cellule : sélectionner une seule ligne. La cellule est
réécrite même si elle l'était déjà — mais pour ce geste, la macro
Formaliserdu mode cellule par cellule est plus économique.
Le compte rendu final indique le nombre de cellules écrites et le nombre d'appels consommés, ce dernier étant ce qui se déduit du quota horaire.
Quota, coût et durée
| Critères à réécrire, chapitre de référence | 57 |
| Enregistrements par appel | CHUNK, 20 par défaut |
| Appels pour un chapitre | 3 |
| Quota API | 50 requêtes/heure, par adresse IP |
| Garde-fou par exécution | MAX_CALLS = 45 appels |
| Tokens d'entrée par chapitre | ~18 000 (estimation : ~3 900 d'instructions + ~2 000 de CSV par appel) |
| Coût d'entrée estimé | ~0,04 $ par chapitre, contre ~0,46 $ cellule par cellule |
| Durée d'un appel | ~1 à 2 min (estimation, à mesurer) |
| Durée d'un chapitre | ~4 à 6 min |
Le coût de sortie, lui, ne change pas : c'est le même texte qui est produit dans les deux modes. L'économie porte entièrement sur les instructions, qui sont renvoyées 3 fois au lieu de 57.
Les trois propriétés de reprise sont conservées, mais à la maille du lot :
- Reprise automatique — seules les cellules dont
Commentaires réécritsest vide sont traitées, et la mémoire des débuts est relue dans cette même colonne. - Écriture par lot — chaque lot est écrit dès sa validation. Une coupure fait perdre au plus le lot en cours, soit ~20 cellules.
- Arrêt propre — sur
429, sur erreur HTTP et sur écart de compte, la macro s'arrête en indiquant ce qui a été écrit et où reprendre.
Limites et risques
- Le dépassement de délai est le vrai risque du mode colonne. Une réponse
de vingt commentaires demande une à deux minutes ; le
proxy_read_timeoutdu nginx placé devant l'API reste à vérifier, et c'est lui qui décidera duCHUNKpraticable. En cas de coupure systématique au même endroit, descendreCHUNKà 10 avant toute autre hypothèse. LeTIMEOUTde 300 s côté Python est délibérément large pour que ce soit l'API qui tranche. - L'interface est figée pendant chaque appel, une à deux minutes durant.
Basic est synchrone : ni
await, ni interruption par Échap. Enregistrer le fichier avant de lancer. - Un lot rejeté est à refaire en entier. C'est le prix du garde-fou : mieux vaut vingt cellules à refaire qu'une colonne décalée.
- Le contrôle porte sur le nombre de lignes, pas sur leur ordre. Voir « Le garde-fou de calage », plus haut.
- Sélection multiple ignorée au-delà de la première plage.
- La cohérence s'arrête à la borne du lot. Un acronyme est développé une
fois par lot, donc trois fois par chapitre au lieu de cinquante-sept — mieux
qu'en mode cellule, pas parfait pour autant. Augmenter
CHUNKaméliore ce point et aggrave le risque de délai : c'est le seul arbitrage à faire.
Variante Apache OpenOffice
Toute la macro fonctionne telle quelle : UNO et StarBasic sont partagés. Seul
le pont ne transfère pas — AOO 4.1.x embarque Python 2.7. Route pragmatique :
remplacer RukhAsk par un appel curl synchrone (4ᵉ argument de Shell à
True) écrivant le corps JSON dans un fichier temporaire, avec les utilitaires
JsonEscape et JsonStringValue de
docs/SPREADSHEETS.md transposés en remplaçant
AscW/ChrW$ par Asc/Chr$.
Le mode colonne rend cette variante plus fragile que le mode cellule : le corps JSON à échapper contient tout un CSV, et la réponse à extraire fait plusieurs milliers de signes — soit exactement ce que l'extracteur artisanal gère le moins bien. Si le poste peut recevoir LibreOffice, l'y installer.
Déploiement du contexte eval-oc-full
data/ et docs/private/ sont dans le .gitignore : le contexte créé en
local ne part pas avec un git push. Pour le rendre disponible sur
rukh.w3hc.org, le déposer via l'API de contextes (POST /context puis
POST /context/upload avec instruction-file.md), signature SIWE à l'appui —
l'adresse doit correspondre au creatorAddress de index.json. Tant que ce
n'est pas fait, la macro renverra des réponses hors contexte, et le garde-fou
de calage rejettera probablement tous les lots.
Après toute modification de instruction-file.md, refaire le dépôt : c'est le
fichier déployé qui fait foi, pas la copie locale.