Koha
Koha loan history script
We’ve been looking at intergrating the Koha LMS with the LORLS reading list management system, as we know of sites using Koha that are interested in LORLS (and also because we’re interested in seeing how Koha works!). After getting the basic integration for looking up works using Koha’s Z39.50 server and finding out item holdings/availability working last week, the next thing to tackle was to get loan histories out of Koha.
We use loan histories in LORLS’s purchase predictor code. We need to be able to grab an XML feed of both current and old loan issues, which is then used to work out what the peak number of concurrent loans have been made for an item and thus whether it has had sufficient demand to warrant purchasing additional copies.
For the current loans we need to know the date and time they were issued and for old issues we want both the issue date/time and the return date/time. For both current and old loan issues we also want to know the type (status) of the item (“long_loan”, “short_loan”, etc) and which department the borrower of the loan came from. The latter is so that we can apportion purchasing costs between different departments for the cases where multiple modules include the same books on their reading lists.
The item status is fairly easy to do in Koha – we’d already created item types in Koha and these can easily be mapped in the Perl code that implements the XML API into the long_loan, short_loan, week_loan, etc status format our purchase predictor code already expects. Indeed if we wanted to we could make the items types in Koha just be “long_loan”, “short_loan” and “week_loan” so no mapping would be required, but a mapping function adds a bit of flexibility.
The borrowers’ department is a bit more involved. It appears that in Koha this would be an “extended attribute” which needs to be enabled (it doesn’t appear to be on by default). I created an extended borrower attribute type of called DEPT, and then entered some of Loughborough’s department codes as a controlled vocabulary for it. In real life these would have to be slipped into Koha as part of a regularly (probably daily) borrower upload from our central reservation systems, which is roughly how we do it with our production Aleph LMS. In our test environment I just added the extended attribute value manual to a couple of test users so that we could play with the code.
At the end of this posting you’ll find the resulting Perl code for creating this simple XML feed, which Koha sites might find handy even if they don’t use LORLS. One interesting thing to note in Koha is that the isbn field of the biblioitems table appears to contain more than one ISBN, separated by white space and vertical bar character (” | “). This means that you need to do a “like” match on the ISBN. This was a little unexpected and took me a while to track down what was wrong with my SQL when I had a simple “=” rather than a “like” in the select statements! The separation of biolios (works), biblioitems (manifestations) and items (er, items) is nicely done though.
#!/usr/bin/perl
use strict;
use lib '/usr/share/koha/lib';
use CGI;
use DBI;
use C4::Context;
$| = 1;
my $q = new CGI;
my $isbn = $q->param('isbn');
my $dept_code = $q->param('dept_code');
my $return_period = $q->param('return_period') || 365;
print STDOUT "Content-type: text/xmlnn";
print STDOUT "<loan_history>n";
my $status_map = {
'BK' => 'long_loan',
'SL BOOK' => 'short loan',
'WL BOOK' => 'week loan',
'REFBOOK' => 'reference',
};
my $dbh = C4::Context->dbh;
my $total_number = 0;
my $sql =
'select items.itemnumber, issues.issuedate, biblioitems.itemtype, ' .
' borrower_attributes.attribute ' .
'from biblioitems, items, issues, borrowers, borrower_attributes, ' .
' borrower_attribute_types ' .
'where biblioitems.isbn like ' . $dbh->quote("%$isbn%") . ' and ' .
' biblioitems.biblioitemnumber = items.biblioitemnumber and ' .
' items.itemnumber = issues.itemnumber and ' .
' issues.borrowernumber = borrowers.borrowernumber and ' .
' borrowers.borrowernumber = borrower_attributes.borrowernumber and '.
' borrower_attributes.code = borrower_attribute_types.code and ' .
' borrower_attribute_types.description = "Department" and ' .
' borrower_attributes.attribute = ' . $dbh->quote($dept_code);
my $currentloan = $dbh->prepare($sql);
$currentloan->execute;
while (my ($id, $issuedate, $status, $bor_type) = $currentloan->fetchrow_array) {
$status = $status_map->{$status};
print STDOUT " <loan>n";
print STDOUT " <issue_date>$issuedate</issue_date>n";
print STDOUT " <status>$status</status>n";
print STDOUT " <dept_code>$bor_type</dept_code>n";
print STDOUT " <number>1</number>n";
print STDOUT " <current>Y<current>n";
print STDOUT " </loan>n";
$total_number++;
}
$currentloan->finish;
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime(time-($return_period * 24 * 60 * 60));
$year += 1900;
$mon++;
my $target_date = sprintf("%04d%02d%02d",$year,$mon,$mday);
$sql =
'select items.itemnumber, old_issues.issuedate, old_issues.returndate, ' .
' biblioitems.itemtype, borrower_attributes.attribute ' .
'from biblioitems, items, old_issues, borrowers, borrower_attributes, ' .
' borrower_attribute_types ' .
'where biblioitems.isbn like ' . $dbh->quote("%$isbn%") . ' and ' .
' biblioitems.biblioitemnumber = items.biblioitemnumber and ' .
' items.itemnumber = old_issues.itemnumber and ' .
' old_issues.returndate > ' . $dbh->quote($target_date) . ' and ' .
' old_issues.borrowernumber = borrowers.borrowernumber and ' .
' borrowers.borrowernumber = borrower_attributes.borrowernumber and '.
' borrower_attributes.code = borrower_attribute_types.code and ' .
' borrower_attribute_types.description = "Department" and ' .
' borrower_attributes.attribute = ' . $dbh->quote($dept_code);
my $pastloan = $dbh->prepare($sql);
$pastloan->execute;
while (my ($id, $issuedate, $return_date, $status, $bor_type) = $pastloan->fetchrow_array)
{
$status = $status_map->{$status};
print STDOUT " <loan>n";
print STDOUT " <issue_date>$issuedate</issue_date>n";
print STDOUT " <return_date>$returndate</return_date>n";
print STDOUT " <status>$status</status>n";
print STDOUT " <dept_code>$bor_type</dept_code>n";
print STDOUT " <number>1</number>n";
print STDOUT " <current>N<current>n";
print STDOUT " </loan>n";
$total_number++;
}
$pastloan->finish;
print STDOUT " <total_loan_count>$total_number</total_loan_count>n";
print STDOUT "</loan_history>n";
Integrating LORLS with Koha
At Loughborough we use Ex Libris’s Aleph as our Library Management System (LMS). Our LORLS reading list system also makes use of Aleph in a number of ways:
- To provide bibliographic information via Z39.50, allowing people to quickly add works to reading lists by simply entering the ISBN,
- In batch jobs to check which works the library holds, again using Z39.50,
- To find how many items for a work are held and how many are available for loan which is displayed on a work’s detail pop up in CLUMP. This has been done using the Ex Libris X Server product (not to be confused with X11 servers!),
- To build up lists of current and past loans, used for purchase prediction, high demand reporting and usage tracking. This is mostly done using custom local Perl scripts that interrogate Aleph’s Oracle database directly and then provide an XML API that LORLS code calls.
We’ve recently had another site say they are interested in using LORLS, but they use the Koha LMS. They asked us what would be needed to allow LORLS to integrate with Koha? Luckily Koha is open source, so we could just download a copy and install it on a new virtual machine to have a play with and see what was needed.
Looking up bibliographic information was pretty simple to support. Koha has a built in Z39.50 server, so all we needed to do was to tweak the LUMP.pm file on our dev server to point the Z3950Hostname(), Z3950Port() and Z3950DBName() to point to our new VM, the port that Koha’s Z39.50 server is running on and the new Z39.50 database name (which appears to default to “biblios”). That seemed to work a treat.
Get item holdings for works was a bit more involved. Koha obviously doesn’t have Ex Libris’s X server, so we needed another way to get similar data. Luckily Koha does implement some of the Digital Library Federation Integrated Library System – Discovery Interface recommendations. One of these Application Programming Interfaces (APIs) is called GetRecords() and, given a set of system record identifiers, will return an XML document with just the sort of information we need (eg for each item linked to a work we get things like item type, whether it can be loaned, what its due date is if it is on loan, if it is damaged, lost, etc).
Unfortunately LORLS doesn’t know anything about Koha’s system record identifiers, and the GetRecords() ILS-DI API doesn’t appear to allow searches based on control numbers such as ISBNs. To get the system record numbers we can however fall back on Z39.50 again, which Koha uses to implement the ILS-DI SRU type interfaces. Searching for a Bib-1 attribute of type 1007 with the value set to be the ISBN gets us some nice USMARC records to parse. We need to look at the 999 $c field in the MARC record as this appears to be the Koha system record identifier.
Just to make things interesting we discovered accidentally that in Koha you can end up with more than one work for the same ISBN (and each work can then have multiple physical items). I guess this is a flexibility feature in some way, but it means that we need to make sure that we get all the system record identifiers that match our ISBN from LORLS and then pass all of these to the Koha GetRecords() API. Luckily the API call can take a whole set of system record identifiers in one go, so this isn’t too much of a problem.
One thing we do have to have in the code though is some way of distinguishing between loan categories (long loan, short loan, week loan, reference, etc). In Koha you can create an arbitrary number of item types which can correspond to loan categories, to which you can then assign things like differing loan rules. We slipped in:
- BK – normal long loan book (the default in Koha it seems)
- REFBOOK – a book that can’t be loaned.
- SL BOOK – a short loan book (usually loaned for less than a day – our “high demand” stock),
- WL BOOK – a book that can be loaned for a week (effectively moderately in demand works).
Our code currently then has these hard coded in order to return the same sort of holdings Perl structure that Aleph did. Extra item types assigned in Koha will need to been inserted into this code – we might have to think of a “nice” way of doing this if folk make lots of these changes on a regular basis but I suspect item types are one of those things that are configured when an LMS is setup and rarely, if ever, tweaked again.
In the StructuralUnit.pm Perl module I created a new method called Koha_ILS_DI_Holdings() to implement the new Koha item holdings and availability code. The existing Holdings() method was renamed to Aleph_Holdings() and a new Holdings() method implemented that checks a new Holdings() method in LUMP.pm for the name of a holdings retrieval algorithm to use (currently now either “Koha:ILS-DI” which selects the new code, or anything else defaulting back to the old Aleph_Holdings() method). This means that if someone else comes along with XYZ Corp LMS that uses some other whacky way of retrieving holdings availability we can simply write a new method in StructuralUnit.pm and add another if clause to the Holdings() method to allow a quick LUMP.pm change to select it. The advantage of this is that other code in LORLS that uses the Holdings() method from StructuralUnit.pm doesn’t have to be touched – it is insulated from the messy details of which implementation is in use (ooh, object oriented programming at work!).
This appears to work on our test set up and it means we can just ship a new StructuralUnit.pm and LUMP.pm file to the folk with Koha and see how they get on with it. Our next trick will be getting loan history information out of Koha – this may take a bit more work and its really replacing functionality that we’ve mostly implemented solely for Loughborough (using our custom scripts as even Aleph didn’t provide a usable API for what we needed). It doesn’t appear at first glance that Koha’s ILS-DI APIs cover this use case – I guess we’re a bit odd in being interested in work loan histories!