feat(case_tags): the tag vocabulary app — Tag, TagAlias, normalizer, seed - #467
feat(case_tags): the tag vocabulary app — Tag, TagAlias, normalizer, seed#467notashwinii wants to merge 1 commit into
Conversation
…seed Machinery only. No vocabulary content and nothing reads it yet; `Case.tags` is untouched. The 123-term vocabulary.yml lands next, deliberately separate so a Nepali-speaking reviewer can check labels and aliases without reading Django. A separate app rather than more models in `cases`: the vocabulary is a controlled list that happens to be applied to cases today, not a property of a case. Keeping it apart is what lets entity or material keywords reuse it later without importing the case model. It falls through `_db_for_label` to `default` alongside `cases`, which it must, since resolving a case's tags will join the two. `Tag.id` is the slug, not a surrogate key -- it is what the index stores, what `?tags=` carries, and what a reviewer reads in the YAML. A numeric pk would put an opaque join between all three. `TagAlias.tag` is nullable on purpose. Three outcomes have to be distinguishable: resolves to a tag, was deliberately dropped, was never seen. `?tags=CIAA` is a live URL today; after the cleanup it must be able to say "that filter was removed" rather than "unknown tag", which reads as a bug to anyone holding a bookmark. The `dropped:` block seeds those rows. The normalizer's non-obvious step is the Preeti repair: ो and ौ arrive DECOMPOSED as ा+े / ा+ै from legacy Preeti->Unicode conversion. They render almost identically but are different byte sequences, so those values can never match a correctly typed query -- two live corpus values are affected. NFC does not fix this, because ा+े is not a canonical decomposition of ो but two distinct marks that happen to look like one. The repair is explicit and runs before anything compares strings. `broader` is capped at one level, in clean() and at seed time. Roll-up is applied by walking it at index time; a chain would mean an unbounded walk per document and a facet where selecting a tag silently pulls in grandparents nobody chose. Three CHECK constraints, not just clean(), because a bulk write bypasses model validation: a merged tag must name its replacement, and neither self-reference is allowed. resolve() also guards for a merge cycle -- unreachable through the constraints in one write, but arrivable by editing two rows that were each fine. ANN lint applies here (per-file-ignores is an opt-OUT list and this directory is not on it), so everything is annotated; verified by probing that an unannotated function in this package does fail the gate. Dockerfile COPY alongside the pyproject wheel entry -- tests/test_app_package_names catches exactly this, and caught that I had done only the second half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 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. Comment |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
🛠️ Relevant configurations:
[config] enable_ai_metadata: False
model: openai/cx/gpt-5.5
git_provider: github
custom_reasoning_model: False
output_relevant_configurations: True
custom_model_max_tokens: 200000
fallback_models: ['openai/cx/gpt-5.4-mini']
ENABLE_AUTO_APPROVAL: True
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
|
PR Code Suggestions ✨Explore these optional code suggestions:
🛠️ Relevant configurations:
[config] enable_ai_metadata: False
model: openai/cx/gpt-5.5
git_provider: github
custom_reasoning_model: False
output_relevant_configurations: True
custom_model_max_tokens: 200000
fallback_models: ['openai/cx/gpt-5.4-mini']
ENABLE_AUTO_APPROVAL: True
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
|
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:
(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 |
|
Auto-approved PR |
User description
Machinery for a controlled tag vocabulary. No vocabulary content, nothing reads it,
Case.tagsis untouched. The 121-termvocabulary.ymlis the follow-up PR, kept separate so a Nepali-speaking reviewer can check labels and aliases without reading Django.Why this exists
Case.tagsis free text. Across the 82 published cases it holds 144 distinct values — including seven spellings of "illicit enrichment", four of Ncell, 21 money amounts, 2 court case numbers, and a handful of people’s names. None of it can be a public filter as it stands.What is here
models.pyTag,TagAlias,resolve()normalize.pyseed_case_tags--dry-runThree decisions worth your attention
Tag.idis the slug, not a surrogate key. It is what the search index stores, what?tags=carries, and what a reviewer reads in the YAML. A numeric pk would put an opaque join between all three.TagAlias.tagis nullable. Three outcomes have to stay distinguishable: resolves to a tag, was deliberately dropped, was never seen.?tags=CIAAis a live URL today — after the cleanup it must be able to say that filter was retired rather than unknown tag, which reads as a bug to anyone holding a bookmark.broaderis capped at one level, inclean()and at seed time. Roll-up is applied by walking it at index time; a chain would make that walk unbounded and let selecting a tag silently pull in grandparents nobody chose.The non-obvious bit of the normalizer
ोandौarrive decomposed asा+े/ा+ैfrom legacy Preeti→Unicode conversion. They render almost identically but are different byte sequences, so those values can never match a correctly typed query — two live corpus values are affected. NFC does not fix it:ा+ेis not a canonical decomposition ofो, it is two distinct marks that happen to look like one. The repair is explicit and runs before anything compares strings.Notes for review
clean(), because a bulk write bypasses model validation.resolve()also guards a merge cycle — unreachable in one write, but arrivable by editing two rows that were each individually fine.ANNlint applies here (per-file-ignoresis an opt-out list and this directory is not on it), so everything is annotated. Verified by probing that an unannotated function in this package does fail the gate.DockerfileCOPY alongside thepyprojectwheel entry —tests/test_app_package_namescatches exactly this omission, and caught that I had done only one half.Full suite green: 5442 passed, 5 skipped.
ruff,tyandmakemigrations --checkall clean.🤖 Generated with Claude Code
PR Type
Enhancement, Tests
Description
case_tagsvocabulary app addedTag normalization, resolution implemented
YAML seed command added
Model, seed tests covered
Diagram Walkthrough
File Walkthrough
4 files
Add case tag app configSeed YAML vocabulary into databaseDefine tags aliases resolutionNormalize raw tag values1 files
Create tag vocabulary schema3 files
Test tag model invariantsTest tag normalization behaviorTest vocabulary seed command3 files
Register `case_tags` appInclude `case_tags` in imagePackage `case_tags` module5 files
🛠️ Relevant configurations:
These are the relevant configurations for this tool:
[config]
[pr_description]