docketscape is an R package for case-level judicial
records (such as the Development Data Lab extract of Indian lower-court
records). It decomposes court delay into two distinct dimensions:
The package constructs a weighted similarity graph of cases based on shared and diffused legal provisions, estimates censoring-aware expected resolution timelines using legally similar neighbours, calculates relative case lateness, and models resolution hazard relative to filing-time court docket load.
docketscape is strictly an observational,
representation-based modeling framework. To prevent misuse, the package
enforces the following boundaries:
Empirical analysis of court delay faces two persistent methodological challenges:
docketscape treats
pending matters as right-censored survival observations with event
indicator \(0\) and duration equal to
follow-up from filing to the observation cutoff.docketscape
captures substantive affinities between provisions that co-occur across
cases.Install the package directly from GitHub using the repository URL:
# Install from GitHub
# install.packages("remotes")
remotes::install_github("shraddhaajit/docketscape")Or install locally for development:
# Install from local source directory
devtools::install(".")The package pipeline follows a modular sequence:
clean_cases()
↓
build_section_graph()
↓
diffuse_profiles()
↓
build_case_graph()
↓
neighbourhood_survival()
↓
relative_lateness()
↓
court_load()
↓
fit_delay_model()
↓
decompose_delay()
↓
validate_models()
Auditing functions inspect data integrity and graph validity:
check_future_neighbours() # Detects edges from past cases to future cases
check_court_overlap() # Quantifies shared vs unseen courts across splits
check_near_duplicates() # Identifies same-court/date clusters and duplicates
To evaluate out-of-sample performance without future information
leakage, docketscape implements a strictly separated
temporal projection workflow:
TRAINING DATA (Earliest Filing Dates)
↓
build_section_graph(train_sections)
↓
diffuse_profiles(train_sections, train_section_graph)
↓
Compute Training IDF weights
↓
build_case_graph(train_diffused)
↓
fit_delay_model(train_cases, train_case_graph, train_court_load)
VALIDATION CASES (Later Filing Dates)
↓
project_validation_legal_score(val_sections, train_sections)
- Projected into training section vocabulary and transition space
- Connects ONLY to training cases as neighbours
- Validation cases NEVER form neighbours with each other
- Training graph, vocabulary, and IDF are NEVER modified
↓
Evaluate out-of-sample Harrell's C-index
The following self-contained example runs the entire workflow from case cleaning to model validation:
library(docketscape)
# 1. Simulate synthetic court cases
set.seed(123)
n_cases <- 80
cases <- data.frame(
case_id = 1:n_cases,
court = sample(c("Court_A", "Court_B", "Court_C"), n_cases, replace = TRUE),
filing_date = sample(seq.Date(as.Date("2012-01-01"), as.Date("2013-06-01"), by = "month"), n_cases, replace = TRUE),
duration_days = round(runif(n_cases, 40, 600)),
is_disposed = rbinom(n_cases, 1, 0.75),
stringsAsFactors = FALSE
)
# Populate decision date for disposed cases; pending cases have NA
cases$decision_date <- as.Date(NA)
cases$decision_date[cases$is_disposed == 1] <-
cases$filing_date[cases$is_disposed == 1] + cases$duration_days[cases$is_disposed == 1]
# 2. Clean cases and establish survival status relative to an observation cutoff
cleaned <- clean_cases(cases, observation_end = "2015-01-01")
# 3. Simulate legal sections invoked per case
set.seed(42)
acts_pool <- c("NI Act", "IPC", "Customs", "CrPC", "Companies Act")
sections_pool <- list(
"NI Act" = c("138", "141", "142"),
"IPC" = c("302", "307", "420", "406"),
"Customs" = c("110", "112", "135"),
"CrPC" = c("154", "482", "167"),
"Companies Act" = c("241", "242", "447")
)
sec_list <- lapply(1:n_cases, function(i) {
n_sec <- sample(2:4, 1)
acts <- sample(acts_pool, n_sec, replace = TRUE)
secs <- sapply(acts, function(a) sample(sections_pool[[a]], 1))
data.frame(case_id = i, act = unname(acts), section = unname(secs), stringsAsFactors = FALSE)
})
sections_df <- unique(do.call(rbind, sec_list))
# 4. Construct section co-occurrence graph and diffuse legal profiles
sec_graph <- build_section_graph(sections_df)
diffused <- diffuse_profiles(sections_df, sec_graph, alpha = 0.7)
# 5. Build k-nearest-neighbour case similarity graph
case_graph <- build_case_graph(diffused, k = 5, use_idf = TRUE)
# 6. Estimate neighbourhood survival and relative lateness for a focal case
km_focal <- neighbourhood_survival(case_graph, cleaned, case_id = 1)
lateness_focal <- relative_lateness(km_focal)
print(lateness_focal)
# 7. Compute monthly court docket load
load_df <- court_load(cleaned, by = "month")
# 8. Fit delay Cox proportional hazards model
fit <- fit_delay_model(cleaned, case_graph, load_df)
print(fit)
# 9. Decompose delay linear predictor into legal and load components
decomp <- decompose_delay(fit)
print(head(decomp$case_decomposition, 3))
# 10. Perform leakage-aware temporal holdout validation
val_results <- validate_models(fit, sections = sections_df, temporal_split = 0.7)
print(val_results)To illustrate core data mechanics, consider a compact 6-case scenario:
cases_six <- data.frame(
case_id = 1:6,
court = c("C1", "C1", "C1", "C2", "C2", "C2"),
case_type = c("Cheque", "Cheque", "Criminal", "Cheque", "Criminal", "Criminal"),
filing_date = as.Date(c(
"2012-01-10", "2012-03-05", "2012-06-20",
"2013-02-11", "2013-05-30", "2014-01-15"
)),
decision_date = as.Date(c(
"2012-11-02", "2013-02-14", NA,
"2013-12-01", NA, NA
))
)
cleaned_six <- clean_cases(cases_six, observation_end = "2014-12-31")
print(cleaned_six[, c("case_id", "court", "duration", "event", "is_valid")])Output:
case_id court duration event is_valid
1 1 C1 297 1 TRUE
2 2 C1 346 1 TRUE
3 3 C1 924 0 TRUE
4 4 C2 293 1 TRUE
5 5 C2 580 0 TRUE
6 6 C2 350 0 TRUE
event = 0 and durations measured from filing to the
observation cutoff (2014-12-31). They are retained in the
analysis rather than discarded.docketscape sets
fit_success = FALSE and records the exact numerical
diagnostics in fit_message. Downstream functions
(decompose_delay(), validate_models()) return
explicit unavailability flags (available = FALSE) rather
than fabricating non-converged statistics.clean_cases)decision_date, yielding event = 1 and
duration = decision_date - filing_date. Pending cases have
missing decision_date, yielding event = 0
(right-censored) and
duration = observation_end - filing_date.is_valid = FALSE) with explicit reasons if:
decision_before_filing).observation_end
(filed_after_observation_end).non_positive_duration).build_section_graph)Act::Section (e.g., IPC::420).diffuse_profiles)Raw citation vectors are sparse. A case citing Section 420 (cheating) shares substantive affinity with Section 406 (criminal breach of trust) if those sections frequently co-occur across the court’s docket.
The package computes a one-step weighted profile diffusion:
\[X_{\text{raw}} = \alpha X_{\text{init}} + (1 - \alpha) X_{\text{init}} P\]
where \(P = D^{-1} A\) is the row-stochastic section transition matrix. - \(\alpha \in [0, 1]\) governs retention of directly cited sections versus network smoothing. - This is an exact one-step convex combination smoothing operation rather than multi-step power iteration or full stationary PageRank. - Rows of \(X_{\text{raw}}\) are subsequently \(L_1\)-normalized so each case’s legal profile sums to 1.
build_case_graph)case_id.neighbourhood_survival)For a target case \(i\), an expected
resolution timeline is estimated from its valid neighbours in the case
graph: - A weighted Kaplan-Meier estimator is fitted over the
neighbours, where case weights equal the graph similarity edge weights
\(w_{ij}\). - Censoring
Handling: Censored neighbours contribute fully to the risk set
\(Y(t)\) up to their censoring
durations, correctly preventing underestimation of resolution times. -
If all neighbours are right-censored, the survival curve does not drop
to zero, and the median survival time remains undefined
(NA).
relative_lateness)Relative lateness evaluates where a target case falls within its peer survival curve:
\[\mathrm{relative\_position} = 1 - S(T)\]
where \(T\) is the target case
duration, and \(S(T)\) is the weighted
Kaplan-Meier neighbourhood survival function evaluated at \(T\). - Estimability: If
target duration \(T\) exceeds the
maximum follow-up observed among its neighbours, the package sets
estimable = FALSE and returns NA.
docketscape explicitly refuses to extrapolate beyond
empirical neighbour follow-up.
court_load)format(filing_date, "%Y-%m-01")).load_at_filing is the
count of active (open) cases in that court as of the filing month. It
reflects baseline institutional queue pressure at entry, not cumulative
lifetime congestion.fit_delay_model)Case resolution hazard is modeled using semi-parametric Cox proportional hazards regression:
\[\lambda(t \mid X) = \lambda_0(t) \exp\left(\beta_{\mathrm{legal}} \cdot \mathrm{score}_i + \beta_{\mathrm{load}} \cdot \mathrm{load}_i\right)\]
score: Mean similarity weight of incident edges in the
case similarity graph (legal_score).load: Court docket load at the case’s filing month
(load_at_filing).decompose_delay)The fitted linear predictor \(\eta_i\) is partitioned into its linear additive components:
\[\eta_i = \beta_{\mathrm{legal}} \cdot \mathrm{score}_i + \beta_{\mathrm{load}} \cdot \mathrm{load}_i\]
Component shares are computed using absolute magnitudes:
\[\mathrm{absolute\_legal\_share}_i = \frac{|\beta_{\mathrm{legal}} \cdot \mathrm{score}_i|}{|\beta_{\mathrm{legal}} \cdot \mathrm{score}_i| + |\beta_{\mathrm{load}} \cdot \mathrm{load}_i|}\]
\[\mathrm{absolute\_load\_share}_i = \frac{|\beta_{\mathrm{load}} \cdot \mathrm{load}_i|}{|\beta_{\mathrm{legal}} \cdot \mathrm{score}_i| + |\beta_{\mathrm{load}} \cdot \mathrm{load}_i|}\]
Statistical Safeguard: These shares partition the fitted linear predictor of the model. They are not causal attributions of delay.
Constructing a similarity graph over a pooled cross-section of cases introduces temporal leakage: earlier-filed cases can acquire neighbours from cases filed years into the future.
build_case_graph() across all cases allows future cases to
influence training representations. In audits of full-sample graphs,
substantial proportions of historical cases acquire future
neighbours.(filing_date, case_id).project_validation_legal_score().check_future_neighbours(): Flags graph edges connecting
past cases to future cases across a temporal cutoff.check_court_overlap(): Distinguishes whether validation
assesses generalisation across time within known courts or across unseen
courts.check_near_duplicates(): Flags same-court, same-filing,
same-decision clusters to inspect recording redundancy.Evaluated on the canonical 80-case synthetic validation dataset:
| Dimension | Cohort Specification | Value |
|---|---|---|
| Total Cohort | Total Cases (\(N\)) | 80 |
| Observed Events | 59 | |
| Right-Censored (Pending) | 21 | |
| Training Cohort (70%) | Training Cases (\(N_{\mathrm{train}}\)) | 56 |
| Observed Events | 41 | |
| Right-Censored | 15 | |
| Validation Cohort (30%) | Validation Cases (\(N_{\mathrm{val}}\)) | 24 |
| Observed Events | 18 | |
| Right-Censored | 6 | |
| Validation Discrimination | Held-Out Harrell’s C-index | 0.5038 |
The held-out concordance index of 0.5038 on this synthetic dataset is approximately chance-level discrimination (\(0.50\)). Because the synthetic outcomes were generated independently of the statutory profiles and docket load features, an out-of-sample C-index near 0.50 is the expected statistical baseline. This confirms correct execution of the leakage-free training-to-validation projection pipeline without numerical collapse or data fabrication; it does NOT demonstrate predictive success or empirical findings.
To validate pipeline robustness and representation behavior on
real-world judicial records, the pipeline was audited against an
external, unbundled 10,000-case sample of 2010 Indian lower-court
filings from the Development Data Lab (DDL) repository. All DDL analyses
were conducted using external scripts outside the package repository;
no empirical DDL records are bundled within
docketscape.
Methodological Status: All metrics below are strictly EXPLORATORY empirical validations on this specific subset. They do NOT support causal claims, do NOT generalize to all Indian lower courts, and do NOT rank or evaluate judicial officers or courts.
Audit of candidate observation cutoffs across the full 10,000-case sample:
| Cutoff Date | Description | Valid Cases | Invalid Cases | Events | Censored | Censoring Rate | Median Duration | Mean Duration |
|---|---|---|---|---|---|---|---|---|
| 2015-12-31 | 5-Year Window | 8,462 | 1,538 | 7,178 | 1,284 | 15.17% | 896 days | 975.7 days |
| 2018-12-31 | Stated Panel End | 9,863 | 137 | 8,579 | 1,284 | 13.02% | 1,107 days | 1,334.3 days |
| 2019-12-31 | Pre-Pandemic Cutoff | 9,933 | 67 | 8,649 | 1,284 | 12.93% | 1,120 days | 1,394.8 days |
| 2020-07-23 | Max Observed Date | 9,943 | 57 | 8,659 | 1,284 | 12.91% | 1,122 days | 1,423.4 days |
date_of_decision was clerical duplication of
date_first_list, while 9 were typographic year/month entry
errors. clean_cases() correctly flags all 57 cases as
invalid (is_valid = FALSE) and masks their durations to
NA_real_, preventing negative follow-ups from corrupting
downstream survival estimates.court_load()
without missing-value inflation or index misalignment.A representative subset of 557 cases across the top 10 most active courts was analyzed end-to-end: - Section Graph: 84 unique statutory section nodes, 126 undirected co-occurrence edges. Edge weights (integer case co-occurrences): min = 1, median = 1, mean = 3.24, max = 39. - Profile Diffusion Stability: Across all diffusion weights \(\alpha \in \{0.1, 0.3, 0.5, 0.7, 0.9, 1.0\}\), all 557 case profile row sums strictly satisfy \(L_1 = 1.000000\) (numerical drift \(< 10^{-15}\)). Non-zero diffused entries: 6,239 for \(\alpha < 1.0\) (sparsity = 0.8667) vs. 965 for un-diffused \(\alpha = 1.0\) (sparsity = 0.9794). - Case Similarity Graph (\(k=5\)): 557 case nodes, 2,204 undirected edges. Cosine similarity edge weights: min = 0.0241, 1st Qu = 0.8559, median = 1.0000, mean = 0.9095, max = 1.0000. Degree distribution: min = 0, median = 5, mean = 7.91, max = 45 (3 isolated cases with degree 0, 554 connected cases).
project_validation_legal_score): Projected 167
held-out validation cases against training representations:
Semi-parametric Cox proportional hazards models converged reliably across varying sample sizes and hyperparameter configurations:
Sample Size Sensitivity:
| Cohort Size (\(N\)) | Complete Obs | Observed Events | \(\beta_{\mathrm{legal}}\) | Legal \(p\)-value | \(\beta_{\mathrm{load}}\) | Load \(p\)-value | Harrell’s C-index | Optimization Status |
|---|---|---|---|---|---|---|---|---|
| 50 | 48 | 42 | 1.2600 | 0.2303 | 0.01466 | 0.0496 | 0.647 | Converged |
| 100 | 95 | 87 | 2.3277 | 0.0070 | 0.00783 | 0.1517 | 0.580 | Converged |
| 250 | 236 | 221 | 1.6144 | 0.0027 | 0.00452 | 0.2099 | 0.536 | Converged |
| 557 | 521 | 494 | 1.0498 | 0.0018 | 0.00535 | 0.0366 | 0.525 | Converged |
Hyperparameter Sensitivity Grid (\(\alpha \times k\) at \(N=557\)):
| Diffusion \(\alpha\) | Nearest Neighbours \(k\) | \(\beta_{\mathrm{legal}}\) | \(\beta_{\mathrm{load}}\) | Harrell’s C-index |
|---|---|---|---|---|
| 0.3 | 3 | 1.8591 | 0.00555 | 0.531 |
| 0.7 | 3 | 1.0069 | 0.00586 | 0.528 |
| 0.9 | 3 | 0.7507 | 0.00592 | 0.528 |
| 0.3 | 5 | 1.8649 | 0.00531 | 0.531 |
| 0.7 | 5 | 1.0498 | 0.00535 | 0.525 |
| 0.9 | 5 | 0.8302 | 0.00540 | 0.526 |
| 0.3 | 10 | 1.2763 | 0.00493 | 0.522 |
| 0.7 | 10 | 0.7201 | 0.00542 | 0.519 |
| 0.9 | 10 | 0.5990 | 0.00544 | 0.519 |
Across all configurations, baseline filing-month court load exhibits a positive association with resolution hazard (\(\beta_{\mathrm{load}} \approx 0.005\) to \(0.006\)), while legal score association scales inversely with network diffusion smoothing. Concordance indexes remain modestly above chance (0.519 to 0.531), consistent with observational non-causal representations.
cases Data Frame:
case_id (character or numeric, unique): Case
identifier.filing_date (Date or ISO character string): Date of
case institution.decision_date (Date or ISO character string, optional):
Date of final order/disposal (NA if pending).court (character, optional): Court identifier for
institutional grouping.sections Data Frame:
case_id (character or numeric): Foreign key matching
cases$case_id.act (character): Statutory enactment
(e.g. "IPC", "NI Act").section (character): Specific statutory provision
(e.g. "420", "138").court_load Data Frame:
court (character): Court identifier.date (Date): First day of calendar month
(YYYY-MM-01).load (numeric): Count of active cases open in that
court as of the month.is.na(decision_date)) receive event = 0 and
duration = observation_end - filing_date. Disposed cases
receive event = 1 and
duration = decision_date - filing_date.observation_end, decision dates
preceding filing dates, or non-positive durations are flagged as
is_valid = FALSE with an informative
invalid_reason. Their durations are masked to
NA_real_ to ensure invalid records never enter survival
likelihoods.NA legal scores only if entirely disjoint from training
vocabulary.Matrix::crossprod()) avoiding combinatorial pair
enumeration.chunk_size = 1000) and integer-ranked
tie-breaking (case_id_ranks), scaling to 25,000 cases in
under 1.9 GB RAM.findInterval() event tracking, eliminating dense \(O(T \times N)\) boolean allocation.docketscape is built entirely on open-source
dependencies with compatible licenses:
| Dependency | Component | License |
|---|---|---|
R (>= 4.1.0) |
Core runtime environment | GPL-2 | GPL-3 |
igraph |
Graph structures and algorithms | GPL-2 | GPL-3 |
Matrix |
Sparse matrix operations | GPL-2 | GPL-3 |
survival |
Semi-parametric survival models | LGPL-2 | LGPL-2.1 | LGPL-3 |
stats |
Statistical distributions and optimizers | GPL-2 | GPL-3 (Base R) |
knitr |
Vignette rendering engine | GPL-2 | GPL-3 |
rmarkdown |
Dynamic document generation | GPL-3 |
testthat |
Unit and regression test suite | MIT |
spelling |
Documentation orthography validation | MIT |
Researchers using docketscape must adhere to the
following 12 explicit methodological constraints:
docketscape was conceptualized for analyzing case-level
judicial data from the Development Data Lab (DDL)
Indian lower-court dataset (devdatalab.org).
docketscape
contains zero empirical DDL records. All datasets in
this package, including package tests, examples, and the canonical
80-case validation benchmark, are strictly generated synthetic data
structures.docketscape neither redistributes
DDL data nor grants or invents any licensing or redistribution
permissions on behalf of Development Data Lab.| Function | Primary Purpose |
|---|---|
clean_cases() |
Standardises dates, establishes right-censored durations, flags invalid records. |
build_section_graph() |
Constructs statutory section co-occurrence network from case citations. |
diffuse_profiles() |
Smooths case citation vectors over section network via one-step weighted profile diffusion. |
build_case_graph() |
Builds \(k\)-NN case similarity graph with IDF weighting and deterministic tie handling. |
neighbourhood_survival() |
Estimates weighted Kaplan-Meier expected timeline from legally similar neighbours. |
relative_lateness() |
Evaluates case duration percentile relative to neighbour survival experience. |
court_load() |
Computes monthly open court docket load. |
fit_delay_model() |
Fits Cox proportional hazards regression on legal score and filing-time court load. |
decompose_delay() |
Partitions linear predictor into legal and court load absolute contribution shares. |
validate_models() |
Evaluates out-of-sample temporal discrimination and leakage-aware projection. |
project_validation_legal_score() |
Projects validation cases into training legal representation without future leakage. |
check_future_neighbours() |
Audits graph edges connecting past cases to future cases. |
check_court_overlap() |
Audits court representation overlap across training and validation splits. |
check_near_duplicates() |
Audits same-date/court clusters and exact duplicate records. |
If you use docketscape in your research, please
cite:
@manual{docketscape2026,
title = {docketscape: Legal Network and Congestion-Aware Delay Modeling for Court Dockets},
author = {Shraddha Ajit and DK Janani},
year = {2026},
note = {R package version 0.1.0},
url = {https://github.com/shraddhaajit/docketscape}
}This project is licensed under the MIT License — see the LICENSE file for details.