Skip to content

feat(courts): expose verdict fields on the court-case API, whitelisted - #438

Merged
damo-da merged 1 commit into
mainfrom
feat/courtcase-verdict-fields
Aug 7, 2026
Merged

feat(courts): expose verdict fields on the court-case API, whitelisted#438
damo-da merged 1 commit into
mainfrom
feat/courtcase-verdict-fields

Conversation

@damo-da

@damo-da damo-da commented Aug 7, 2026

Copy link
Copy Markdown
Member

User description

Why

Groundwork for the case-progress stepper (design doc): the public case page needs to show where a CIAA case sits on the CIAA charge → Special Court → appeal ladder, and that needs the docket's verdict.

CourtCaseSerializer exposed 12 fields and stopped there. verdict_type, verdict_date_bs and verdict_date_ad were added to the ORM projection in migration 0003 — the comment at courts/models.py:84-90 says the point was to make verdict "first-class, queryable, and indexable" — but they never crossed the API boundary. Today the frontend can read case_status and hearings, but not the promoted outcome.

The catch: verdict_type is not a clean enum

It's a plain CharField with no DB constraint, and historic Supreme enrichment wrote raw portal text into it. Measured on prod 2026-08-06 — of 104,327 populated supreme rows, 1,323 (1.27%) across 37 distinct values are not enum members:

class rows example
unmapped disposition 718 जारी (writ issued)
unsplit compound 312 बदर, उच्च अदालतमा पठाउने
bench referral 242 पूर्ण इजलासमा पेस हुने
interlocutory 46 कैफियत प्रतिवेदन माग्ने
garbage 5 ।।।।।।।

special, patanhc and kathmandudc are 100% clean, so this is Supreme-specific.

The referrals are the dangerous class, not the garbage. पूर्ण इजलासमा पेस हुने ("to be presented to the full bench") reads like a disposition but means the case is still live. Publishing it verbatim would tell a reader that a pending appeal had been decided — on the exact cases where being wrong matters most.

What this does

verdict_type goes out through a new cs.VERDICT_TYPES frozenset. Anything else serialises as null, which is the honest claim: we hold no classified verdict for that docket. The raw value stays in the database for the DQ backfill to repair.

The dates need no guard. verdict_date_bs is shape-clean corpus-wide (95,366 well-formed / 44,382 null / zero malformed), and every dirty verdict_type row carries a NULL date — so a date can't assert a decision the type guard just suppressed.

No migration. The columns are already in Django's state.

Verification

  • courts/tests + cases/tests — 594 passed
  • tests/api/test_openapi_documentation.py — 8 passed
  • ruff check and ty check clean
  • Mutation-checked: replacing the guard with return value or None fails exactly test_bench_referral_is_not_published_as_a_verdict and test_scrape_garbage_is_not_published
  • Replayed against all 53 distinct prod supreme values: 103,004 publish, 1,323 null, no Devanagari leaks, and every enum member seen on other courts is covered

test_whitelist_covers_every_declared_constant guards the failure mode where someone adds a verdict constant and it's silently dropped on the wire.

Reviewer notes

  • The 1,323 dirty rows are a pre-existing DQ problem this PR contains rather than fixes. Cleanup is tracked separately with the backfill_case_status run.
  • Deliberately not exposing verdict_judge (500-char free-text bench string) or case_subject (untruncated TextField) — not needed for the stepper, and both widen the public surface for no gain.

🤖 Generated with Claude Code


PR Type

Enhancement, Tests


Description

  • Expose court-case verdict fields

  • Whitelist public verdict types

  • Suppress raw Supreme scrape values

  • Add API coverage


Diagram Walkthrough

flowchart LR
  A["CourtCase.verdict_type"] -- "validate" --> B["VERDICT_TYPES"]
  B -- "allowed" --> C["API verdict_type"]
  A -- "unknown" --> D["null"]
  E["Verdict dates"] -- "expose" --> C
Loading

File Walkthrough

Relevant files
Enhancement
case_status.py
Add verdict type whitelist                                                             

courts/case_status.py

  • Adds VERDICT_TYPES whitelist.
  • Documents dirty Supreme raw values.
  • Defines safe public verdict set.
+19/-0   
serializers.py
Expose filtered verdict fields                                                     

courts/serializers.py

  • Adds verdict_type serializer method.
  • Exposes verdict date fields.
  • Converts unknown verdicts to None.
+21/-0   
Tests
test_api.py
Cover verdict API exposure                                                             

courts/tests/test_api.py

  • Tests enum verdict publication.
  • Tests raw Nepali/garbage suppression.
  • Tests null unenriched dockets.
  • Guards whitelist completeness.
+79/-0   


🛠️ Relevant configurations:


These are the relevant configurations for this tool:

[config]

custom_model_max_tokens: 200000
git_provider: github
output_relevant_configurations: True
model: openai/cx/gpt-5.5
ENABLE_AUTO_APPROVAL: True
custom_reasoning_model: False
fallback_models: ['openai/cx/gpt-5.4-mini']
is_auto_command: True
publish_output: True
publish_output_progress: True
progress_gif_url: 
progress_gif_width: 48
verbosity_level: 0
use_extra_bad_extensions: False
log_level: DEBUG
use_wiki_settings_file: True
use_repo_settings_file: True
use_global_settings_file: True
extra_config_url: 
disable_auto_feedback: False
ai_timeout: 120
response_language: en-US
repo_context_files: ['AGENTS.md']
repo_context_from_default_branch: True
repo_context_max_lines: 500
max_description_tokens: 500
max_commits_tokens: 500
max_model_tokens: 32000
model_token_count_estimate_factor: 0.3
patch_extension_skip_types: ['.md', '.txt']
allow_dynamic_context: True
max_extra_lines_before_dynamic_context: 10
patch_extra_lines_before: 5
patch_extra_lines_after: 1
cli_mode: False
large_patch_policy: clip
duplicate_prompt_examples: False
seed: -1
temperature: 0.2
ignore_pr_title: ['^\\[Auto\\]', '^Auto', '^Bump ', '^chore\\(deps\\)']
ignore_pr_target_branches: []
ignore_pr_source_branches: []
ignore_pr_labels: []
ignore_pr_authors: []
ignore_repositories: []
ignore_language_framework: []
restricted_mode: False
enable_ai_metadata: False
reasoning_effort: medium
enable_claude_extended_thinking: False
extended_thinking_budget_tokens: 2048
extended_thinking_max_output_tokens: 4096
claude_extended_thinking_models_override: []
extract_issue_from_branch: True
branch_issue_regex: 
enable_custom_labels: False

[pr_description]

publish_labels: False
add_original_user_description: True
generate_ai_title: False
use_bullet_points: True
extra_instructions: 
enable_pr_type: True
final_update_message: True
enable_help_text: False
enable_help_comment: False
enable_pr_diagram: True
publish_description_as_comment: False
publish_description_as_comment_persistent: True
enable_semantic_files_types: True
collapsible_file_list: adaptive
collapsible_file_list_threshold: 6
inline_file_summary: False
use_description_markers: False
enable_large_pr_handling: True
include_generated_by_header: True
max_ai_calls: 4
async_ai_calls: True

Summary by CodeRabbit

  • New Features

    • Public case responses now include recognized verdict types and verdict dates.
    • Supported verdict values are validated against a defined set of options.
  • Bug Fixes

    • Invalid, blank, raw, or unenriched verdict data is excluded from public responses and returned as unavailable.

The case-progress stepper needs to know whether a docket has been decided
and how, but `CourtCaseSerializer` stopped at the 12 fields it shipped with:
`verdict_type` and the verdict dates were added to the ORM projection in
migration 0003 — explicitly to make verdict "first-class, queryable, and
indexable" — and then never crossed the API boundary. So the frontend can
read `case_status` and hearings but not the promoted outcome.

Exposing the column raw would have been wrong. `verdict_type` is a plain
CharField with no DB constraint, and historic Supreme enrichment wrote raw
portal text into it. Measured on prod 2026-08-06: of 104,327 populated
`supreme` rows, 1,323 (1.27%, 37 distinct values) are not enum members —
242 bench referrals, 46 interlocutory orders, 312 unsplit compounds, 718
unmapped dispositions and 5 rows of punctuation. `special`, `patanhc` and
`kathmandudc` are clean, so this is Supreme-specific.

The referrals are the dangerous class, not the garbage: `पूर्ण इजलासमा पेस
हुने` ("to be presented to the full bench") reads like a disposition but
means the case is still live. Publishing it would tell a reader a pending
appeal was decided.

So `verdict_type` goes out through `cs.VERDICT_TYPES`; anything else
serialises as null, which is the honest claim — we hold no classified
verdict for that docket. The raw value stays in the database for the DQ
backfill to repair. Verified against all 53 distinct prod values: 103,004
publish, 1,323 null, no Devanagari leaks.

The dates need no guard — `verdict_date_bs` is shape-clean corpus-wide
(95,366 well-formed, 44,382 null, zero malformed), and every dirty
`verdict_type` row carries a NULL date, so a date can't assert a decision
the type guard just suppressed.

No migration: the columns already exist in Django's state.

Co-Authored-By: Claude <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 18df7806-5565-4d98-9f77-72c4f49b26c2

📥 Commits

Reviewing files that changed from the base of the PR and between 8ec884f and 448b84b.

📒 Files selected for processing (3)
  • courts/case_status.py
  • courts/serializers.py
  • courts/tests/test_api.py

📝 Walkthrough

Walkthrough

The change defines supported verdict types, exposes validated values through CourtCaseSerializer, and adds API tests for recognized, blank, invalid, and missing verdict values.

Changes

Verdict exposure

Layer / File(s) Summary
Verdict type contract
courts/case_status.py
Defines VERDICT_TYPES as the complete public whitelist of supported verdict constants.
Serializer validation and API coverage
courts/serializers.py, courts/tests/test_api.py
Adds the read-only verdict_type field. The serializer trims values and returns only whitelisted values. Tests cover valid and suppressed values and verify whitelist consistency.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: damodaha

Poem

A rabbit checks the verdict set,
And trims each field before it’s met.
Valid words hop into the case,
Scrape-garbage leaves no trace.
Tests watch every value’s flight,
Whitelisted and serialized right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: exposing whitelisted verdict fields through the court-case API.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/courtcase-verdict-fields

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jawafdehi-pr-agent

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

🛠️ Relevant configurations:


These are the relevant configurations for this tool:

[config]

enable_ai_metadata: False
custom_model_max_tokens: 200000
git_provider: github
output_relevant_configurations: True
model: openai/cx/gpt-5.5
ENABLE_AUTO_APPROVAL: True
custom_reasoning_model: False
fallback_models: ['openai/cx/gpt-5.4-mini']
is_auto_command: True
publish_output: True
publish_output_progress: True
progress_gif_url: 
progress_gif_width: 48
verbosity_level: 0
use_extra_bad_extensions: False
log_level: DEBUG
use_wiki_settings_file: True
use_repo_settings_file: True
use_global_settings_file: True
extra_config_url: 
disable_auto_feedback: False
ai_timeout: 120
response_language: en-US
repo_context_files: ['AGENTS.md']
repo_context_from_default_branch: True
repo_context_max_lines: 500
max_description_tokens: 500
max_commits_tokens: 500
max_model_tokens: 32000
model_token_count_estimate_factor: 0.3
patch_extension_skip_types: ['.md', '.txt']
allow_dynamic_context: True
max_extra_lines_before_dynamic_context: 10
patch_extra_lines_before: 5
patch_extra_lines_after: 1
cli_mode: False
large_patch_policy: clip
duplicate_prompt_examples: False
seed: -1
temperature: 0.2
ignore_pr_title: ['^\\[Auto\\]', '^Auto', '^Bump ', '^chore\\(deps\\)']
ignore_pr_target_branches: []
ignore_pr_source_branches: []
ignore_pr_labels: []
ignore_pr_authors: []
ignore_repositories: []
ignore_language_framework: []
restricted_mode: False
reasoning_effort: medium
enable_claude_extended_thinking: False
extended_thinking_budget_tokens: 2048
extended_thinking_max_output_tokens: 4096
claude_extended_thinking_models_override: []
extract_issue_from_branch: True
branch_issue_regex: 
enable_custom_labels: False

[pr_reviewer]

require_ticket_analysis_review: False
require_score_review: False
require_tests_review: True
require_estimate_effort_to_review: True
require_can_be_split_review: False
require_security_review: True
require_estimate_contribution_time_cost: False
require_todo_scan: False
publish_output_no_suggestions: True
persistent_comment: True
extra_instructions: Focus on: logic errors and edge cases; security/authz regressions; missing error handling;
Django/DRF correctness (migrations, N+1 queries, transaction/atomicity, serializer & permission gaps).
Do NOT comment on formatting, import order, or naming — ruff handles those in CI.

num_max_findings: 3
final_update_message: True
enable_review_labels_security: True
enable_review_labels_effort: True
require_all_thresholds_for_incremental_review: False
minimal_commits_for_incremental_review: 0
minimal_minutes_for_incremental_review: 0
enable_intro_text: True
enable_help_text: False

@jawafdehi-pr-agent

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Gate verdict dates

Invalid verdict_type still exposes verdict_date_bs / verdict_date_ad if legacy data
has dates. Gate dates through the same whitelist, else API can imply a real verdict.

courts/serializers.py [32-62]

 verdict_type = serializers.SerializerMethodField()
+verdict_date_bs = serializers.SerializerMethodField()
+verdict_date_ad = serializers.SerializerMethodField()
 ...
             "material_id", "courtcase_iri",
             "verdict_type", "verdict_date_bs", "verdict_date_ad",
 ...
+    def _has_public_verdict(self, obj: CourtCase) -> bool:
+        return (obj.verdict_type or "").strip() in cs.VERDICT_TYPES
+
     def get_verdict_type(self, obj: CourtCase) -> str | None:
-        ...
         value = (obj.verdict_type or "").strip()
         return value if value in cs.VERDICT_TYPES else None
 
+    def get_verdict_date_bs(self, obj: CourtCase) -> str | None:
+        return obj.verdict_date_bs if self._has_public_verdict(obj) else None
+
+    def get_verdict_date_ad(self, obj: CourtCase):
+        return obj.verdict_date_ad if self._has_public_verdict(obj) else None
+
Suggestion importance[1-10]: 8

__

Why: Valid bug: invalid verdict_type is hidden, but verdict_date_bs / verdict_date_ad still leak for dirty legacy rows. Same whitelist gate preserves API semantics.

Medium

🛠️ Relevant configurations:


These are the relevant configurations for this tool:

[config]

enable_ai_metadata: False
custom_model_max_tokens: 200000
git_provider: github
output_relevant_configurations: True
model: openai/cx/gpt-5.5
ENABLE_AUTO_APPROVAL: True
custom_reasoning_model: False
fallback_models: ['openai/cx/gpt-5.4-mini']
is_auto_command: True
publish_output: True
publish_output_progress: True
progress_gif_url: 
progress_gif_width: 48
verbosity_level: 0
use_extra_bad_extensions: False
log_level: DEBUG
use_wiki_settings_file: True
use_repo_settings_file: True
use_global_settings_file: True
extra_config_url: 
disable_auto_feedback: False
ai_timeout: 120
response_language: en-US
repo_context_files: ['AGENTS.md']
repo_context_from_default_branch: True
repo_context_max_lines: 500
max_description_tokens: 500
max_commits_tokens: 500
max_model_tokens: 32000
model_token_count_estimate_factor: 0.3
patch_extension_skip_types: ['.md', '.txt']
allow_dynamic_context: True
max_extra_lines_before_dynamic_context: 10
patch_extra_lines_before: 5
patch_extra_lines_after: 1
cli_mode: False
large_patch_policy: clip
duplicate_prompt_examples: False
seed: -1
temperature: 0.2
ignore_pr_title: ['^\\[Auto\\]', '^Auto', '^Bump ', '^chore\\(deps\\)']
ignore_pr_target_branches: []
ignore_pr_source_branches: []
ignore_pr_labels: []
ignore_pr_authors: []
ignore_repositories: []
ignore_language_framework: []
restricted_mode: False
reasoning_effort: medium
enable_claude_extended_thinking: False
extended_thinking_budget_tokens: 2048
extended_thinking_max_output_tokens: 4096
claude_extended_thinking_models_override: []
extract_issue_from_branch: True
branch_issue_regex: 
enable_custom_labels: False

[pr_code_suggestions]

commitable_code_suggestions: False
dual_publishing_score_threshold: -1
focus_only_on_problems: True
extra_instructions: Prefer a few high-impact, project-specific suggestions over many generic ones.
Skip style/formatting (ruff-enforced) and changes under cases/migrations/.

enable_help_text: False
enable_chat_text: False
persistent_comment: True
max_history_len: 4
publish_output_no_suggestions: True
suggestions_score_threshold: 0
new_score_mechanism: True
new_score_mechanism_th_high: 9
new_score_mechanism_th_medium: 7
auto_extended_mode: True
num_code_suggestions_per_chunk: 3
max_number_of_calls: 3
parallel_calls: True
final_clip_factor: 0.8
decouple_hunks: False
demand_code_suggestions_self_review: False
code_suggestions_self_review_text: **Author self-review**: I have reviewed the PR code suggestions, and addressed the relevant ones.
approve_pr_on_self_review: False
fold_suggestions_on_self_review: True
num_code_suggestions: 4

@jawafdehi-pr-agent

Copy link
Copy Markdown

PR Agent Walkthrough 🤖

Welcome to the PR Agent, an AI-powered tool for automated pull request analysis, feedback, suggestions and more.

Here is a list of tools you can use to interact with the PR Agent:

ToolDescriptionTrigger Interactively 💎

DESCRIBE

Generates PR description - title, type, summary, code walkthrough and labels
  • Run

REVIEW

Adjustable feedback about the PR, possible issues, security concerns, review effort and more
  • Run

IMPROVE

Code suggestions for improving the PR
  • Run

UPDATE CHANGELOG

Automatically updates the changelog
  • Run

HELP DOCS

Answers a question regarding this repository, or a given one, based on given documentation path
  • Run

ADD DOCS

Generates documentation to methods/functions/classes that changed in the PR
  • Run

ASK

Answering free-text questions about the PR

[*]

GENERATE CUSTOM LABELS

Generates custom labels for the PR, based on specific guidelines defined by the user

[*]

(1) Note that each tool can be triggered automatically when a new PR is opened, or called manually by commenting on a PR.

(2) Tools marked with [*] require additional parameters to be passed. For example, to invoke the /ask tool, you need to comment on a PR: /ask "<question content>". See the relevant documentation for each tool for more details.

@jawafdehi-pr-agent

Copy link
Copy Markdown

Auto-approved PR

@damo-da
damo-da merged commit 0be1fda into main Aug 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant