Conversation
There was a problem hiding this comment.
Pull request overview
Adds support for recording an AI-generated content percentage on applications, including schema changes, a new admin API route to set the value (only once and only by the assigned admin), and UI updates to display/edit the value in the admin review experience.
Changes:
- Add
ai_percentagecolumn (SMALLINT) toapplications, update seed data, and propagate the field through application query/response shapes. - Add admin PUT endpoint
/admin/applications/{applicationID}/aiPercentand Swagger documentation for setting the AI percentage. - Update admin UI to show AI percentage in assigned-application details and in the all-applicants table; add client API call to set it.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/store/storage.go | Extends ApplicationReviews storage interface with SetAIPercentage. |
| internal/store/reviews.go | Implements SetAIPercentage update guarded by assignment and “only-once” semantics. |
| internal/store/mock_store.go | Adds mock implementation for SetAIPercentage. |
| internal/store/applications.go | Adds ai_percentage to application/list queries and introduces (buggy) ApplicationsStore.SetAIPercentage plus extra DTO types. |
| internal/db/seed.go | Updates application seed insert to include ai_percentage as NULL. |
| docs/docs.go | Adds Swagger path + schema definitions for the new endpoint and field. |
| cmd/migrate/migrations/000013_add_ai_percentage.up.sql | Adds ai_percentage column to applications. |
| cmd/migrate/migrations/000013_add_ai_percentage.down.sql | Drops ai_percentage column. |
| cmd/api/reviews.go | Adds request/response DTOs and handler for setting AI percentage. |
| cmd/api/api.go | Mounts the new admin route. |
| client/web/src/types.ts | Extends Application type with ai_percentage. |
| client/web/src/pages/admin/assigned/components/ApplicationDetailsPanel.tsx | Adds AI percentage display/edit UI (currently nested incorrectly). |
| client/web/src/pages/admin/assigned/api.ts | Adds setAIPercentage client call (and contains an existing notes-route mismatch). |
| client/web/src/pages/admin/assigned/AssignedPage.tsx | Wires UI callback to update local application detail state. |
| client/web/src/pages/admin/all-applicants/types.ts | Extends list item type with ai_percentage. |
| client/web/src/pages/admin/all-applicants/components/ApplicationsTable.tsx | Adds table column to display AI percentage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| async function saveEditing() { | ||
| const percentage = Number(inputValue); |
There was a problem hiding this comment.
saveEditing() uses Number(inputValue), which treats an empty string as 0 and can produce NaN for invalid input. This can lead to accidentally saving 0% or sending invalid JSON (NaN becomes null). Add client-side validation (required, integer, 0–100) before calling the API.
| const percentage = Number(inputValue); | |
| const trimmed = inputValue.trim(); | |
| if (trimmed === '') { | |
| toast.error('AI percentage is required'); | |
| return; | |
| } | |
| const percentage = Number(trimmed); | |
| if (!Number.isInteger(percentage) || percentage < 0 || percentage > 100) { | |
| toast.error('AI percentage must be an integer between 0 and 100'); | |
| return; | |
| } |
cmd/api/reviews.go
Outdated
| applicationID := chi.URLParam(r, "applicationID") | ||
|
|
||
| if applicationID == "" { | ||
| app.badRequestResponse(w, r, errors.New("Application ID is required")) |
There was a problem hiding this comment.
The error message for a missing applicationID uses different capitalization than other handlers (e.g., "Application ID is required" vs "application ID is required"). For consistency in API error messages, align this with the existing style used elsewhere in cmd/api/reviews.go/cmd/api/applications.go.
| app.badRequestResponse(w, r, errors.New("Application ID is required")) | |
| app.badRequestResponse(w, r, errors.New("application ID is required")) |
| if (res.error === "not found") { | ||
| return {success: false, error: "You do not have permission to change this review's percentage, only the assigned admin can"} | ||
| } | ||
| else{ | ||
| return { success: false, error: res.error || 'Failed to set AI Percentage' }; | ||
| } |
There was a problem hiding this comment.
This special-cases res.error === "not found" as a permission error, but the backend returns the same "not found" payload for multiple conditions (missing application, not assigned, already set). This message will be inaccurate in some cases. Consider distinguishing these cases via status codes (e.g., 403/409) or using a more generic message.
| if (res.error === "not found") { | |
| return {success: false, error: "You do not have permission to change this review's percentage, only the assigned admin can"} | |
| } | |
| else{ | |
| return { success: false, error: res.error || 'Failed to set AI Percentage' }; | |
| } | |
| return { success: false, error: res.error || "Unable to set AI percentage for this application." }; |
internal/store/applications.go
Outdated
| SELECT 1 from application_reviews | ||
| where application_id = $2 | ||
| and admin_id = $3 | ||
| ) | ||
| ` | ||
|
|
||
| result, err := s.db.ExecContext(ctx, query, percentage, applicationID, adminID) |
There was a problem hiding this comment.
ApplicationsStore.SetAIPercentage uses $3 for ai_percentage here, but later in the EXISTS subquery $3 is used for admin_id. This placeholder reuse will make the update incorrect or fail if this method is invoked. Use distinct placeholders for (applicationID, adminID, percentage).
| SELECT 1 from application_reviews | |
| where application_id = $2 | |
| and admin_id = $3 | |
| ) | |
| ` | |
| result, err := s.db.ExecContext(ctx, query, percentage, applicationID, adminID) | |
| SELECT 1 FROM application_reviews | |
| WHERE application_id = $1 | |
| AND admin_id = $2 | |
| ) | |
| ` | |
| result, err := s.db.ExecContext(ctx, query, applicationID, adminID, percentage) |
internal/store/applications.go
Outdated
| WaitlistVotes int `json:"waitlist_votes"` | ||
| ReviewsAssigned int `json:"reviews_assigned"` | ||
| ReviewsCompleted int `json:"reviews_completed"` | ||
| AIPercentage *int `json:"ai_percentage"` |
There was a problem hiding this comment.
ApplicationListItem.AIPercentage is typed as *int, but the DB column is SMALLINT and Application.AIPercentage is *int16. This inconsistency can lead to unnecessary type conversions and makes it harder to reason about the API schema; consider making this *int16 for consistency with the underlying column and the full Application type.
| AIPercentage *int `json:"ai_percentage"` | |
| AIPercentage *int16 `json:"ai_percentage"` |
| {/* Short Answers */} | ||
| {application.short_answer_questions?.length > 0 && ( | ||
| <div> |
There was a problem hiding this comment.
The AI Percentage section is rendered inside this Short Answers conditional block. As a result, applications without short-answer questions will never show the AI percentage UI. Move the AI percentage section outside of this conditional.
| return getRequest<NotesListResponse>( | ||
| `/admin/reviews/applications/${applicationId}/notes`, | ||
| "review notes" |
There was a problem hiding this comment.
The backend route for notes is /admin/applications/{applicationID}/notes (see cmd/api/api.go), but this client calls /admin/reviews/applications/{applicationId}/notes. This will 404 if used; update the endpoint path to match the server.
There was a problem hiding this comment.
Copliot onto something here ^
| @@ -0,0 +1,2 @@ | |||
| ALTER TABLE applications | |||
| ADD COLUMN ai_percentage SMALLINT DEFAULT NULL; | |||
There was a problem hiding this comment.
The migration adds ai_percentage without a DB-level constraint to enforce the documented 0–100 range. Existing migrations use CHECK constraints for similar validation (e.g., applications_age_check in 000004_create_applications.up.sql). Consider adding a CHECK constraint (or domain) so invalid values cannot be inserted outside the API validation path.
| ADD COLUMN ai_percentage SMALLINT DEFAULT NULL; | |
| ADD COLUMN ai_percentage SMALLINT DEFAULT NULL; | |
| ALTER TABLE applications | |
| ADD CONSTRAINT applications_ai_percentage_check | |
| CHECK (ai_percentage IS NULL OR (ai_percentage >= 0 AND ai_percentage <= 100)); |
internal/store/applications.go
Outdated
| } | ||
|
|
||
| type SetAIPercentagePayload struct { | ||
| AIPercentage int16 `json:"ai_percentage" validate:"required,min=0, max = 100"` |
There was a problem hiding this comment.
The SetAIPercentagePayload/AIPercentageResponse types here appear unused (the API layer defines these in cmd/api/reviews.go, and Swagger references main.*). Also, the validator tag includes spaces (min=0, max = 100) which go-playground/validator will not parse as intended. Consider removing these store-level DTOs or consolidating to a single canonical definition with a correct tag.
| AIPercentage int16 `json:"ai_percentage" validate:"required,min=0, max = 100"` | |
| AIPercentage int16 `json:"ai_percentage" validate:"required,min=0,max=100"` |
internal/store/applications.go
Outdated
| where application_id = $2 | ||
| and admin_id = $3 | ||
| ) | ||
| ` | ||
|
|
||
| result, err := s.db.ExecContext(ctx, query, percentage, applicationID, adminID) |
There was a problem hiding this comment.
The ExecContext argument order doesn’t match the SQL placeholders (WHERE id = $1 ...), since it currently passes (percentage, applicationID, adminID). Fix the argument order to align with the query (applicationID/adminID/percentage), or remove this method if ApplicationReviewsStore.SetAIPercentage is the intended implementation.
| where application_id = $2 | |
| and admin_id = $3 | |
| ) | |
| ` | |
| result, err := s.db.ExecContext(ctx, query, percentage, applicationID, adminID) | |
| where application_id = $1 | |
| and admin_id = $2 | |
| ) | |
| ` | |
| result, err := s.db.ExecContext(ctx, query, applicationID, adminID, percentage) |
There was a problem hiding this comment.
Lowkey these copilot comments are spot on.
cmd/api/api.go
Outdated
| r.Put("/reviews/{reviewID}", app.submitVote) | ||
| r.Get("/reviews/completed", app.getCompletedReviews) | ||
|
|
||
| r.Put("/applications/{applicationID}/aiPercent", app.setAIPercentage) |
There was a problem hiding this comment.
change this to
r.Put("/applications/{applicationID}/ai-percent", app.setAIPercentage)
once you do that, update the routes in the frontend
| type AIPercentageResponse struct { | ||
| AIPercentage int16 `json:"ai_percentage"` | ||
| } | ||
|
|
There was a problem hiding this comment.
can SetAIPercentagePaylod and AIPercentage response not use the same structure? You can rename it to AIPercentage and load or get depending on the endpoint.
There was a problem hiding this comment.
2 seperate for reponse and request is fine, it's like that everywhere else plus response may change in the future.
internal/store/applications.go
Outdated
| return emails, nil | ||
| } | ||
|
|
||
| func (s *ApplicationsStore) SetAIPercentage(ctx context.Context, applicationID string, adminID string, percentage int16) error { |
There was a problem hiding this comment.
Where are there 2 SetAIPercentage 😭
There was a problem hiding this comment.
uhhh wtf i dont remeber puttin 2 😭
internal/store/reviews.go
Outdated
| } | ||
|
|
||
| // SetAIPercentage sets the AI-generated percentage on an application, only if the admin is assigned to it and it hasn't been set yet. | ||
| func (s *ApplicationReviewsStore) SetAIPercentage(ctx context.Context, applicationID string, adminID string, percentage int16) error { |
There was a problem hiding this comment.
Where are there 2 SetAIPercentage 😭
There was a problem hiding this comment.
Also can we change the naming to SetAIPercent I don't like percentage rolls off my tongue :) lol But also I think I told you to do Ai but keep it AI.
There was a problem hiding this comment.
Like change all percentage --> percent
was learning codebase and forgot to delete the 2nd one
Created new api route, added new migration to DB (up and down) for new field, handled backend error handling, created front end ui, made it editable only by the assigned admin and handled what happens when someone else tries to mess with it, once it is set field is no longer updatable on both front and backend