Click or drag to resize
Integration with National ID service

[This is preliminary documentation and is subject to change.]

Patient identification within TOPICA

TOPICA is used to enter data on patients. Patients need to be uniquely identified within a TOPICA application. The identifiying property used in this identification is a property named "National ID" in this documentation (but note that the label in the application may be something different - depending on the current culture and/or application-specific configuration settings).

Example - the cultures (=countries) supported in the current release:

Country

National ID label

Denmark

CPR-nummer

Sweden

Personnummer

UK

NHS-ID ?

USA

Social Security Number

The TOPICA framework assures, that no two patients may have same "National ID". It also builds country / culture specific databases designed to hold various national ID formats, with validations functions etc.

There is NO built-in lookup / validation against any central "National ID" service in the framework. There may be plugins available for certain countries / national ID services, though.

Using National ID services in TOPICA

A TOPICA application may run just fine without intregration to any central "National ID" service. But in most real world situations, it is necessary to combine patient data entered in a TOPICA application with data from other systems. To validate that correct national ids are used, it is necessary to implement a mechanisms to perform lookup against a "National ID" service.

Also, when using a "National ID" service, patient properties like name, address, etc. will be filled in automatically, when a patient is created. This eliminates redundant data registration.

As described above, TOPICA framework does NOT contain any built-in lookup / validation against any central "National ID" service. Reasons for this are as follow:

  • Central "National ID" services vary from country to country. Some countries have national IDs that are used for a mulitude of purposes - some countries may have IDs used only for specific purposes (e.g. within the health sector).

  • Even when central national ID service(s) exists, some TOPICA customers may choose NOT to use one of the central services, but choose to perform lookup / validation using their own local service (that may hold data on a subset of the national service - e.g. only citizens living in a specific region, for applications that are only used within a specific region).

  • Services for national ID may change over time - e.g. when new (national or regional) infrastructure is introduced. As a consequence, several tecnical interfaces (delivering the same data) may exist simultaneously.

There are different ways to implement integration with a "National ID" service:

  • Developing a .NET assembly, that implements a specific interface IPatient defined by the TOPICA framework. The resulting assembly (= a .DLL-file) must be placed in the framework's bin-folder, and must be enabled in the configuration's Server.config-file.

    This method is (about to be) deprecated.

  • Developing a custom WebForm (= an .aspx). This WebForm must be placed in a special folder within the configuration folder.

    This method is the recommended way to implement integration with a national ID service. It is documented in the following section.

Using Custom WebForm to implement National ID service integration in TOPICA

This way of implementing national ID integration has evolved in this way:

  • In TOPICA relases prior to 4.21, this method is NOT implemented.

  • In TOPICA relases 4.21 and 4.22, the WebForm (.aspx) must be placed in a folder named CprIntegration, and the .aspx may have any name (the framework executes the FIRST file found inside the folder CprIntegration.

  • In TOPICA relases 4.23 and newer, the WebForm (.aspx) must be placed in a folder named NationalId, and the .aspx must have the specific name Lookup.aspx.

The WebForm must have this functionality:

  • Get the national ID from the query string

  • Look up patient (person) data using the national ID service, using the passed national ID as key.

  • If the service cannot find data for the national ID, the custom WebForm should do nothing.

  • If the service did find data for the national ID, the custom WebForm should:

    • If a patient with the requested national ID did NOT exist in TOPICA already, it should be created.

    • If a patient with the requested national ID DID exist in TOPICA already, it must be checked, if any data from the national ID serviced differs from the data on the TOPICA patient.

      • If any changes, the TOPICA patient must be updated.

      • If no changes, the TOPICA patient must NOT be updated. Otherwise superflous rows will be generated in the history table (because any UPDATE on a record in the Patient table will automatically insert a row in the Patient_History table - even if no fields have changed values).

When the TOPICA framework detects the existence of the custom WebForm, the following happens:

  • In the "patient property" form, a button (labeled something like "Lookup National ID") is displayed next to the field, where the user enters the national ID.

  • When the user clicks the lookup button, the framework executes the custom WebForm.

  • When execution of the custom WebForm has completed, the framework checks whether a patient with the requested national ID exists. If this is the case, the framework knows, that the patient data has been synchonized with the national ID service. As a consequence, the values of most input fields in the patient property form are automatically filled in, and these fields become read only.

Sample Code: WebForm implementing National ID integration

The WebForm described here is a simple "mock up":

  • It does not call any external national ID service. Instead, it uses an XML file to simulate a database of valid national IDs. I.e. it searches the XML file for the supplied national ID - and if found, it creates or updates the corresponding patient. A "real world" national ID integration should call some service, that returns person data for the supplied national ID.

  • It only updates first name, last name and death date. A "real world" national ID integration will most likely also update address fields etc.

  • This WebForm is part of the demo configuration DemoNationalId. DemoNationalId is a very simple configuration: no configured forms, no reports - nothing but the two files listed below - demonstrating how to do national ID lookup.

File: NationalID/Lookup.aspx

C#
<%@ Page Language="C#" Inherits="CSC.SC.TOPICA4.TOPICA4Web.TopicaBasePage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="log4net" %>

<%@ Import Namespace="CSC.SC.Enterprise.DataAccess" %>
<%@ Import Namespace="CSC.SC.Enterprise.Utilities" %>
<%@ Import Namespace="CSC.SC.TOPICA4.Controllers" %>
<%@ Import Namespace="CSC.SC.TOPICA4.Library" %>

<script runat="server">

    //--------------------------------------------------------------------------

    private struct LookupData
    {
        public string NationalId;
        public string FirstName;
        public string LastName;
        public DateTime? DeathDate;
    }

    private bool CheckChanges(Patient patient, LookupData lookupData)
    {
        if (patient.FirstName != lookupData.FirstName)
            return true;
        if (patient.LastName != lookupData.LastName)
            return true;
        if (patient.DeathDate != lookupData.DeathDate)
            return true;
        return false;
    }

    private void SetPatientProperties(Patient patient, LookupData lookupData)
    {
        patient.FirstName = lookupData.FirstName;
        patient.LastName = lookupData.LastName;
        patient.DeathDate = lookupData.DeathDate;
        patient.SynchronizedWith = "Lookup.aspx";
    }

    private Patient CreateOrUpdatePatient(LookupData lookupData)
    {
        var patient = Patient.DBGet(base.Database, lookupData.NationalId);
        if (patient == null)
        {
            patient = new Patient();
            patient.NationalId = lookupData.NationalId;    // NB: releases before 4.23: use patient.CprNr instead!
            this.SetPatientProperties(patient, lookupData);
            base.PatientController.Create(patient);
            Debug.WriteLine("patient created : " + patient.ToString());
        }
        else if (this.CheckChanges(patient, lookupData))
        {
            this.SetPatientProperties(patient, lookupData);
            base.PatientController.Update(patient);
            Debug.WriteLine("patient updated : " + patient.ToString());
        }
        else
            Debug.WriteLine("no changes, patient NOT updated : " + patient.ToString());
        return patient;
    }

    //--------------------------------------------------------------------------

    protected void Page_Load(object sender, EventArgs e)
    {
        var nationalIdInternal = Request.QueryString["nationalId"];
        if (String.IsNullOrEmpty(nationalIdInternal))
            nationalIdInternal = Request.QueryString["cpr"];    // backward compatibility
        Debug.WriteLine("nationalIdInternal: " + nationalIdInternal);
        var nationalIdDisplay = Person.Nationalid_Display(base.Database, nationalIdInternal);
        Debug.WriteLine("nationalIdDisplay: " + nationalIdDisplay);
        // 
        Patient patient = null;
        var dataUrl = base.Request.CurrentExecutionFilePath + ".xml";
        var dataFilename = base.Server.MapPath(dataUrl);
        Debug.WriteLine("dataFilename: " + dataFilename);
        XmlDocument doc = new XmlDocument();
        using (StreamReader streamReader = new StreamReader(dataFilename, System.Text.Encoding.Default))
        {
            doc.Load(streamReader);
            // 
            foreach (XmlNode node in doc.DocumentElement.ChildNodes)
            {
                string nationaIdCompare = (string)(node.Attributes["NationalId"].Value);
                if (StringLibrary.MatchIgnoreCase(nationaIdCompare, nationalIdInternal) 
                    || StringLibrary.MatchIgnoreCase(nationaIdCompare, nationalIdDisplay))
                {
                    var lookupData = new LookupData()
                    {
                        NationalId = nationalIdInternal,
                        FirstName = (string)node.Attributes["FirstName"].Value,
                        LastName = (string)node.Attributes["LastName"].Value,
                        DeathDate = XMLUtil.GetAttributeDateTime2(node, "DeathDate")
                    };
                    patient = this.CreateOrUpdatePatient(lookupData);
                    break;
                }
            }
        }
        if (patient == null)
            Debug.WriteLine("NationalId: " + nationalIdInternal + " not found");
    }

    //--------------------------------------------------------------------------

</script>
Sample Data (used by above WebForm)

File: NationalID/Lookup.aspx.xml

<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
<Citizens>
  <Citizen NationalId="010203-1111" FirstName="FirstName-010203-1111" LastName="LastName-010203-1111" />
  <Citizen NationalId="020304-1111" FirstName="FirstName-020304-2222" LastName="LastName-020304-2222" />
  <Citizen NationalId="030405-1111" FirstName="FirstName-030405-3333" LastName="LastName-030405-3333" />
  <Citizen NationalId="010101-1111" FirstName="FirstName1111" LastName="LastName1111" DeathDate="2001-01-01" />
  <Citizen NationalId="010101-2222" FirstName="FirstName2222" LastName="LastName2222" />
</Citizens>
Notes on sample code and data

Birthdate:

  • In this example, birthdate is not part of the data returned from the mockup "service". This is consistent with national ID systems, where the birthdate may be calculated from the national ID (e.g. danish "CPR-nummer" and swedish "Personnummer"). In this case the birthdate is a computed field in the database.

  • In other national ID systems, birthdate cannot be computed from National ID. In these cases, the national ID service should return the birthdate - and the values should be set on the created/updated patient object.

Calling external webservices:

  • As stated above, a real world national ID integration would most likely need to call a webservice.

  • There are several ways to call a webservice from .NET code. By far the easiest is to use "Add Web Reference", which will generate "proxy" code. However, this "proxy" code cannot easily be "embedded" in an .aspx-file.

  • Therefore, the recommended way is to develop an assembly (a .DLL-file), that contains the web reference (including the auto generated "proxy" code), and then call this DLL from the custom WebForm.

  • For more detailed desrciption on this technique, see Web services example.

Batch updates:

  • The above example shows how to do a single national ID lookup "on demand" (when user clicks on a button). But patients may change address (move), change name - or even die - after they have been created/checked in the TOPICA application.

    You would therefore probably want to implement some kind of automatic batch updating. For example: you would want to run a periodic task, updating all patients with new death dates, adresses etc. How this techically is done, depends heavily on the national ID service being used.

    • Some national ID services may define a "subscription" mechanism (our "client" application gets notified, when there is a change on a particular national ID).

    • Some national ID services may have a mechanism that makes it possible to get a list of changes in some time interval. Then the updating process will have to get updates from only a small fraction of the patient population.

    • Some national ID services may have neither of above mechanism. In this case it will be necessary to develop a loop over all patients in the TOPICA application, calling the national service once per patient.

    In any case, it would probably be useful to use same assembly (DLL) both for the "on demand" and the "batch update" case.

    A typical way to implement such a periodic update process, is to develop a console application (*.exe file), that is run using a "Scheduled task".