| Title: | Legal Network and Congestion-Aware Delay Modeling for Court Dockets |
|---|---|
| Description: | Splits court delay into two parts: (1) what a case is, represented through the statutory Acts and Sections invoked by the case, and (2) where it sits, represented by time-varying court docket load. Builds a weighted similarity graph of cases from legal provisions using 'igraph' and 'Matrix', estimates censoring-aware expected resolution timelines using legally similar neighbours, calculates relative lateness, and models delay in relation to filing-time court docket load using 'survival'. Designed for case-level judicial records such as the Development Data Lab (DDL) extract of Indian lower courts (<https://www.devdatalab.org/judicial-data>). Observational and non-causal; does not rank judges, evaluate judicial performance, or claim causal delay effects. |
| Authors: | Shraddha Ajit [aut, cre], DK Janani [aut] |
| Maintainer: | Shraddha Ajit <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-11-14 00:29:40 UTC |
| Source: | https://github.com/cran/docketscape |
Builds an undirected igraph graph of cases where nodes represent cases,
edges connect legally similar cases based on their diffused section profiles, and
edge weights represent cosine legal similarity values in [0, 1] after optional
inverse-document-frequency (IDF) section rarity weighting.
build_case_graph(profiles, k = 3, use_idf = TRUE)build_case_graph(profiles, k = 3, use_idf = TRUE)
profiles |
An object of class |
k |
Positive integer specifying the maximum number of nearest neighbours each case retains. Defaults to |
use_idf |
Logical flag indicating whether to weight sections by inverse document frequency (rarity) prior to cosine similarity. Defaults to |
Graph Construction Pipeline:
IDF Rarity Weighting: For section present in cases out of total cases:
Sections appearing in fewer cases receive higher weights.
Cosine Similarity: For cases and with weighted profiles and :
k-NN Pruning & Deterministic Symmetrization: Each case selects up to neighbours with highest non-zero similarity (ties broken deterministically by case ID). An undirected edge is created if either case selects the other.
An undirected igraph graph object containing:
name / case_id matching the input case IDs.
weight numeric cosine legal similarity values in [0, 1].
syn_sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sec_g <- build_section_graph(syn_sec) diff_p <- diffuse_profiles(syn_sec, sec_g) case_g <- build_case_graph(diff_p, k = 2) print(case_g)syn_sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sec_g <- build_section_graph(syn_sec) diff_p <- diffuse_profiles(syn_sec, sec_g) case_g <- build_case_graph(diff_p, k = 2) print(case_g)
Builds a weighted, undirected igraph graph where vertices represent unique
legal section identifiers (combination of Act and Section) and edge weights
represent the count of distinct cases in which two legal sections co-occur.
build_section_graph( sections, case = "case_id", act = "act", section = "section" )build_section_graph( sections, case = "case_id", act = "act", section = "section" )
sections |
A |
case |
Character string specifying the column name for case IDs. Defaults to |
act |
Character string specifying the column name for Act names. Defaults to |
section |
Character string specifying the column name for Section numbers/names. Defaults to |
Graph Construction Details:
Identifiers: Legal sections are identified by combining Act and Section (e.g., "NI Act::138"). This ensures sections from different Acts with identical section numbers remain distinct vertices.
Deduplication: Duplicate occurrences of the same (case_id, act, section) triplet are removed prior to counting to prevent artificial inflation of edge weights.
Singletons: Sections that appear without co-occurring with any other section in the same case are retained as isolated vertices with degree 0.
Missing Data: Rows containing NA, empty strings, or blank values in case_id, act, or section are excluded from graph construction.
An undirected igraph graph object containing:
name / identifier (unique "Act::Section"), act, section.
weight (integer count of distinct cases with co-occurrence).
sections <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) g <- build_section_graph(sections) print(g)sections <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) g <- build_section_graph(sections) print(g)
Evaluates court representation overlap between training and validation case sets.
check_court_overlap(train_cases, validation_cases, court = "court")check_court_overlap(train_cases, validation_cases, court = "court")
train_cases |
A |
validation_cases |
A |
court |
Character string specifying the court column name if data frames are provided. Defaults to |
Court overlap describes whether validation tests generalisation across time within known courts or tests generalisation to entirely unseen courts. Shared courts are a validation design feature, not data leakage.
An S3 object of class c("court_overlap_audit", "list") containing:
Sorted vector of unique training courts.
Sorted vector of unique validation courts.
Sorted vector of shared courts.
Count of shared courts.
Vector of courts present only in training.
Vector of courts present only in validation.
Fraction of validation courts present in training.
Statistical interpretation of court overlap.
train_df <- data.frame(court = c("C1", "C2")) val_df <- data.frame(court = c("C1", "C3")) audit <- check_court_overlap(train_df, val_df) print(audit)train_df <- data.frame(court = c("C1", "C2")) val_df <- data.frame(court = c("C1", "C3")) audit <- check_court_overlap(train_df, val_df) print(audit)
Identifies graph edges where a case is connected in case_graph to a case filed later
than a specified temporal cutoff date (or later than its own filing date).
check_future_neighbours(case_graph, cases_clean, cutoff_date = NULL)check_future_neighbours(case_graph, cases_clean, cutoff_date = NULL)
case_graph |
An |
cases_clean |
A |
cutoff_date |
Optional |
Future-Neighbour Directionality:
For an undirected edge between Case A (filed 2012) and Case B (filed 2013), Case A has a future neighbour
(Case B), whereas Case B has a past neighbour (Case A).
If cutoff_date is provided, only edges where a pre-cutoff case is connected to a post-cutoff case are flagged.
Statistical Interpretation: Future-neighbour leakage indicates that a case's legal similarity profile was constructed using cases filed after its temporal cutoff. This is an audit of information leakage, not proof of an invalid model.
An S3 object of class c("future_neighbour_audit", "list") containing:
Logical indicating if the audit was evaluated successfully.
Date threshold used, or NULL if auditing full graph temporal order.
Total number of edges in case_graph.
Count of future-neighbour leakage edges.
Count of distinct cases with at least one future neighbour.
Character vector of affected case IDs.
A data.frame of individual leakage edges.
Latest filing date among identified future neighbours.
Statistical interpretation of audit findings.
cases <- data.frame( case_id = c("1", "2"), filing_date = as.Date(c("2012-01-10", "2013-05-15")) ) g <- igraph::make_graph(c("1", "2"), directed = FALSE) audit <- check_future_neighbours(g, cases, cutoff_date = as.Date("2012-12-31")) print(audit)cases <- data.frame( case_id = c("1", "2"), filing_date = as.Date(c("2012-01-10", "2013-05-15")) ) g <- igraph::make_graph(c("1", "2"), directed = FALSE) audit <- check_future_neighbours(g, cases, cutoff_date = as.Date("2012-12-31")) print(audit)
Identifies same-date/court case clusters and exact duplicate rows in cleaned case data based on court, filing date, and decision date.
check_near_duplicates(cases_clean, court = "court")check_near_duplicates(cases_clean, court = "court")
cases_clean |
A |
court |
Character string specifying the court column name in |
Audit checks for multiple case records sharing identical court, filing date, and decision date.
Cases with distinct case_ids sharing dates/courts form same-date/court clusters, which are explicitly distinguished
from duplicate case records. Fuzzy near-duplicate matching on unstructured text is not performed as case-level text is not present.
An S3 object of class c("near_duplicate_audit", "list") containing:
Logical indicating if audit evaluated successfully.
Total number of case rows audited.
Number of distinct same-date/court clusters.
Total number of case rows belonging to clusters.
A data frame detailing clusters and case IDs.
Statistical interpretation of audit findings.
cases <- data.frame( case_id = 1:3, court = c("C1", "C1", "C2"), filing_date = as.Date(c("2012-01-10", "2012-01-10", "2012-03-05")), decision_date = as.Date(c("2012-11-02", "2012-11-02", "2013-02-14")) ) audit <- check_near_duplicates(cases) print(audit)cases <- data.frame( case_id = 1:3, court = c("C1", "C1", "C2"), filing_date = as.Date(c("2012-01-10", "2012-01-10", "2012-03-05")), decision_date = as.Date(c("2012-11-02", "2012-11-02", "2013-02-14")) ) audit <- check_near_duplicates(cases) print(audit)
Takes raw case-level data containing filing dates and optional decision dates, validates dates, flags problematic records without deletion, and calculates right-censored survival duration and event indicators relative to an observation end date.
clean_cases( cases, filing = "filing_date", decision = "decision_date", observation_end )clean_cases( cases, filing = "filing_date", decision = "decision_date", observation_end )
cases |
A |
filing |
Character string specifying the name of the column containing filing dates, or a vector of filing dates matching |
decision |
Character string specifying the name of the column containing decision dates, or a vector of decision dates matching |
observation_end |
A single Date object or character string coercible to Date (e.g. |
Censoring Logic:
Disposed case: decision_date <= observation_end. duration = decision_date - filing_date, event = 1.
Pending case: is.na(decision_date). duration = observation_end - filing_date, event = 0.
Invalid case: Cases with missing filing dates, decision dates before filing dates, or filing dates after observation_end are flagged with is_valid = FALSE and an explicit invalid_reason. They are NOT deleted. Durations for invalid records are set to NA to ensure no negative durations exist in downstream statistical models.
A modified data.frame based on cases with additional or updated columns:
Standardised Date object for case filing.
Standardised Date object for case decision (NA if pending).
Numeric duration in days. For disposed cases, decision_date - filing_date. For pending cases, observation_end - filing_date. Set to NA_real_ for invalid records.
Numeric event status indicator. 1 for disposed, 0 for pending, NA_real_ for invalid records.
Logical flag indicating whether the record is valid (TRUE) or problematic (FALSE).
Character string describing the issue if is_valid == FALSE, otherwise NA_character_.
cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) clean_cases(cases, observation_end = "2014-12-31")cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) clean_cases(cases, observation_end = "2014-12-31")
Computes the number of open cases at each court across specified time points (monthly or daily).
A case contributes to court load at time if it was filed on or before and
has not yet been disposed by .
court_load(cases_clean, court = "court", by = "month")court_load(cases_clean, court = "court", by = "month")
cases_clean |
A |
court |
Character string specifying the column name for court identifiers. Defaults to |
by |
Character string specifying the time aggregation interval: |
Open-Case Boundary Convention:
A case is defined as open at court at time if:
Disposed Cases (): Contribute to load starting on their filing date up to, but excluding, their decision date. A case disposed on date is considered no longer open at time point .
Pending Cases (): Remain open at all time points on or after their filing date through the end of the observation window.
Time Points: For by = "month", time points are set to the 1st of each calendar month (e.g. 2020-01-01, 2020-02-01).
Statistical Interpretation:
court_load() measures docket exposure / open case volume over time. It does NOT measure judge efficiency, judicial quality, or causal congestion effects.
An S3 object of class c("court_load", "data.frame") containing:
Character column containing court identifiers.
Date object representing the evaluation time point (1st of each month for by = "month").
Integer count of open cases at that court and date.
cases <- data.frame( case_id = 1:3, court = c("C1", "C1", "C2"), filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") load_df <- court_load(cleaned, by = "month") head(load_df)cases <- data.frame( case_id = 1:3, court = c("C1", "C1", "C2"), filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") load_df <- court_load(cleaned, by = "month") head(load_df)
Decomposes the log-hazard linear predictor of a fitted delay_model into
legal-similarity and filing-time docket-load association components.
decompose_delay(fit)decompose_delay(fit)
fit |
A |
Linear Predictor Decomposition:
For each included case , the fitted Cox model log-hazard linear predictor is decomposed as:
Legal Component:
Load Component:
Total Linear Predictor:
Absolute Shares: Relative contribution shares are calculated on absolute component magnitudes to prevent denominator distortion or sign cancellation:
If both components are exactly zero, both absolute shares return NA.
Statistical Safeguard: The decomposition partitions the fitted Cox model's linear predictor into observational associations with legal similarity and filing-time docket load. It does NOT estimate causal contributions to delay, judge effects, or court performance/efficiency scores.
An S3 object of class c("delay_decomposition", "list") containing:
A data.frame of case-level predictor components and absolute shares.
A data.frame of aggregate summary metrics across all decomposed cases.
A named vector of the fitted Cox model coefficients ().
The underlying fitted coxph model object.
Explicit non-causal statistical safeguard statement.
Logical indicating whether decomposition was successfully computed.
Status message describing decomposition availability.
cases <- data.frame( case_id = paste0("C", 1:10), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 5)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100), event = c(1, 1, 1, 0, 1, 1, 0, 1, 1, 1), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:9), to = paste0("C", 2:10), weight = c(0.8, 0.7, 0.6, 0.5, 0.7, 0.8, 0.6, 0.5, 0.9), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) decomp <- decompose_delay(fit) print(decomp)cases <- data.frame( case_id = paste0("C", 1:10), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 5)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100), event = c(1, 1, 1, 0, 1, 1, 0, 1, 1, 1), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:9), to = paste0("C", 2:10), weight = c(0.8, 0.7, 0.6, 0.5, 0.7, 0.8, 0.6, 0.5, 0.9), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) decomp <- decompose_delay(fit) print(decomp)
Performs a one-step personalized diffusion of case-level legal section profiles across a weighted section co-occurrence graph. This allows legally related cases to share similarity even when they do not invoke identical sections.
diffuse_profiles( sections, section_graph, case = "case_id", act = "act", section = "section", alpha = 0.7 )diffuse_profiles( sections, section_graph, case = "case_id", act = "act", section = "section", alpha = 0.7 )
sections |
A |
section_graph |
An |
case |
Character string specifying the column name for case IDs. Defaults to |
act |
Character string specifying the column name for Act names. Defaults to |
section |
Character string specifying the column name for Section numbers/names. Defaults to |
alpha |
Numeric value in |
Mathematical Formulation:
Let be the weighted adjacency matrix of the section graph, and be the diagonal degree matrix.
The transition matrix is defined as (with rows of isolated nodes set to 0).
For a case with normalized initial section indicator vector (where ):
The final profile is normalized to unit norm:
If , the original section profile is returned without diffusion.
If , the profile is derived entirely from neighboring sections in the co-occurrence graph.
An S3 object of class "diffused_profiles" containing:
A sparse matrix of class dgCMatrix (from Matrix) of dimension N_cases x M_sections, with row sums equal to 1.
Character vector of unique case IDs corresponding to matrix rows.
Character vector of section identifiers ("Act::Section") corresponding to matrix columns.
The numeric alpha value used for diffusion.
syn_sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) g <- build_section_graph(syn_sec) diff_res <- diffuse_profiles(syn_sec, g, alpha = 0.7) print(diff_res)syn_sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) g <- build_section_graph(syn_sec) diff_res <- diffuse_profiles(syn_sec, g, alpha = 0.7) print(diff_res)
Fits a Cox proportional hazards model relating case resolution speed to the case's legal similarity profile score and its court's docket load at filing time.
fit_delay_model(cases_clean, case_graph, court_load_df, court = "court")fit_delay_model(cases_clean, case_graph, court_load_df, court = "court")
cases_clean |
A |
case_graph |
An |
court_load_df |
A |
court |
Character string specifying the column name for court identifiers in |
Legal Score Definition:
For case , legal_score is calculated as the mean weight of all incident edges in case_graph:
If a case has no legal graph neighbours, legal_score is assigned NA (not 0).
Filing-Time Court Load Alignment:
Each case's filing date is mapped to the first of its calendar month (format(filing_date, "%Y-%m-01")),
and matched against the court's docket load from court_load_df.
load_at_filing represents baseline docket exposure at filing, not time-varying cumulative load across case lifetime.
Statistical Interpretation:
Model estimates quantify observational associations. A hazard ratio for load_at_filing indicates that
higher baseline docket load is associated with a higher hazard of resolution (faster resolution), conditional on legal score.
Model coefficients are non-causal associations and must NOT be interpreted as causal delay impacts or judge performance measures.
An S3 object of class c("delay_model", "list") containing:
The fitted coxph model object (or NULL if fitting failed).
The subset data.frame used for model fitting.
A data frame of estimated coefficients, standard errors, z-scores, and p-values.
A data frame of hazard ratios () and 95% confidence intervals.
Total number of valid observations included in the model.
Number of resolution events (event == 1) in the model data.
Total number of cases excluded due to missing data or unmapped predictors.
Named integer vector showing breakdown of exclusion reasons.
Character string describing the legal score calculation method.
Character string describing filing-time court docket load alignment.
The formula used in fitting.
Output of cox.zph diagnostic test if feasible, otherwise NULL.
Logical indicating whether the Cox regression model fitted successfully.
Character description of model status or error message if fitting failed.
cases <- data.frame( case_id = paste0("C", 1:10), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 5)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100), event = c(1, 1, 1, 0, 1, 1, 0, 1, 1, 1), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:9), to = paste0("C", 2:10), weight = c(0.8, 0.7, 0.6, 0.5, 0.7, 0.8, 0.6, 0.5, 0.9), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) print(fit)cases <- data.frame( case_id = paste0("C", 1:10), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 5)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100), event = c(1, 1, 1, 0, 1, 1, 0, 1, 1, 1), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:9), to = paste0("C", 2:10), weight = c(0.8, 0.7, 0.6, 0.5, 0.7, 0.8, 0.6, 0.5, 0.9), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) print(fit)
Estimates a case-specific weighted Kaplan-Meier survival curve using the legally similar neighbouring cases of a target case in the case similarity graph.
neighbourhood_survival(case_graph, cases_clean, case_id)neighbourhood_survival(case_graph, cases_clean, case_id)
case_graph |
An |
cases_clean |
A |
case_id |
Character string or numeric value specifying the target case ID. |
Weighted Kaplan-Meier Estimator:
For a target case , let be its legally similar neighbours in case_graph
with similarity weights .
Target case is explicitly excluded from its own neighbourhood to prevent outcome leakage.
At each distinct disposal event time :
Right-censored neighbours () remain in the risk set until their censoring duration
but do not contribute to event counts .
An S3 object of class "neighbourhood_survival" containing:
Character string of the target case ID.
Character status ("pending", "disposed", or "invalid").
Observed age/duration of the target case.
Event indicator of the target case.
data.frame containing neighbour case IDs, weights, durations, and event indicators.
Integer count of valid neighbours used.
data.frame with columns time, n_at_risk, n_events, surv.
Estimated median resolution time (days), or NA_real_ if not reached.
cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sg <- build_section_graph(sec) dp <- diffuse_profiles(sec, sg) cg <- build_case_graph(dp, k = 2) km_res <- neighbourhood_survival(cg, cleaned, case_id = 1) print(km_res)cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sg <- build_section_graph(sec) dp <- diffuse_profiles(sec, sg) cg <- build_case_graph(dp, k = 2) km_res <- neighbourhood_survival(cg, cleaned, case_id = 1) print(km_res)
Print method for court_load
## S3 method for class 'court_load' print(x, ...)## S3 method for class 'court_load' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input court_load object of class c("court_load", "data.frame"). Called primarily for its side effect of printing a formatted docket load summary to the console.
Print method for court_overlap_audit
## S3 method for class 'court_overlap_audit' print(x, ...)## S3 method for class 'court_overlap_audit' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input court_overlap_audit object of class c("court_overlap_audit", "list"). Called primarily for its side effect of printing the court overlap audit summary to the console.
Print method for delay_decomposition
## S3 method for class 'delay_decomposition' print(x, ...)## S3 method for class 'delay_decomposition' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input delay_decomposition object of class c("delay_decomposition", "list"). Called primarily for its side effect of printing the linear predictor decomposition summary to the console.
Print method for delay_model
## S3 method for class 'delay_model' print(x, ...)## S3 method for class 'delay_model' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input delay_model object of class c("delay_model", "list"). Called primarily for its side effect of printing the fitted delay model summary and hazard ratios to the console.
Print method for diffused_profiles
## S3 method for class 'diffused_profiles' print(x, ...)## S3 method for class 'diffused_profiles' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input diffused_profiles object of class "diffused_profiles". Called primarily for its side effect of printing profile dimensions and metadata to the console.
Print method for future_neighbour_audit
## S3 method for class 'future_neighbour_audit' print(x, ...)## S3 method for class 'future_neighbour_audit' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input future_neighbour_audit object of class c("future_neighbour_audit", "list"). Called primarily for its side effect of printing the temporal leakage audit summary to the console.
Print method for model_validation
## S3 method for class 'model_validation' print(x, ...)## S3 method for class 'model_validation' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input model_validation object of class c("model_validation", "list"). Called primarily for its side effect of printing temporal holdout validation diagnostics and C-index metrics to the console.
Print method for near_duplicate_audit
## S3 method for class 'near_duplicate_audit' print(x, ...)## S3 method for class 'near_duplicate_audit' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input near_duplicate_audit object of class c("near_duplicate_audit", "list"). Called primarily for its side effect of printing same-date/court duplicate cluster audit findings to the console.
Print method for neighbourhood_survival
## S3 method for class 'neighbourhood_survival' print(x, ...)## S3 method for class 'neighbourhood_survival' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input neighbourhood_survival object of class "neighbourhood_survival". Called primarily for its side effect of printing neighbourhood Kaplan-Meier estimates and median survival duration to the console.
Print method for relative_lateness
## S3 method for class 'relative_lateness' print(x, ...)## S3 method for class 'relative_lateness' print(x, ...)
x |
A |
... |
Additional arguments (unused). |
Invisibly returns the input relative_lateness object of class "relative_lateness". Called primarily for its side effect of printing relative lateness percentile and diagnostic interpretation to the console.
Projects validation cases onto a training-derived legal section representation space using training section graph diffusion, training IDF weights, and similarity against training cases only. This ensures validation case legal scores are calculated strictly from training information, preventing future-neighbour leakage and validation-to-validation contamination.
project_validation_legal_score( val_sections, train_sections, k = 3, use_idf = TRUE, alpha = 0.7 )project_validation_legal_score( val_sections, train_sections, k = 3, use_idf = TRUE, alpha = 0.7 )
val_sections |
A |
train_sections |
A |
k |
Positive integer specifying the maximum number of training neighbours each validation case retains. Defaults to |
use_idf |
Logical flag indicating whether to apply training inverse document frequency (IDF) weighting. Defaults to |
alpha |
Numeric diffusion parameter in |
Information Boundary & Projection Mechanics:
Training Representation: The section graph, section transition matrix , and IDF weights are constructed exclusively from train_sections.
Unseen Sections: Any section present in a validation case that was absent from train_sections is assigned zero contribution (ignored in representation space) without modifying training vocabulary or matrices.
Independent Projection: Each validation case is projected independently against training cases. Validation cases cannot form edges to each other (val_cases_using_val_neighbours = 0).
A list containing:
Named numeric vector of projected legal scores for validation cases.
Character vector of validation case IDs containing sections absent from the training section vocabulary.
Count of validation cases with at least 1 training neighbour.
Count of validation cases using validation neighbours (always 0L by design).
List mapping each validation case ID to a data frame of its top training neighbours and similarity weights.
train_sec <- data.frame( case_id = c("1", "2", "2"), act = c("IPC", "IPC", "CrPC"), section = c("420", "420", "154"), stringsAsFactors = FALSE ) val_sec <- data.frame( case_id = c("3", "3"), act = c("IPC", "NI"), section = c("420", "138"), stringsAsFactors = FALSE ) proj <- project_validation_legal_score(val_sec, train_sec, k = 2) print(proj$validation_legal_scores)train_sec <- data.frame( case_id = c("1", "2", "2"), act = c("IPC", "IPC", "CrPC"), section = c("420", "420", "154"), stringsAsFactors = FALSE ) val_sec <- data.frame( case_id = c("3", "3"), act = c("IPC", "NI"), section = c("420", "138"), stringsAsFactors = FALSE ) proj <- project_validation_legal_score(val_sec, train_sec, k = 2) print(proj$validation_legal_scores)
Evaluates a target case's current age or resolution duration relative to the censoring-aware survival timeline of its legally similar neighbouring cases.
relative_lateness(neighbourhood)relative_lateness(neighbourhood)
neighbourhood |
An object of class |
Estimability Rules:
Relative position is considered estimable = TRUE if either:
The survival curve has reached 0.0 at or before the target duration .
At least one resolution event () occurred in the neighbourhood AND the target duration does not exceed the maximum follow-up duration of the available neighbours.
Otherwise, estimable = FALSE and relative_position is set to NA_real_ (e.g. when all neighbours are censored, or when the target duration exceeds the maximum follow-up of all neighbours).
Step-Function Convention:
When estimable, is evaluated using Kaplan-Meier right-continuous step function logic:
If is before the first event time: .
If lies between event times: equals the survival probability of the most recent event time .
An S3 object of class "relative_lateness" containing:
Character string of the target case ID.
Character status ("pending" or "disposed").
Observed duration/age of the target case (days).
Survival probability from the neighbourhood curve at duration , or NA_real_ if not estimable.
Relative position percentile in [0, 1], or NA_real_ if not estimable.
Logical flag indicating whether the relative position is statistically estimable (TRUE) or not (FALSE).
Human-readable non-causal summary interpretation.
Count of valid neighbouring cases used.
Median resolution time of neighbours (days), or NA_real_.
The input neighbourhood_survival object.
cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sg <- build_section_graph(sec) dp <- diffuse_profiles(sec, sg) cg <- build_case_graph(dp, k = 2) km_res <- neighbourhood_survival(cg, cleaned, case_id = 1) lateness <- relative_lateness(km_res) print(lateness)cases <- data.frame( case_id = 1:3, filing_date = as.Date(c("2012-01-10", "2012-03-05", "2012-06-20")), decision_date = as.Date(c("2012-11-02", "2013-02-14", NA)) ) cleaned <- clean_cases(cases, observation_end = "2014-12-31") sec <- data.frame( case_id = c(1, 1, 2, 2, 3), act = c("NI Act", "NI Act", "NI Act", "NI Act", "IPC"), section = c("138", "141", "138", "142", "302") ) sg <- build_section_graph(sec) dp <- diffuse_profiles(sec, sg) cg <- build_case_graph(dp, k = 2) km_res <- neighbourhood_survival(cg, cleaned, case_id = 1) lateness <- relative_lateness(km_res) print(lateness)
Evaluates out-of-sample statistical discrimination (Harrell's C-index) and calibration
of a fitted delay_model using a chronological temporal holdout split based on filing dates.
Audits for potential future-neighbour leakage when predictors are constructed over a full-sample case graph,
and evaluates a strictly leakage-aware validation pipeline using training-only graph construction and validation projection.
validate_models( fit, data = NULL, case_graph = NULL, time_horizon = NULL, temporal_split = 0.7, seed = 123, sections = NULL, k = 3, alpha = 0.7, use_idf = TRUE )validate_models( fit, data = NULL, case_graph = NULL, time_horizon = NULL, temporal_split = 0.7, seed = 123, sections = NULL, k = 3, alpha = 0.7, use_idf = TRUE )
fit |
A |
data |
Optional |
case_graph |
Optional |
time_horizon |
Optional numeric time horizon (duration) for calibration evaluation. Defaults to median duration. |
temporal_split |
Numeric fraction specifying the proportion of earliest filing date cases assigned to training. Defaults to |
seed |
Optional integer random seed for reproducibility. Defaults to |
sections |
Optional |
k |
Positive integer specifying top-k neighbours for case graph. Defaults to |
alpha |
Numeric diffusion weight in |
use_idf |
Logical flag indicating whether to use IDF weighting. Defaults to |
An S3 object of class c("model_validation", "list") containing:
Logical indicating whether temporal validation was successfully evaluated.
Numeric fraction used for splitting (e.g. 0.7).
Date cutoff threshold separating training and validation cases.
Number of training observations.
Number of validation observations.
Number of resolution events in the training subset.
Number of resolution events in the validation subset.
Vector of minimum and maximum filing dates in training.
Vector of minimum and maximum filing dates in validation.
Harrell's concordance index on the training set.
Harrell's concordance index on the held-out validation set.
C-index computed using full-sample graph legal scores.
C-index computed using training-only graph construction and validation projection.
Logical indicating whether full-sample training cases have future neighbours.
Count of validation cases using validation neighbours (0 by design).
Count of validation cases using training neighbours.
Count of validation cases containing sections unseen in training.
List containing hazard ratios and cox.zph proportional hazards test.
Explicit non-causal statistical safeguard statement.
Character description of validation status.
cases <- data.frame( case_id = paste0("C", 1:20), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 10)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100, 60, 85, 115, 145, 75, 95, 135, 165, 125, 105), event = rep(c(1, 1, 1, 0, 1), 4), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:19), to = paste0("C", 2:20), weight = rep(c(0.8, 0.7, 0.6, 0.9), length.out = 19), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) val <- validate_models(fit, temporal_split = 0.7) print(val)cases <- data.frame( case_id = paste0("C", 1:20), court = "C1", filing_date = as.Date(rep(c("2012-01-10", "2012-02-10"), each = 10)), duration = c(50, 80, 110, 140, 70, 90, 130, 160, 120, 100, 60, 85, 115, 145, 75, 95, 135, 165, 125, 105), event = rep(c(1, 1, 1, 0, 1), 4), stringsAsFactors = FALSE ) cl <- data.frame( court = "C1", date = as.Date(c("2012-01-01", "2012-02-01")), load = c(10, 20), stringsAsFactors = FALSE ) edges <- data.frame( from = paste0("C", 1:19), to = paste0("C", 2:20), weight = rep(c(0.8, 0.7, 0.6, 0.9), length.out = 19), stringsAsFactors = FALSE ) g <- igraph::graph_from_data_frame(edges, directed = FALSE) fit <- fit_delay_model(cases, g, cl) val <- validate_models(fit, temporal_split = 0.7) print(val)