Skip to content

fix: finish KAN-278 terminology cleanup - #441

Merged
corrin merged 3 commits into
mainfrom
feat/KAN-278-follow-up
Jul 12, 2026
Merged

fix: finish KAN-278 terminology cleanup#441
corrin merged 3 commits into
mainfrom
feat/KAN-278-follow-up

Conversation

@corrin

@corrin corrin commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Rename remaining KAN-278 client/contact API and persisted terminology to company/person.
  • Add reversible data migrations for JobEvent types/detail labels and search telemetry source values.
  • Replace the stale client/contact analyzer with scripts/analyze_company_people.py.

Validation

  • pytest apps/company/tests/test_duplicate_phone_report.py apps/job/tests/test_duplicate_phones_view.py apps/crm/tests/test_phone_call_service.py apps/company/tests/test_company_merge_service.py apps/workflow/tests/test_search_telemetry.py apps/job/tests/test_job_event_terminology_migration.py apps/workflow/tests/test_search_telemetry_migration.py -q (68 passed)
  • npm run test:unit -- src/pages/crm/tests/calls.pagination.test.ts (5 passed)
  • ./.venv/bin/python manage.py makemigrations --check --dry-run
  • ./.venv/bin/python scripts/analyze_company_people.py (0 empty names, 0 duplicate company/person-name groups, 985 links)
  • pre-push checks completed during git push

Remaining KAN-278 Work

  • Person merge tooling and duplicate-person report.
  • Entry-prevention UX and person/company link management UI.
  • Xero write-back policy decision; current recommendation is no write-back.

Summary by CodeRabbit

  • New Features

    • CRM phone-call listings now support company-based matching via company_match, with clear rejection messaging for legacy filtering.
    • Duplicate-phones reporting and UI now use consistent company/person terminology (including cross_company).
    • Job/search telemetry and event terminology are updated to reflect company/person-focused labeling.
  • Bug Fixes

    • Data-quality report outputs, API schema docs, and related serializers were aligned with the renamed fields and labels.
    • Added/updated tests and migrations to verify forward/reverse terminology changes.
    • Removed an obsolete analysis script and improved the related company–person link analysis output.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@corrin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46722c66-5f06-46a5-927d-227666f35081

📥 Commits

Reviewing files that changed from the base of the PR and between 5993778 and 6141e5e.

📒 Files selected for processing (1)
  • scripts/analyze_company_people.py
📝 Walkthrough

Walkthrough

The change standardizes client-oriented terminology to company/person terminology across duplicate-phone reporting, CRM phone-call filtering, job events, search telemetry, schemas, frontend views, migrations, tests, and company-person data-quality analysis.

Changes

Company terminology alignment

Layer / File(s) Summary
Duplicate-phone report contracts and rendering
apps/company/services/duplicate_phone_report.py, apps/job/serializers/data_quality_report_serializers.py, frontend/schema.yml, frontend/src/pages/reports/data-quality/duplicate-phones.vue, apps/*/tests/*duplicate*phone*
Renames duplicate-phone fields and issue labels from client terminology to company terminology across report generation, serializers, schemas, tests, and UI rendering.
Company-based phone-call filtering
apps/crm/views/phone_call_views.py, apps/crm/tests/test_phone_call_service.py, frontend/src/pages/crm/calls.vue, frontend/src/pages/crm/__tests__/calls.pagination.test.ts, frontend/schema.yml
Replaces client_match with company_match, rejects the legacy parameter, updates tab query construction, and documents the revised endpoint contract.
Job event terminology migration
apps/job/models/*, apps/job/migrations/0006_rename_job_event_people_company_terms.py, apps/job/tests/*job*event*, apps/company/tests/test_company_merge_service.py
Emits company_changed and person_changed, updates description mappings, migrates stored event data in both directions, and updates related tests.
Company lookup telemetry migration
apps/workflow/migrations/0008_rename_search_telemetry_company_lookup_source.py, frontend/src/composables/useCompanyLookup.ts, frontend/src/components/CompanyLookup.vue, apps/workflow/tests/*telemetry*
Renames lookup telemetry from client_lookup to company_lookup and verifies forward and reverse migration behavior.
Search reason label
apps/job/services/kanban_service.py
Changes the company-name search reason to company_contains.

Company-person data-quality analysis

Layer / File(s) Summary
Company-person analysis command
scripts/analyze_company_people.py
Adds read-only checks for empty person names and duplicate company-person links, flagged-link aggregation, verbose diagnostics, and CLI summary output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • corrin/docketworks#421: Introduced the duplicate-phone report and its earlier client-based conflict terminology.
  • corrin/docketworks#440: Directly updates the duplicate-phone report’s company terminology and owner mapping.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and validation, but it omits the required Jira Work Item section and the checklist from the template. Add a Related Jira Work Item section with the KAN-278 key and include the full checklist section from the template, even if unchecked.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the terminology-cleanup focus and matches the main changes in the PR.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/KAN-278-follow-up

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.

@corrin
corrin marked this pull request as ready for review July 10, 2026 06:27

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/workflow/tests/test_search_telemetry.py (1)

32-48: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add assertion for event.source.

The test sends "source": "company_lookup" but never asserts the value was persisted. Adding assert event.source == "company_lookup" verifies the terminology change end-to-end through the API.

💚 Proposed fix
     assert event.result_count == 7
     assert event.metadata == {"extra": "future-safe"}
+    assert event.source == "company_lookup"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/workflow/tests/test_search_telemetry.py` around lines 32 - 48, Add an
assertion in the search telemetry test after retrieving the event to verify
event.source equals "company_lookup", confirming the API persists the submitted
source value end to end.
🧹 Nitpick comments (1)
scripts/analyze_company_people.py (1)

30-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer Q objects over queryset | operator.

Using Q objects in a single .filter() call is the idiomatic Django pattern and avoids constructing two separate querysets.

♻️ Proposed refactor
+from django.db.models import Count, Q
+
-    empty_name_links = CompanyPersonLink.objects.filter(
-        person__name=""
-    ) | CompanyPersonLink.objects.filter(person__name__regex=r"^\s+$")
+    empty_name_links = CompanyPersonLink.objects.filter(
+        Q(person__name="") | Q(person__name__regex=r"^\s+$")
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/analyze_company_people.py` around lines 30 - 32, Replace the queryset
union assigned to empty_name_links with a single
CompanyPersonLink.objects.filter() call combining the person__name="" and
person__name__regex=r"^\s+$" conditions through a Q object, adding the required
Django Q import.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/analyze_company_people.py`:
- Around line 135-138: Clarify the cleanup estimate logged in the final
logging.info call: rename “Total links after cleanup” to indicate it is an
estimate assuming all flagged links are removed, and calculate the deduction
using the union of empty-name and duplicate links so overlapping links are not
subtracted twice or produce negative values. Update the relevant counting logic
alongside empty_count and duplicate_count to track overlap or unique flagged
links.

---

Outside diff comments:
In `@apps/workflow/tests/test_search_telemetry.py`:
- Around line 32-48: Add an assertion in the search telemetry test after
retrieving the event to verify event.source equals "company_lookup", confirming
the API persists the submitted source value end to end.

---

Nitpick comments:
In `@scripts/analyze_company_people.py`:
- Around line 30-32: Replace the queryset union assigned to empty_name_links
with a single CompanyPersonLink.objects.filter() call combining the
person__name="" and person__name__regex=r"^\s+$" conditions through a Q object,
adding the required Django Q import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 94e2da8c-e7c8-4d4c-acc6-ec6e6d7e1d12

📥 Commits

Reviewing files that changed from the base of the PR and between 05a89f5 and 7c7e158.

⛔ Files ignored due to path filters (1)
  • frontend/src/api/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (23)
  • apps/company/services/duplicate_phone_report.py
  • apps/company/tests/test_company_merge_service.py
  • apps/company/tests/test_duplicate_phone_report.py
  • apps/crm/tests/test_phone_call_service.py
  • apps/crm/views/phone_call_views.py
  • apps/job/migrations/0006_rename_job_event_people_company_terms.py
  • apps/job/models/job.py
  • apps/job/models/job_event.py
  • apps/job/serializers/data_quality_report_serializers.py
  • apps/job/services/kanban_service.py
  • apps/job/tests/test_duplicate_phones_view.py
  • apps/job/tests/test_job_event_terminology_migration.py
  • apps/workflow/migrations/0008_rename_search_telemetry_company_lookup_source.py
  • apps/workflow/tests/test_search_telemetry.py
  • apps/workflow/tests/test_search_telemetry_migration.py
  • frontend/schema.yml
  • frontend/src/components/CompanyLookup.vue
  • frontend/src/composables/useCompanyLookup.ts
  • frontend/src/pages/crm/__tests__/calls.pagination.test.ts
  • frontend/src/pages/crm/calls.vue
  • frontend/src/pages/reports/data-quality/duplicate-phones.vue
  • scripts/analyze_client_contacts.py
  • scripts/analyze_company_people.py
💤 Files with no reviewable changes (1)
  • scripts/analyze_client_contacts.py

Comment thread scripts/analyze_company_people.py

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
scripts/analyze_company_people.py (1)

59-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

DRY violation and N+1 query pattern in flagged_link_ids().

Two concerns:

  1. The empty-name filter Q(person__name="") | Q(person__name__regex=r"^\s+$") is duplicated between analyze_empty_names (line 31–32) and flagged_link_ids (line 62–63). If the criteria change, both must be updated in sync. Extract to a module-level constant.

  2. The loop over duplicate_groups (lines 73–79) issues a separate query per group — an N+1 pattern. For a small dataset this is negligible, but it can be avoided with a single batch query.

♻️ Proposed refactor: extract shared filter and batch duplicate IDs
+EMPTY_NAME_FILTER = Q(person__name="") | Q(person__name__regex=r"^\s+$")
+
+
 def flagged_link_ids() -> set[UUID]:
     """Return company/person link IDs that need manual review."""
     empty_name_ids = set(
-        CompanyPersonLink.objects.filter(
-            Q(person__name="") | Q(person__name__regex=r"^\s+$")
-        ).values_list("id", flat=True)
+        CompanyPersonLink.objects.filter(EMPTY_NAME_FILTER).values_list("id", flat=True)
     )
 
-    duplicate_groups = (
-        CompanyPersonLink.objects.values("company", "person__name")
-        .annotate(count=Count("id"))
-        .filter(count__gt=1)
-    )
-    duplicate_ids: set[UUID] = set()
-    for duplicate in duplicate_groups:
-        duplicate_ids.update(
-            CompanyPersonLink.objects.filter(
-                company_id=duplicate["company"],
-                person__name=duplicate["person__name"],
-            ).values_list("id", flat=True)
-        )
+    from collections import Counter
+
+    all_links = list(
+        CompanyPersonLink.objects.values_list("id", "company_id", "person__name")
+    )
+    group_counts = Counter((c, n) for _, c, n in all_links)
+    duplicate_keys = {k for k, v in group_counts.items() if v > 1}
+    duplicate_ids = {lid for lid, c, n in all_links if (c, n) in duplicate_keys}
 
     return empty_name_ids | duplicate_ids

Then update analyze_empty_names to use the same constant:

-    empty_name_links = CompanyPersonLink.objects.filter(
-        Q(person__name="") | Q(person__name__regex=r"^\s+$")
-    )
+    empty_name_links = CompanyPersonLink.objects.filter(EMPTY_NAME_FILTER)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/analyze_company_people.py` around lines 59 - 82, Refactor the shared
empty-name predicate used by analyze_empty_names and flagged_link_ids into a
module-level constant, then reuse it in both filters. In flagged_link_ids,
replace the per-group CompanyPersonLink query loop with one batch query that
identifies and returns all links matching the duplicate company/person-name
combinations, preserving the existing empty-name and duplicate-ID union.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/analyze_company_people.py`:
- Around line 59-82: Refactor the shared empty-name predicate used by
analyze_empty_names and flagged_link_ids into a module-level constant, then
reuse it in both filters. In flagged_link_ids, replace the per-group
CompanyPersonLink query loop with one batch query that identifies and returns
all links matching the duplicate company/person-name combinations, preserving
the existing empty-name and duplicate-ID union.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f5bdfc1-18e7-456d-9ed0-6dbdb46015f7

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7e158 and 5993778.

📒 Files selected for processing (2)
  • apps/workflow/tests/test_search_telemetry.py
  • scripts/analyze_company_people.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/workflow/tests/test_search_telemetry.py

@corrin
corrin merged commit 6c0a269 into main Jul 12, 2026
9 checks passed
@corrin
corrin deleted the feat/KAN-278-follow-up branch July 12, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant