Skip to content

feat(server): expose practice-area standing, trend and review history - #1486

Draft
ValentinGruener wants to merge 8 commits into
mainfrom
feat/practice-area-server
Draft

feat(server): expose practice-area standing, trend and review history#1486
ValentinGruener wants to merge 8 commits into
mainfrom
feat/practice-area-server

Conversation

@ValentinGruener

@ValentinGruener ValentinGruener commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the API a developer profile needs to explain a practice area: its current standing and guidance, the direction its recent evidence points, the feedback sources behind it, and the review moments that produced it. Delivered feedback can be marked helpful or not helpful.

Trend direction is evidence-weighted and opportunity-indexed rather than time-boxed, so a practice recovers after two problem-free reviews instead of staying marked mixed for the rest of the 90-day window.

Bottom of stack #1488. #1487 builds the profile surfaces on this API. Nothing here is reachable from the UI yet — that is deliberate, so this half can be reviewed on its own.

This replaces the abandoned #1448, which carried the whole feature as one 49-file PR against a main it had drifted 20 commits behind.

Why the generated client is in this PR

webapp/src/api/** ships with openapi.yaml rather than with the UI layer above. The openapi-validation gate regenerates both and fails on any drift, so splitting them would leave this PR red by construction.

How to test

CI covers the server side. To exercise the endpoints by hand:

GET /workspaces/{slug}/practice-areas/status
GET /workspaces/{slug}/practice-areas/{areaSlug}/trend
GET /workspaces/{slug}/practice-areas/{areaSlug}/review-history

Checklist

  • My changeset summary reads as an operator/user-facing note (it becomes the changelog entry) — see .changeset/README.md
  • If the operator must act on this change (new required env var, manual migration step), the changeset summary says how (**Operators:** …) and MIGRATION.md is updated

Three changesets ride along: the profile API, evidence-based trends, and the two-clean-reviews recovery rule. The Liquibase changelog runs automatically; no operator action.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation application-server Spring Boot server: APIs, business logic, database feature New feature or enhancement dependencies Package updates, version bumps, lock file changes webapp React app: UI components, routes, state management size:XXL This PR changes 1000+ lines, ignoring generated files. labels Aug 22, 2026
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

📚 Documentation Preview

This PR includes documentation changes. A preview has been deployed:

🔗 View Docs Preview

Preview for commit 1c14fee. Updates automatically on new commits.

Valentin Grüner and others added 8 commits August 25, 2026 11:20
Adds the API a developer profile needs to explain a practice area: its
current standing and guidance, the direction its recent evidence points,
the feedback sources behind it, and the review moments that produced it.

Trend direction is evidence-weighted and opportunity-indexed rather than
time-boxed, so a practice recovers after two problem-free reviews instead
of staying marked mixed for the rest of the window.

Delivered feedback can be marked helpful or not helpful.

Includes the regenerated OpenAPI spec and client, which must ship with the
API change: the openapi-validation gate regenerates both and fails on drift.
Both levels now answer the same question — what share of the evidence was
positive — and read the answer off the same scale, so an area can no longer
contradict the cards it is built from.

A practice standing is the recency-weighted share of positive outcomes across
its four newest evidence opportunities. That replaces a pair of rules that
disagreed about both unit and denominator: an existence test over items that
could not tell one problem from fifty, plus a clean-streak override that could.
The decay is derived rather than picked, so two problem-free work items in a row
still acknowledge a fixed habit — and a fresh regression registers just as fast.

An area averages the continuous practice standings and classifies once, instead
of rounding each practice to a label and averaging those.

A practice can now say why it has nothing to say: NOT_OBSERVED when no review
reached it, NO_OPPORTUNITY when the reviews ran and found nothing to report.
Carrying that on the practice lets the area be a pure roll-up and removes the
separate evidence census that existed only to compensate.

Also fixes the latest-run correlation on the learner surfaces: it now matches on
practice and subject, not the artifact alone, so a review that never examined a
practice no longer supersedes what an earlier one found there. The mentor
aggregates keep the old grain deliberately and say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
An area summary treats every practice as equally important, which is rarely
true: a team that cares most about test coverage cannot say so. Practices now
carry an area weight that applies to both halves of the area view — the standing
and the direction — so an area reads as the practices its workspace actually
weighs.

The trend already accepted per-practice weights and folded them into its
inverse-variance precision; both call sites passed an empty map. They now pass
the weights from the reflection snapshot, so the area status and the trend
detail cannot disagree about how much a practice counts. The area standing moves
from a plain average to the weighted one.

Weight 0 withdraws a practice from its area's summary while leaving it fully
reviewed and fully visible: not counting toward the area is a different
statement from not being looked at, and the autonomy ladder already makes the
latter. The default is neutral, so an unconfigured workspace is unaffected.

Nothing about a practice's own card changes — a practice is never judged more
or less harshly for being weighted, only listened to more or less when its area
speaks for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
The package was called reviewhistory but held both halves of the area detail
page: the review history AND the trend query service behind
/practice-areas/{areaSlug}/trend. Its own controller already told the truth —
PracticeAreaDetailController — so the package name was the odd one out, and a
reader opening reviewhistory had no reason to expect a trend service there.

Renamed to areadetail, with a package-info that records where the boundary
runs: the trend QUERY lives here because it answers for a screen, while the
trend MATHEMATICS stays in practices.observation.trend. Nothing outside the
package references any of these types, so the move is closed under the compiler
and no module boundary shifts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
The rebase conflicted on openapi.yaml and the generated client, which are
regenerated rather than merged. Resolving them to this branch's side dropped
main's catalog-adoption endpoints and the deletePractices parameter; running
both generators against the merged Java restores them alongside this branch's
own additions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
Three states were measured against the evidence this surface actually has and
removed, and one boundary was moved to where the meaning changes.

STABLE is gone. Claiming equivalence is a stronger claim than claiming
difference: the posterior mass inside the practical-equivalence band has to
clear the credibility threshold, which at four opportunities a bundle peaks
around 0.70 and cannot. Reaching it needs roughly 12 opportunities a bundle for
a practice at the extremes and 58 for one in the middle — against the four
available. UNCERTAIN already carries the case honestly: no direction is
supported, which is not the same as asserting there is none.

SupportLevel is gone with it. Once both bundles must be full for a comparison
to happen at all, it has one reachable value; and across all twenty
combinations reachable at this bundle size, a three-item previous bundle never
once changed a verdict against a four-item one, because the posterior already
widens for the smaller sample. TrendSupport keeps its counts, which say the
same thing without grading it.

minBundleSize therefore rises to the bundle size, and the rule states in one
line: the last four against the four before them.

The standing's lower boundary moves from one half to 0.37, which is where the
meaning changes rather than where the number is round. One problem on the
newest work item, everything before it clean, scores 0.384; two in a row score
0.138. A boundary at one half put both on the same side, so for a developer
working at an 80% success rate one review in five read as "needs attention"
while the trend beside it stayed silent — correctly, since a single item is no
evidence of change. Simulated over 20000 runs that fired 19.6% of the time; it
now fires 9.6%, and at a 90% success rate 2.8% rather than 10.1%. The scale is
symmetric again in the unit it reasons about: two clean opportunities earn a
strength, two problems cost one, and a single event of either kind moves the
standing without settling it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
…ody uses

Rebasing onto main surfaced a break git could not: main removed the ArtifactKind
import from ObservationService while this branch still reads the type at the
artifact-kind filter. Both sides touched different lines of the same file, so the
merge was clean and the compile was not — which is why it only appeared in CI,
where the merge with main is what gets built.

The twenty unused imports either side left behind now fail the build rather than
warn, after main made the Java lint reject violations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XBuU2tfPFntHoE34Cmw1Z
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

application-server Spring Boot server: APIs, business logic, database dependencies Package updates, version bumps, lock file changes documentation Improvements or additions to documentation feature New feature or enhancement size:XXL This PR changes 1000+ lines, ignoring generated files. webapp React app: UI components, routes, state management

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant