OpacMarc: Organización de registros en base a códigos de relación

Estas notas fueron motivadas por una consulta de Gabriela Cirelli (IB-CAB), en el grupo de Catalis (febrero 2009).

Introducción

Queremos organizar el display de una lista de registros bibliográficos asociados a un encabezamiento (pensemos por ahora en nombres personales), de manera tal que queden separados en bloques los registros donde la persona aparece como “autor”, de aquellos donde desempeña otra función. A su vez, sería deseable subclasificar estos últimos de acuerdo al tipo de función.

Un ejemplo sencillo puede verse en este mensaje del grupo Catalis.

Consideremos, para simplificar, que “autor” corresponde a un 100 o un 700 sin código de relación (subcampo $4), y “otra función” corresponde a un 700 con $4.

Implementación

Si ---tal como sucede actualmente--- al invertido de la base BIBLIO mandamos los puntos de acceso sin hacer distinción según la relación (700$4), entonces al recuperar usando un punto de acceso obtendremos indiscriminadamente todos los registros asociados, y la clasificación en base a la relación habrá que hacerla analizando cada uno de los registros recuperados. Esto agrega un costo a cada búsqueda, proporcional al número de registros recuperados.

Si en cambio mandamos al invertido de forma diferenciada los “autores” y los “otra-función”, entonces podemos hacer dos búsquedas separadas: con una recuperamos los registros donde Fulano aparece como “autor”, y con otra aquellos donde Fulano aparece en “otra-función”. Luego ordenamos cada uno de esos dos conjuntos por el criterio que corresponda. Para aplicar esa distinción a nivel del invertido podemos usar diferentes tags en la FST, e.g. 9100 para “autor” y 9101 para “otra-función”.

Pero, ¿y si queremos ser más específicos y poder recuperar solamente registros donde Fulano es editor, o director de tesis, en vez de juntar todos los casos de “otra-función” en una misma bolsa? Tal vez deberíamos añadir nuevas claves al diccionario, de la forma _NAME_<id>_<relación>:

_NAME_1234_--- <= "autor" (i.e., relación no especificada)
_NAME_1234_EDT <= editor
_NAME_1234_AUI <= prologuista
_NAME_1234_TRL <= traductor

Si la persona tiene más de un código de relación (p.ej. aui y trl):

_NAME_1234_AUI_TRL <= traductor y prologuista

aunque quizás sea mejor enviarlos al invertido en forma separada, así lo recuperamos al buscar por una cualquiera de las relaciones:

_NAME_1234_AUI <= prologuista
_NAME_1234_TRL <= traductor

Actualmente, sólo estamos usando esta clave:

_NAME_1234 <= no discrimina relaciones

Parece que sería redundante conservarla si vamos a utilizar las propuestas arriba, pero... ¡Atención! Si bien para una búsqueda general podríamos usar _NAME_1234_$ (que agrupa todos los casos), para determinar de manera sencilla el número de postings (registros asociados al encabezamiento) conviene tener también una clave “limpia” _NAME_1234, aunque esto resulte redundante. Actualmente se usa algo así para encontrar los postings asociados al encabezamiento v9:

f(npost(['biblio']'_NAME_'v9),1,0)

y no podemos usar npost() con un término que incluya el sufijo $.

Encabezamientos versus identificadores numéricos

Actualmente sólo usamos las claves _NAME_<id> para poder averiguar fácilmente la cantidad de postings asociados a un encabezamiento (hacer un wcgrep de “_NAME_” y de “_%s_”), pero no para las búsquedas por encabezamiento en la base BIBLIO, que siguen dependiendo de la forma *textual* del encabezamiento (con los consiguientes problemas asociados al límite en la longitud de las claves, ver heading-match-in-bib-record.xis). Si bien es conveniente que la URL de una búsqueda de este tipo tenga como query el encabezamiento, y no un identificador numérico (que podría llegar a variar), podríamos introducir un paso intermedio que mapee el encabezamiento, recibido vía URL, a su identificador, y luego hacer la búsqueda en biblio de manera precisa, usando sólo el identificador. No importa que el identificador sea inestable (i.e., que pueda cambiar en una futura generación de las bases del OPAC), ya que no lo estaríamos exponiendo públicamente. (¿Por qué no habremos hecho esto antes?)

Cómo hacer el mapeo encabezamiento => id (on the fly, con cada petición):

Normalizar el encabezamiento (headsort.pft?)

Buscar registros de la base NAME mediante la forma normalizada del encabezamiento

0 hits: mensaje de error

1 o más hits: hacer loop sobre los registros recuperados, para verificar exactitud del match

0 matches: mensaje de error

1 match: obtener id

2 o más matches: error?

Este procedimiento sería similar (pero más fácil y limpio) al actualmente empleado, que hace la búsqueda directamente en BIBLIO, sin pasar por el id. Si hacemos esto, ¿podemos omitir los encabezamientos completos en el invertido de BIBLIO?

Archivos a modificar

biblio.fst

Modificar las líneas 9100 que generan claves de la forma _NAME_<id>:

if p(v700) then
proc('d1000', 'd1001'), /* limpiamos campos auxiliares */
proc(
/*'d1000',*/
(,
'a1000¦',
replace(v700*3,'^','¦a1000¦'),
'¦',
'a1000¦##¦',
)
),
/* loop sobre v1000 */
,(,
if v1000 = '##' then
/,
proc('d1001'), /* almacena los $4 de una ocurrencia del v700 */
else if 'abcdq' : v1000.1 then
'~'v1000*1,
/* ATENCION! esto sólo funciona si 700$4 aparece *ANTES* que 700$9 */
else if '4' = v1000.1 then
proc('d1001','a1001|',v1001[1],'_',v1000*1,'|'),
else if '9' = v1000.1 then
/,
'_NAME_', /* sin sufijo */
v1000*1,
/,
'_NAME_', /* con sufijo para indicar la relación */
v1000*1,
if v1001[1] = '' then '_---' else v1001[1], fi, /
fi,fi,fi,fi,
,),
fi,

heading-match-in-bib-record.xis

El algoritmo sería algo como esto:

Encontrar ID del encabezamiento

Normalizar query para convertirlo en la expresión de búsqueda expr (esto es, aplicar a query las transformaciones necesarias para que pueda ser utilizado como clave de búsqueda)

Ejecutar una búsqueda en la base de encabezamientos correspondiente (NAME, SUBJ), usando la expresión expr

Si no hay hits: error, abortar

matches <- {}

Para cada encabezamiento H encontrado:

si H coincide con query: matches <- matches + {encabezamiento}

Si matches tiene cero o más de un elemento: error, abortar

ID <- id del único encabezamiento en matches

Encontrar relaciones asociadas al encabezamiento

relations <- {}

Para cada clave k del diccionario BIBLIO entre _NAME_<ID>_--- y _NAME_<ID>_zzz:

relations <- relations + {los 3 caracteres finales de k}

Encontrar y procesar los registros bibliográficos

Para cada relación rel en relations:

Ejecutar una búsqueda en BIBLIO, usando como expresión _NAME_<ID>_<rel>

Mostrar un encabezado con la relación, e.g. 'Editor' (excepto para “autor”) (Para esto necesitamos una tabla que mapee códigos de relación a términos en el idioma de la interfaz)

Mostrar los registros asociados a esa relación

script

<!-- Encontrar ID del encabezamiento -->
<!-- Primero, normalizar query.
Ver cómo aparecen los encabezamientos en el diccionario de las bases NAME, SUBJ.
Notar que no coincide con el diccionario de BIBLIO. -->
<field tag="3001" action="replace"><pft>hacer algo a v2001</pft></field>
<!-- Búsqueda -->
<do task="search">
<parm name="db"><pft>v2003</pft></parm> <!-- v2003: NAME / SUBJ -->
<parm name="expression"><pft>v3001</pft></parm> <!-- v2001 normalizado -->
<define>1002 Isis_Total</define>
<loop>
<field action="import" tag="list">2001</field>
<flow action=""><pft>if val(v1002) <> 1 then 'Quit' fi</pft></flow>
<field tag="9" action="replace"><pft>if v1 = v2001 then v9 fi</pft></field>
<field tag="9" action="export">9</field>
</loop>
</do>
<!-- Encontrar relaciones asociadas al encabezamiento -->
<do task="keyrange">
<parm name="db">BIBLIO</parm>
<parm name="from"><pft>'_'v2003'_', v9, '_'</pft></parm>
<parm name="to"><pft>'_'v2003'_', v9, '_ZZZ'</pft></parm>
<define>1 Isis_Key</define>
<loop>
<list action="load" type="list"><pft>v1</pft></list>
</loop>
</do>
<!-- Encontrar y procesar los registros bibliográficos -->
<!-- La lista contiene las relaciones (claves para las búsquedas) -->
<do task="list">
<define>1 Isis_Item</define>
<!-- Una búsqueda por cada clave -->
<!-- FIXME - Vamos a tener un problema con la limitación de wxis a una única lista;
en cada búsqueda necesitamos una lista para almacenar los resultados antes de
presentarlos, pero todas las búsquedas viven dentro de un ''do task=list''
exterior. El script agrep-multi.xis parece que resuelve un problema similar
(usa un <do> + iocc) -->
<display><pft>'<h3>Relación: ', right(v1, 3),'</h3>'</pft></display>
<do task="search">
<parm name="db">BIBLIO</parm>
<parm name="expression"><pft>v1</pft></parm>
<define>1002 Isis_Total</define>
<loop>
<!-- procesar cada registro bibliográfico -->
<display><pft>'Total: ',v1002,'<br>'</pft></display>
</loop>
</do>
</do>

Encabezamientos en el diccionario

Debemos prestar atención a la diferencia entre estas dos maneras de enviar encabezamientos a los diccionarios, según la base.

¿Por qué reciben tratamientos diferentes?

¿Qué uso damos a las claves en cada caso?

¿Podrían unificarse? Mejor aún: ¿para qué necesitaríamos seguir usando estas claves en la base BIBLIO, si vamos a recuperar por encabezamientos usando IDs?

NAME / SUBJ

mx name gizmo=REMOVE-CHARS,1 <= elimina apóstrofes y otros caracteres equivalentes
fst=@HEADINGS.FST <= 2 0 '~',@HEADSORT.PFT,
actab=AC-ANSI.TAB
uctab=UC-ANSI.TAB
fullinv=name
----------------------------
~ABREGU MARTIN <= 1 "~aAbregú, Martín."
~ADERINWALE AYODELE
~ADLER GLENN 1958 <= 1 "~aAdler, Glenn,~d1958-"
~AFRICA INSTITUTE OF SOUTH AFR
~AFRICA LEADERSHIP FORUM
~AGUERO FELIPE
~ALBERT EINSTEIN INSTITUTION C
~ALEN ANDRE
~ALFONSIN RAUL
~ARMSTRONG CHARLES K

Estas claves se utilizan en browse-headings.xis, mediante un keyrange (y no un search). El input es browseTerm, el término inicial a partir del cual se desea recorrer el índice de encabezamientos. Este término (ingresado por el usuario, potencialmente una cadena arbitraria de caracteres) se normaliza eliminando espacios iniciales y el carácter \, anteponiendo ~a, aplicando HEADSORT.PFT, y finalmente eliminando el carácter final (si es punto, coma o espacio). El propósito de esta normalización es lograr que la cadena resultante sea consistente con las claves del diccionario de NAME.

BIBLIO

mx biblio gizmo=DICTGIZ,100,110,111,130,700,710,711,730,800,810,811,830
gizmo=DICTGIZ,240,245,246,440,740,600,610,611,630,650,651,653,655,656
fst=@BIBLIO.FST <= 9100 0 if p(v700) then proc('d1000',(,'a1000¦',replace(v700*3,'^','¦a1000¦'),'¦','a1000¦##¦',) ), ,(,if v1000 = '##' then / else if 'abcdq' : v1000.1 then '~'v1000*1, else if '9'=v1000.1 then /'_NAME_',v1000*1/ fi,fi,fi ,), fi,
actab=AC-ANSI.TAB
uctab=UC-ANSI.TAB
stw=@BIBLIO.STW
fullinv=biblio
----------------------------
~ABREGU_MARTIN <= 700 "1#^aAbregú, Martín.^9000676"
~ADERINWALE_AYODELE
~ADLER_GLENN~1958- <= 700 "1#^aAdler, Glenn,^d1958-^9001507"
~AFRICA_INSTITUTE_OF_SOUTH_AFR
~AFRICA_LEADERSHIP_FORUM
~AGUERO_FELIPE
~ALBERT_EINSTEIN_INSTITUTION_[
~ALEN_ANDRE
~ALFONSIN_RAUL
~ARMSTRONG_CHARLES_K

Estas claves se utilizan en heading-match-in-bib-record.xis, mediante un search. El input es query, el encabezamiento para el cual queremos encontrar registros bibliográficos asociados. En condiciones “normales”, query ha sido generado automáticamente a partir de un encabezamiento existente, y no ingresado por el usuario. La normalización consiste en: reemplazar ^x por ~ (para cualquier x), y aplicar DICTGIZ.PFT. El propósito de esta normalización es lograr que la cadena resultante sea consistente con las claves del diccionario de BIBLIO.

¿A qué viene este DICTGIZ? El propósito de este gizmo era normalizar los encabezamientos de acuerdo con estos criterios:

Acortar: debido a la limitación que impone el diccionario de CDS/ISIS a la longitud de las claves, necesitamos quitarnos de encima caracteres no esenciales, como puntuación y algunos espacios

Evitar conflictos con la sintaxis de búsqueda: espacios, paréntesis, símbolos +, * son reemplazados

A diferencia de la base NAME, donde solamente estábamos interesados en utilizar las claves asociadas a encabezamientos en forma aislada, para identificar el MFN del registro correspondiente, en la base BIBLIO debíamos considerar la posibilidad de que las claves fueran parte de expresiones de búsqueda más complejas ---es decir, con operadores---, de ahí la importancia del segundo criterio. Posiblemente esta haya sido la razón para aplicar diferentes tratamientos.

Asuntos varios

¿Qué pasa si un nombre aparece como “autor” y como “otra función” en un mismo registro? Creo que no debería suceder que un nombre se repitiera en campos 100/700.

¿Qué hacemos con la mezcla que puede llegar a haber dentro del conjunto de registros correspondientes a “otra función”? Quizás ahora sí podríamos analizar registro por registro, una vez que nos acotamos a este conjunto. Tendremos un problema si un nombre tiene más de un $4 en un mismo registro.

¡Ojo con los puntos de acceso de nombre-título, 700$a$t (con o sin $4)!

Ver la relación entre esta organización del listado y la paginación.

Algunos detalles podrán depender del tipo de relaciones que sean más importantes (i.e., que nos interese destacar) en cada catálogo particular. Por ejemplo, los roles “fotógrafo” o “ilustrador” podrían ser más relevantes en una colección de arte que en una general.

Preguntas que deberían ser fáciles de responder:

¿En qué funciones aparece el encabezamiento H? Hacer un keyrange en BIBLIO, from=_NAME_<H>_ to=_NAME_<H>_ (¿y si quisiéramos averiguarlo sólo mirando NAME?)

¿En cuántos registros aparece el encabezamiento H desempeñando la función f? npost(['BIBLIO']'_NAME_<H>_<f>'); ejemplo: npost(['BIBLIO']'_NAME_12345_EDT') (función: editor)

Análisis de la base

Para permitir la organización de registros aquí planteada, es fundamental que los puntos de acceso incluyan información (normalizada) acerca de la relación. No siempre esta información ha sido registrada, de manera que antes de considerar la implementación de esta funcionalidad para una determinada base bibliográfica, analicemos el uso de los subcampos de relación (7xx$4, 7xx$e), y hagamos las modificaciones apropiadas.

Uso de subcampos para indicar relación:

mxtb biblio create=v700_e "30:(v700^e/)"
mx v700_e "pft=v999,c6,v1/" now
mxtb biblio create=v700_4 "30:(v700^4/)"
mx v700_4 "pft=v999,c6,v1/" now

Los 700$e deberían convertirse en 700$4. TAREA: escribir script.

(Repetir para otros 7xx.)

Detección de registros donde se ha omitido el uso de subcampos para indicar relación (ejemplo típico, resultados sólo aproximadamente válidos):

mx biblio "pft=if v245^c:'edit' and not v700^e:'edit' and not v700^4:'edt' then mfn/v245/(v700/)# fi" now lw=300