Skip to content

DD-42807 - Manual by-case ingestion endpoint (POST /ingestions/start-by-case) - #195

Merged
rkakumanuhmcts merged 5 commits into
developfrom
dev/DD-42807
Jul 16, 2026
Merged

DD-42807 - Manual by-case ingestion endpoint (POST /ingestions/start-by-case)#195
rkakumanuhmcts merged 5 commits into
developfrom
dev/DD-42807

Conversation

@rkakumanuhmcts

@rkakumanuhmcts rkakumanuhmcts commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

JIRA link (if applicable)

DD-42807

Change description

What

Adds POST /ingestions/start-by-case — a synchronous ingestion entry point triggered by the "Process IDPC" button on the AI Search page, which accepts a single caseId and returns the real outcome (STARTED / NOT_REQUIRED / FAILED) in the response.

Why

The existing POST /ingestions/start endpoint is designed for the scheduled/multi-case flow: it looks up cases via GET_CASES_FOR_HEARING and returns 202 ACCEPTED immediately without knowing the outcome. The AI Search UI needs a single-case, synchronous variant that waits for the actual IDPC-availability result before responding, so the user gets an immediate, accurate answer rather than a fire-and-forget acknowledgement.

Changes

  • New endpoint: IngestionController.startIngestionProcessByCase, delegating to a new IngestionProcessorByCase interface / IngestionProcessorByCaseService implementation — mirrors the existing IngestionProcessor / JobManagerService pattern.
  • Shared, plain business rule: IdpcAvailabilityService (no JobManager/task-framework types) is the single owner of the IDPC-availability rule and is called directly, in-process, by both IngestionProcessorByCaseService (this endpoint, synchronous) and CheckIdpcAvailabilityAllDefendantsTask (the existing scheduled JobManager task). There is no separate case-eligibility step/service/task — an empty document list already covers "case doesn't exist", "no defendants", and "already ingested" alike, so the earlier CaseEligibilityService/CheckCaseEligibilityTask round-trip was redundant and has been removed; GetCasesForHearingTask now dispatches CHECK_IDPC_AVAILABILITY_ALL_DEFENDANTS directly.
  • Shared job-data building: RetrieveMaterialAndUploadJobDataService builds the per-document RETRIEVE_MATERIAL_AND_UPLOAD job data once, reused by both the task and the service, instead of duplicating that logic in two places. Each caller still owns its own final ExecutionInfo/executionService.executeWith(...) dispatch shape.
  • New JobPriority constants (HIGH/DEFAULT): every JobManager task submitted from this endpoint runs at HIGH priority, so manual clicks are picked up ahead of scheduled/nightly ingestion. The existing scheduled endpoint is unaffected and stays at default priority.
  • ACL: added a Drools rule for the new casedocumentknowledge-service.ingestion-process-by-case action (cdks-rules.drl) — without it the endpoint 403s. Also removed a pre-existing dead duplicate rule and fixed a rule-name typo while in the file.
  • Removed now-dead progression client code: with the eligibility step gone, ProgressionClient.getProsecutionCaseEligibilityInfo (and its ProsecutionCase/ProsecutionCaseResponse/Defendant/ProsecutionCaseEligibilityInfo DTOs, and config) had no remaining caller and have been deleted.
  • Docs: updated .claude/context/csdk-context.md (API surface table + endpoint behaviour note, kept in sync with the current flow) and .claude/context/tech-stack.md (API contract version).
  • Tests: unit tests for IngestionProcessorByCaseService, IdpcAvailabilityService, CheckIdpcAvailabilityAllDefendantsTask, and IngestionController covering all three response phases; a compose-backed integration test (IngestionProcessByCaseHttpLiveTest) covering STARTED, NOT_REQUIRED, and FAILED end-to-end (FAILED now exercises a downstream court-document-search failure, since that's the only remaining downstream call in the synchronous path).

Does this PR introduce a breaking change?

[ ] Yes
[x] No

…art-by-case)

Adds a synchronous entry point for the "Process IDPC" button on the AI Search
page: given a single caseId it runs eligibility and IDPC-availability checks
inline (via CheckCaseEligibilityTask/CheckIdpcAvailabilityAllDefendantsTask,
same task classes the scheduled flow uses) and only responds once the outcome
is known - STARTED, NOT_REQUIRED, or FAILED. Downstream JobManager tasks run
at HIGH priority so manual clicks jump ahead of scheduled ingestion; the
existing /ingestions/start endpoint is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rkakumanuhmcts
rkakumanuhmcts requested a review from a team as a code owner July 13, 2026 15:45
@rkakumanuhmcts

Copy link
Copy Markdown
Contributor Author

Code Review — DD-42807 POST /ingestions/start-by-case (Claude, pipeline stage 6)

Reviewed diff of dev/DD-42807 against develop (merge-base 161f554, so scope = this PR only). 16 files, +953/-114.

Verdict: Approve with minor changes (non-blocking). The four deliberate architectural decisions all hold up in the code (see below). No correctness, security, PII, or Flyway issues. Three MEDIUM quality items and a few LOW notes are recommended before merge — human reviewer makes the final call.


Design decisions — verified against the code (not just intent)

# Decision Verdict
1 Synchronous in-process invocation of the two tasks (not executeWith) PASSIngestionProcessorByCaseService calls checkCaseEligibilityTask.execute(...) then checkIdpcAvailabilityAllDefendantsTask.registerNewDocumentsAndDispatch(...) directly; HTTP response reflects real STARTED/NOT_REQUIRED/FAILED before returning.
2 CTX_SYNCHRONOUS_INVOCATION_KEY prevents double-dispatch PASS with a latent trap (MEDIUM-2). Flag is read in exactly one place (CheckCaseEligibilityTask.execute L92); when set + eligible it returns the follow-on ExecutionInfo without executeWith. CHECK_IDPC_AVAILABILITY_ALL_DEFENDANTS is therefore never enqueued in the sync path, and CHECK_CASE_ELIGIBILITY itself is never enqueued (only execute() is called directly). No double-dispatch path exists today.
3 JobPriority.HIGH propagates via ExecutionInfo.Builder#from(...) down to the queued RETRIEVE_MATERIAL_AND_UPLOAD job PASS in code, but partially untested (MEDIUM-3). Leg 1 (eligibility→IDPC) copies priority via .from(executionInfo) and is asserted by CheckCaseEligibilityTaskTest (result.getPriority()==HIGH). Leg 2 (IDPC→RETRIEVE_MATERIAL_AND_UPLOAD) also uses .from(executionInfo), so the chain is intact — but no test asserts the dispatched retrieval job carries HIGH, and that is the only job that actually enters the queue where ORDER BY priority ASC takes effect.
4 New Drools rule for casedocumentknowledge-service.ingestion-process-by-case, dead duplicate + typo cleaned up PASS. Confirmed against full file both sides: the original top ingestion-process rule is retained (existing POST /ingestions/start still authorised — no 403 regression); the pre-existing duplicate ingestion-process rule at the bottom was repurposed into the new ingestion-process-by-case rule; the "ingestion sattus""ingestion status" name typo was fixed (action ...ingestion unchanged). Action name matches the controller media type application/vnd.casedocumentknowledge-service.ingestion-process-by-case+json. Nothing else in the file changed.
5 resolveEligibleCase/withDefendantContext package-private PASS. Only callers are CheckCaseEligibilityTask itself and its test in the same package (jobmanager.caseflow). Access level sufficient; nothing broken.
6 API contract bump 0.0.10 → 0.0.11 PASS. Bumped consistently in gradle.properties (version.cdk) and documented in tech-stack.md; supplies IngestionProcessByCaseRequest, IngestionApi.startIngestionProcessByCase, and IngestionProcessPhase.NOT_REQUIRED. (Not independently verifiable without resolving the artifact, but code compiles/tests against it per the PR.)

Findings (ranked)

MEDIUM-1 — Misleading NOT_REQUIRED message for the "not eligible" branch (IngestionProcessorByCaseService, MSG_NOT_REQUIRED)
NOT_REQUIRED is returned for two semantically different situations: (a) eligible but no newer IDPC version, and (b) case not eligible / not found / no defendants. Both return the same message: "...no newer IDPC version is available and an Answers version already exists." For branch (b) neither clause is verified — the code never checks whether an Answers version exists, and the case may simply not exist. IngestionProcessorByCaseServiceTest.returnsNotRequired_whenNotEligible even asserts this misleading text for the not-eligible case. Recommend distinguishing the two branches (or softening the message to not assert an unverified "Answers version already exists").

MEDIUM-2 — synchronousInvocation flag leaks into the async continuation (CheckIdpcAvailabilityAllDefendantsTask.registerNewDocumentsAndDispatch)
The dispatched RETRIEVE_MATERIAL_AND_UPLOAD job data is built with createObjectBuilder(jobData), which copies synchronousInvocation=true forward. That flag then rides through the async chain (RetrieveMaterialAndUpload and anything after it). Harmless today because only CheckCaseEligibilityTask.execute reads it — but it is a latent foot-gun: if any downstream task is later taught to honour the flag, it would wrongly suppress its own dispatch in the async continuation. Recommend stripping the flag when crossing the sync→async boundary (i.e. before dispatching the retrieval job).

MEDIUM-3 — No test guards HIGH-priority propagation to the queued job (design point 3)
CheckIdpcAvailabilityAllDefendantsTaskTest captures executeWith(...) but never asserts getPriority(); the service test mocks registerNewDocumentsAndDispatch. So the one leg where priority actually affects JobExecutor ordering is unverified. Add a captor assertion that the dispatched RETRIEVE_MATERIAL_AND_UPLOAD ExecutionInfo carries JobPriority.HIGH.

LOW-1 — FAILED detection couples the sync service to async retry encoding. IngestionProcessorByCaseService treats ExecutionStatus.INPROGRESS as "eligibility failed" (that is what CheckCaseEligibilityTask's catch block returns, alongside shouldRetry). It works and is tested, but the sync caller reading a retry-signalling status as a terminal failure is an abstraction leak; a dedicated failure signal would be more robust.

LOW-2 — HTTP 200 for FAILED. An internal failure returns 200 OK with phase=FAILED in the body (per the generated contract). Clients/monitoring cannot detect failures by status code — they must parse the body. Confirm this matches the API-contract intent (it appears to).

LOW-3 — requestId is freshly generated, not derived from an inbound correlation header. UUID.randomUUID() in the service means a manual click is only traceable within this service; there is no MDC correlationId/requestId set for the request (consistent with the existing code, not a regression). Worth confirming against logging-standards.md MDC expectations.

INFO — Logging of caseId. New log lines log caseId (UUID). Consistent with the existing codebase convention; flagged only so the human reviewer confirms a case UUID is not treated as court-reference data under the no-PII-in-logs rule. No document content / PII is logged.


Checklist summary

  • Correctness / double-dispatch / priority chain / ACL wiring: PASS (see table).
  • Security — no secrets, no connection strings/SAS/keys, Managed-Identity-only: PASS / N/A (no Azure or config changes).
  • PII — synthetic UUIDs only in tests/stubs; ffffffff-... failing-case id; no real court references: PASS.
  • Logging — @Slf4j JSON, no System.out, no document bodies logged: PASS (see LOW-3, INFO).
  • Flyway append-only: N/A (no migrations; IT seeds via JDBC in test only).
  • Tests — behaviour-focused, all three phases covered at unit + controller + compose IT level; sync-invocation branch and package-private helpers unit-tested: PASS (gap noted in MEDIUM-3).
  • Docs — csdk-context.md / tech-stack.md updated to match: PASS.
  • Accessibility: N/A (backend endpoint, no UI).

Stage 6 is a human gate. This is an automated pre-review — a human engineer must approve before CI/ci-orchestrator proceeds. None of the findings above are blocking; MEDIUM-1..3 are recommended before merge.

🤖 Generated with Claude Code

@rkakumanuhmcts rkakumanuhmcts added reviewed-by-claude Automated pre-review by Claude complete claude-approved Claude pre-review passed; awaiting human approval labels Jul 14, 2026
- Split the shared NOT_REQUIRED message: "case not eligible" now gets its
  own accurate message instead of reusing the "no newer IDPC version
  available, Answers already exists" text, which was misleading when the
  case was never found or has no defendants.
- Strip CTX_SYNCHRONOUS_INVOCATION_KEY from job data before it reaches the
  dispatched RETRIEVE_MATERIAL_AND_UPLOAD job, so the sync-only flag never
  rides into the async job queue.
- Add a test asserting HIGH priority actually propagates through to the
  dispatched retrieval job (the only point priority takes effect), and that
  the synchronous-invocation flag is stripped from its job data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
if (eligibilityResult.getExecutionStatus() == ExecutionStatus.INPROGRESS) {
log.error("Manual ingestion could not be started; eligibility check failed. "
+ "caseId={}, requestId={}", caseId, requestId);
return failed(response);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could the task be still retrying in the background when though response to the client is FAILED?

…e classes

Per review comment: reverts CheckCaseEligibilityTask and
CheckIdpcAvailabilityAllDefendantsTask to thin JobManager task wrappers, and
moves the reusable business logic into CaseEligibilityService and
IdpcAvailabilityService respectively. Both the async scheduled flow (via the
task classes) and the synchronous manual "Process IDPC" flow (via
IngestionProcessorByCaseService) now call these services directly as plain
methods, rather than IngestionProcessorByCaseService invoking
CheckCaseEligibilityTask.execute(ExecutionInfo) through the task-manager
contract.

This removes the need for CTX_SYNCHRONOUS_INVOCATION_KEY entirely: since
IngestionProcessorByCaseService never touches the task classes' execute()
methods, there is no risk of the async job queue being triggered twice for
the same case, so the flag (and its associated stripping-before-dispatch
logic) is deleted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
public ResponseEntity<IngestionProcessResponse> startIngestionProcessByCase(
@RequestBody @Valid final IngestionProcessByCaseRequest ingestionProcessByCaseRequest
) {
final String headerName = cqrsClientProperties.headers().cjsCppuid();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is userId

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not fixed..

return notRequired(response, MSG_NOT_REQUIRED_NOT_ELIGIBLE);
}

final JsonObject jobData = Json.createObjectBuilder()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

static import

* @return the number of newer IDPC documents that require ingestion (i.e. retrieval tasks
* dispatched). {@code 0} means no newer IDPC version is available.
*/
public int registerNewDocumentsAndDispatch(final ExecutionInfo executionInfo) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

method signature should be free of job manager library POJOs.. pass things like caseId, userId explicitly as required by then service

* @return the number of newer IDPC documents that require ingestion (i.e. retrieval tasks
* dispatched). {@code 0} means no newer IDPC version is available.
*/
public int registerNewDocumentsAndDispatch(final ExecutionInfo executionInfo) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rename perhaps to clearly call out what the method does..

* @return the number of newer IDPC documents that require ingestion (i.e. retrieval tasks
* dispatched). {@code 0} means no newer IDPC version is available.
*/
public int registerNewDocumentsAndDispatch(final ExecutionInfo executionInfo) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

make this class complete free of job manager classes

.add(CTX_CASE_ID_KEY, caseId.toString())
.build();

final JsonObject enrichedJobData =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

delete this and figure out where this is actually needed

* Enriches the supplied job data with the defendant context required by the downstream
* IDPC-availability step.
*/
public JsonObject withDefendantContext(final JsonObject jobData, final ProsecutionCaseEligibilityInfo info) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should not be public.. and hence will not be needed here

…data building

CaseEligibilityService/CheckCaseEligibilityTask added a round-trip that
duplicated what IdpcAvailabilityService already establishes: an empty
document list means nothing to ingest, whether that's because the case
doesn't exist, has no defendants, or is already up to date. Both the
scheduled flow (GetCasesForHearingTask now dispatches
CHECK_IDPC_AVAILABILITY_ALL_DEFENDANTS directly) and the manual by-case
flow (IngestionProcessorByCaseService) call IdpcAvailabilityService
directly instead.

Extracted RetrieveMaterialAndUploadJobDataService to build the
per-document RETRIEVE_MATERIAL_AND_UPLOAD job data once, shared by both
call sites, instead of duplicating that logic in two private dispatch
methods.

Removed the now-dead prosecution-case-eligibility client method, its
DTOs and config, and the wiremock fixtures backing it; added a
replacement failure stub on the court-document-search endpoint so the
by-case FAILED integration scenario still exercises a real error path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .claude/context/csdk-context.md
public ResponseEntity<IngestionProcessResponse> startIngestionProcessByCase(
@RequestBody @Valid final IngestionProcessByCaseRequest ingestionProcessByCaseRequest
) {
final String headerName = cqrsClientProperties.headers().cjsCppuid();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not fixed..

The intermediate headerName variable held the header's name, not the
user id, which read as mislabeled per review. Inlining the lookup
removes the confusion entirely — cppuid is the only variable left, and
it holds the actual value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rkakumanuhmcts
rkakumanuhmcts merged commit 86e09f7 into develop Jul 16, 2026
9 checks passed
@rkakumanuhmcts
rkakumanuhmcts deleted the dev/DD-42807 branch July 16, 2026 16:29
rkakumanuhmcts added a commit that referenced this pull request Jul 30, 2026
* Discovery Scheduler Configuration: persistence, service, and REST endpoint (#191)

* added discovery schdule initial files from claude

* added repository method through Calude

* updated claude docs through Claude

* Fix CodeQL log-injection finding on DiscoverySchedulerController

Log specific request fields (courtCentreId, courtRoomId, version) instead of
the raw request DTO, addressing the java/log-injection alert flagged on PR #191.

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

---------

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* [DD-42880] implement nightly scheduler; calculate hearing days ahead and process scheduled ingestions for the dates (#192)

* [DD-42880] implement nightly scheduler; calculate hearing days ahead and process scheduled ingestions for the dates

* DD-42899 - calculate hearingDays ahead and dispatch ingestion workflow tasks for courtcentre configurations (#194)

* calculate hearingDays ahead and dispatch ingestion workflow tasks for courtcentre configurations

* removed Transactional to limit transaction to creating/inserting the jobmanager task

* throw exception when no value for env variable CASEDOCUMENTKNOWLEDGE_SYSTEM_USER_ID

* tidyup

* DD-42807 - Manual by-case ingestion endpoint (POST /ingestions/start-by-case) (#195)

* DD-42807 - add manual by-case ingestion endpoint (POST /ingestions/start-by-case)

Adds a synchronous entry point for the "Process IDPC" button on the AI Search
page: given a single caseId it runs eligibility and IDPC-availability checks
inline (via CheckCaseEligibilityTask/CheckIdpcAvailabilityAllDefendantsTask,
same task classes the scheduled flow uses) and only responds once the outcome
is known - STARTED, NOT_REQUIRED, or FAILED. Downstream JobManager tasks run
at HIGH priority so manual clicks jump ahead of scheduled ingestion; the
existing /ingestions/start endpoint is unaffected.

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

* DD-42807 - address code review findings on manual ingestion endpoint

- Split the shared NOT_REQUIRED message: "case not eligible" now gets its
  own accurate message instead of reusing the "no newer IDPC version
  available, Answers already exists" text, which was misleading when the
  case was never found or has no defendants.
- Strip CTX_SYNCHRONOUS_INVOCATION_KEY from job data before it reaches the
  dispatched RETRIEVE_MATERIAL_AND_UPLOAD job, so the sync-only flag never
  rides into the async job queue.
- Add a test asserting HIGH priority actually propagates through to the
  dispatched retrieval job (the only point priority takes effect), and that
  the synchronous-invocation flag is stripped from its job data.

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

* DD-42807 - move eligibility/IDPC-availability rules into plain service classes

Per review comment: reverts CheckCaseEligibilityTask and
CheckIdpcAvailabilityAllDefendantsTask to thin JobManager task wrappers, and
moves the reusable business logic into CaseEligibilityService and
IdpcAvailabilityService respectively. Both the async scheduled flow (via the
task classes) and the synchronous manual "Process IDPC" flow (via
IngestionProcessorByCaseService) now call these services directly as plain
methods, rather than IngestionProcessorByCaseService invoking
CheckCaseEligibilityTask.execute(ExecutionInfo) through the task-manager
contract.

This removes the need for CTX_SYNCHRONOUS_INVOCATION_KEY entirely: since
IngestionProcessorByCaseService never touches the task classes' execute()
methods, there is no risk of the async job queue being triggered twice for
the same case, so the flag (and its associated stripping-before-dispatch
logic) is deleted.

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

* DD-42807 - drop redundant case-eligibility step, share retrieval job-data building

CaseEligibilityService/CheckCaseEligibilityTask added a round-trip that
duplicated what IdpcAvailabilityService already establishes: an empty
document list means nothing to ingest, whether that's because the case
doesn't exist, has no defendants, or is already up to date. Both the
scheduled flow (GetCasesForHearingTask now dispatches
CHECK_IDPC_AVAILABILITY_ALL_DEFENDANTS directly) and the manual by-case
flow (IngestionProcessorByCaseService) call IdpcAvailabilityService
directly instead.

Extracted RetrieveMaterialAndUploadJobDataService to build the
per-document RETRIEVE_MATERIAL_AND_UPLOAD job data once, shared by both
call sites, instead of duplicating that logic in two private dispatch
methods.

Removed the now-dead prosecution-case-eligibility client method, its
DTOs and config, and the wiremock fixtures backing it; added a
replacement failure stub on the court-document-search endpoint so the
by-case FAILED integration scenario still exercises a real error path.

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

* DD-42807 - inline user-id header lookup in startIngestionProcessByCase

The intermediate headerName variable held the header's name, not the
user id, which read as mislabeled per review. Inlining the lookup
removes the confusion entirely — cppuid is the only variable left, and
it holds the actual value.

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

---------

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* DD-42938 - Update nightly schedule to get courtcentre and case details using /hearing-cases-for-day (#196)

* get hearing cases for day

* passing integration tests

* Updated integration test in NightlyDiscoverySchedulerLiveTes to assert CHECK_CASE_ELIGIBILITY task executed

* Dedupe cases across the hearing days

* removed static reference from the method

* updated HearingClientImpl and response dto as per the response - returns list of caseIds

* rebase with develop; discovery service now dispatches task CHECK_IDPC_AVAILABILITY_ALL_DEFENDANTS

* Add a toggle to the /usage/hearing-cases-for-day endpoint until this is available (#198)

* enable feature toggle to hit new hearing endpoint (#199)

* added env var CP_CDK_SCHEDULER_NIGHTLY_DISCOVERY_CRON for nightly discovery cron (#200)

* Check for an existing answer before reporting NOT_REQUIRED for by-case ingestion (#202)

startIngestionProcess in IngestionProcessorByCaseService returned
IngestionProcessPhase.NOT_REQUIRED whenever no newer IDPC document was
found, even when no answer had ever been generated for the case (e.g.
answer generation failed after a previous ingestion). It now also
checks CaseQueryStatusRepository for an ANSWER_AVAILABLE entry: only
returns NOT_REQUIRED when an answer already exists, otherwise returns
STARTED with a distinct message and dispatches nothing new.

Fixes a latent bug surfaced by this change: CaseQueryStatusRepository's
findByCaseId/findByCaseIdAndQueryId were broken derived queries that
resolved against the entity's plain caseId getter instead of the
embedded id, throwing UnknownPathException the first time they were
actually invoked. Both now use explicit JPQL through
caseQueryStatusId.

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Bump api-cp-ai-rag contract dependency to 0.0.15 (#203)

Verified the only contract change between 0.0.12 and 0.0.15 is removal
of the unused DocumentStatusNotAvailable model; every API and model
class actually used by this service is unchanged. Unit and integration
tests pass against the new version.

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* externalise nightly scheduler lock config (#204)

* Scope by-case ingestion answer check to the latest IDPC document (#205)

* Scope hasAnswerAvailable check to the case's latest IDPC document

Previously any ANSWER_AVAILABLE case_query_status row for a case (regardless
of which document it was generated against) satisfied the check, so a stale
answer from a superseded IDPC version could wrongly report NOT_REQUIRED.
Now resolves the case's latest doc_id (case_documents, ordered by
uploaded_at) and requires at least one answer recorded against that doc_id.

Also adds a caseId/cppuid info log to CheckIdpcAvailabilityAllDefendantsTask
for traceability.

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

* Combine hasAnswerAvailable lookup into a single DB round trip

Address review comment: resolving the latest doc_id and checking its
answer status were two separate repository calls. Replaced with one native
query (CaseQueryStatusRepository#existsAnswerAvailableForLatestDoc) that
resolves the latest doc_id as a correlated subquery and checks it via
EXISTS in a single call, removing the now-unused CaseDocumentRepository
dependency from IngestionProcessorByCaseService.

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

---------

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: ravi kakumanu <ravik@ravis-MacBook-Pro-2.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Natraj Molala <natraj.molala3@hmcts.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-approved Claude pre-review passed; awaiting human approval reviewed-by-claude Automated pre-review by Claude complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants