Shepardizing NKP with REGEX and Backlinks

When reading a legal decision on Nepal Kanoon Patrika (NKP) – ever wondered are there other similar cases you should know about: that are back linked or inbound linked to the decision that you are reading? Trying to solve that here.

After some research, I found that Nepal lacks an established method of Shephardizing legal decisions, which helps track how cases are referenced in subsequent rulings. While my tool doesn’t fully replicate the functionality of Shephard’s Citation, it still brings networked links to the decisions published in the NKP’s website and adds some substance to the bones of the idea of shephardized citations.

What is it?

It’s a simple chrome browser extension that automatically shows you other related cases right at the top of any NKP decision you’re reading. So there is no more manual searching or clicking around – the connections just appear ! How does it work? When you’re reading any case on the NKP website, the extension pulls up a list of related decisions that might be relevant to your research using the backlinks from other other decisions and inbound links in the decision that you are reading that directs to other decisions. 

So no more manual searching for related cases especially when it is necessary to find connections you might have missed and getting a fuller picture of how different decisions relate to each other and stay in the flow like a pro.

Installation is super easy if you want to try it out.

Download: Just grab the extension files [here is the download link] and extract it
Install:
1. Open Chrome and go to the Extensions page (type chrome://extensions in your address bar)
2. Turn on “Developer mode” (top right corner)
3. Click “Load unpacked” and select the extension folder you downloaded and extracted
That’s it ! 🎉

Before

After

Deep dive for data geeks: How was it done?

Creating Database

Here is the shared google colab link below if anybody wants to try to recreate this data on their own. You can also download and see the database file as well using this link:  Link to database file.

This python code in the google colab shared below is essentially creating a digital library of legal decisions from Supreme Court’s Nepal Kanoon Patrika (NKP). It starts by visiting the NKP’s website and collecting information about each legal decision, including the title, details, and most importantly, the decision numbers. Think of it like a librarian going through thousands of legal documents, making notes about each one, and storing all this information in a well-organized digital filing cabinet (in this case, a database). The important part comes next – the code creates connections between related legal decisions, similar to how legal researchers “Shepardize” cases in other countries. When one legal decision refers to another decision, the code makes note of this connection. This connection could be both dependent or precedent reference using regular expression (REGEX) syntaxes. It’s like drawing threads between related cases and this link can be both backlinks or inbound links. 

All of this work is done efficiently using a multithreading process to process multiple requests and data quickly. The end result is a comprehensive database where you can look up any legal decision and instantly see all other decisions that are related to it – whether they directly reference each other or are connected through other cases. This makes legal research much easier, as you can quickly trace how legal precedents are connected and used across different cases. 
Link to goole colab file here

Creating API

This process includes creating a web-based search tool for Nepal’s Supreme Court decisions which we will use as an API for the next step. When someone enters a decision number (like a case reference number), the code searches through the linked database and returns all related court decisions as clickable links. Think of it like a legal citation network – if you’re looking at Case A, the tool will show you links to all other cases that are connected to Case A, whether they reference it or are referenced by it. Each link, when clicked, takes you directly to the full text of that court decision on the official website. If the system can’t find a particular decision or there’s an error, it lets you know instead of showing broken links. 
Use Case: https://sushilparajuli.com/nkp_database/nkpquery.php?number=९३९९

            <?php
// Set content type to HTML with UTF-8 encoding
header('Content-Type: text/html; charset=UTF-8');

// Get the number from the request query parameter 'number'
$number = isset($_GET['number']) ? $_GET['number'] : null;

// Check if a number was provided
if (!$number) {
    echo 'No number provided';
    exit();
}

try {
    // Create (or open) the SQLite database with UTF-8 encoding
    $db = new PDO('sqlite:./NKP_Data.db');

    // Ensure PDO uses UTF-8
    $db->exec("PRAGMA encoding = 'UTF-8';");

    // Set error mode to exception for better error handling
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Prepare the query to get shephardlink based on the input number
    $stmt = $db->prepare('SELECT shephardlink FROM nkp_data WHERE dn = :number');
    $stmt->bindParam(':number', $number);

    // Execute the query
    $stmt->execute();

    // Fetch all shephardlinks
    $shephardlinks = $stmt->fetchAll(PDO::FETCH_COLUMN);

    // Check if any shephardlink was found
    if ($shephardlinks) {
        // Initialize an array to hold the formatted results
        $formatted_links = [];

        // Iterate through each shephardlink
        foreach ($shephardlinks as $shephardlink) {
            // Split shephardlink into individual decision numbers
            $decision_numbers = explode(',', $shephardlink);

            // Iterate through each decision number to get URLs
            foreach ($decision_numbers as $decision_number) {
                // Trim whitespace from the decision number
                $decision_number = trim($decision_number);

                // Prepare the query to get the URL for the current decision number
                $urlStmt = $db->prepare('SELECT URL FROM nkp_data WHERE dn = :decision_number');
                $urlStmt->bindParam(':decision_number', $decision_number);
                $urlStmt->execute();

                // Fetch the URL for the current decision number
                $url = $urlStmt->fetchColumn();

                // Add the formatted link to the results
                if ($url) {
                    $formatted_links[] = '<a href="' . htmlspecialchars($url) . '">' . htmlspecialchars($decision_number) . '</a>';
                } else {
                    // If no URL is found, include the decision number with a no URL message
                    $formatted_links[] = htmlspecialchars($decision_number) . ' (No URL found)';
                }
            }
        }

        // Print the results as a string of hyperlinks
        echo implode(', ', $formatted_links);
    } else {
        // If no data is found, return a message
        echo 'No data found';
    }

} catch (PDOException $e) {
    // Handle database connection or query errors
    echo 'Database error: ' . $e->getMessage();
}
?>

        

Creating Chrome Extension

This is part of creating a Chrome extension that enhances legal case documents by adding related information. When it finds a legal decision number (written in Nepali numerals) on a webpage, it creates a temporary loading message, then makes a request to fetch additional data about that case from a database.

Once the data is received, the code creates a nicely formatted box on the webpage that displays links to related cases and citations. This box includes a header, the main content with styled links that change appearance when hovered over, and a footer with author credits. If anything goes wrong during this process (like failing to find the case number or unable to fetch the data), it shows an appropriate error message to the user.

content.js

            // content.js
async function fetchAdditionalInfo() {
    try {
      const waitForElement = setInterval(() => {
        const titleElement = document.evaluate(
          '/html/body/div[2]/div/div/article/div[1]/div[1]/h1/a',
          document,
          null,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null
        ).singleNodeValue;
  
        if (titleElement) {
          clearInterval(waitForElement);
          processElement(titleElement);
        }
      }, 100);
  
      setTimeout(() => clearInterval(waitForElement), 5000);
    } catch (error) {
      console.error('Error in fetchAdditionalInfo:', error);
    }
  }
  
  async function processElement(titleElement) {
    try {
      const text = titleElement.innerText;
      console.log('Found text:', text);
      
      const match = text.match(/निर्णय नं\.\s*([०१२३४५६७८९]+)/);
      
      if (!match) {
        console.error('Case number not found in text:', text);
        return;
      }
  
      const caseNumber = match[1];
      console.log('Found case number:', caseNumber);
  
      const loadingDiv = document.createElement('div');
      loadingDiv.textContent = 'Loading additional information...';
      loadingDiv.style.padding = '10px';
      loadingDiv.style.margin = '10px';
      loadingDiv.style.backgroundColor = '#fff3cd';
      loadingDiv.style.border = '1px solid #ffeeba';
      loadingDiv.style.borderRadius = '4px';
  
      const articleElement = document.evaluate(
        '/html/body/div[2]/div/div/article',
        document,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      ).singleNodeValue;
  
      if (!articleElement) {
        console.error('Article element not found');
        return;
      }
  
      articleElement.insertBefore(loadingDiv, articleElement.firstChild);
  
      try {
        const response = await new Promise((resolve) => {
          chrome.runtime.sendMessage(
            {
              action: "fetchData",
              url: `https://sushilparajuli.com/nkp_database/nkpquery.php?number=${caseNumber}`
            },
            resolve
          );
        });
  
        if (!response.success) {
          throw new Error(response.error || 'Failed to fetch data');
        }
  
        // Create a new div with custom formatting
        const infoDiv = document.createElement('div');
        infoDiv.style.padding = '20px';
        infoDiv.style.margin = '20px';
        infoDiv.style.border = '1px solid #ccc';
        infoDiv.style.borderRadius = '5px';
        infoDiv.style.backgroundColor = '#f9f9f9';
        infoDiv.style.position = 'relative'; // For footer positioning
        infoDiv.style.minHeight = '100px'; // Ensure space for footer
  
        // Add header
        const headerDiv = document.createElement('div');
        headerDiv.style.fontWeight = 'bold';
        headerDiv.style.marginBottom = '10px';
        headerDiv.textContent = 'See other related inbound and backlinked decisions:';
        infoDiv.appendChild(headerDiv);
  
        // Add main content - using innerHTML instead of textContent
        const contentDiv = document.createElement('div');
        contentDiv.style.marginBottom = '30px'; // Increased margin to make space for footer
        contentDiv.innerHTML = response.data; // Changed from textContent to innerHTML
        
        // Style the links
        contentDiv.querySelectorAll('a').forEach(link => {
          link.style.color = '#0066cc';
          link.style.textDecoration = 'none';
          link.style.marginRight = '5px';
          // Add hover effect
          link.addEventListener('mouseover', () => {
            link.style.textDecoration = 'underline';
          });
          link.addEventListener('mouseout', () => {
            link.style.textDecoration = 'none';
          });
        });
        
        infoDiv.appendChild(contentDiv);
  
        // Add footer
        const footerDiv = document.createElement('div');
        footerDiv.style.position = 'absolute';
        footerDiv.style.bottom = '10px';
        footerDiv.style.right = '20px';
        footerDiv.style.fontSize = '12px';
        footerDiv.innerHTML = 'Courtesy of <a href="https://www.linkedin.com/in/sushil-p/" target="_blank" style="color: #0077b5; text-decoration: none;">Sushil Parajuli</a>';
        infoDiv.appendChild(footerDiv);
  
        // Replace loading indicator with formatted content
        articleElement.replaceChild(infoDiv, loadingDiv);
  
      } catch (fetchError) {
        console.error('Error fetching data:', fetchError);
        loadingDiv.style.backgroundColor = '#f8d7da';
        loadingDiv.style.border = '1px solid #f5c6cb';
        loadingDiv.textContent = 'Error loading additional information. Please try again later.';
      }
  
    } catch (error) {
      console.error('Error in processElement:', error);
      const errorDiv = document.createElement('div');
      errorDiv.textContent = 'An error occurred while processing the page. Please try again later.';
      errorDiv.style.padding = '10px';
      errorDiv.style.margin = '10px';
      errorDiv.style.backgroundColor = '#f8d7da';
      errorDiv.style.border = '1px solid #f5c6cb';
      errorDiv.style.borderRadius = '4px';
  
      const articleElement = document.evaluate(
        '/html/body/div[2]/div/div/article',
        document,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      ).singleNodeValue;
  
      if (articleElement) {
        articleElement.insertBefore(errorDiv, articleElement.firstChild);
      }
    }
  }
  
  // Run the function when the page loads
  document.addEventListener('DOMContentLoaded', fetchAdditionalInfo);
  fetchAdditionalInfo();
        

background.js

            
// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === "fetchData") {
    fetch(request.url)
      .then(response => response.text())
      .then(data => sendResponse({success: true, data: data}))
      .catch(error => sendResponse({success: false, error: error.message}));
    return true; // Will respond asynchronously
  }
});
        

manifest.json

            {
  "manifest_version": 3,
  "name": "NKP Case Information",
  "version": "1.0",
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "https://nkp.gov.np/*",
    "https://sushilparajuli.com/*"
  ],
  "content_scripts": [
    {
      "matches": ["https://nkp.gov.np/full_detail/*"],
      "js": ["content.js"]
    }
  ],
  "background": {
    "service_worker": "background.js"
  }
}