Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ __pycache__/
.codegraph/
.env
.coverage

# Local agent worktree scratch (never commit)
.worktrees/
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ and are not a leftover score. Complete-case coverage (ADR 0168) persists to
`report_leftover_map_coverage` and captions the pair list with how
many scored posts entered the map.

Authorized occupational construct catalog search (ADR 0257) matches official
O*NET preferred labels or descriptions only when a source-eligible, ABAC-visible
Post supports that construct. Hidden Posts, withdrawn truth, and conflicting
truth statuses omit the hit. Clicking a hit opens that Post. Do not return
catalog rows as a vocabulary oracle, scores, or person traits. Continuation
is a construct-IRI keyset; never OFFSET.
Comment on lines +295 to +300

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 AGENTS.md cites the wrong ADR for catalog search

This paragraph attributes catalog search to ADR 0257, which is the accepted occupation-rating observation store. The governing decision is ADR 0265.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


Global Ask relative-time filters (ADR 0150 / 0202) bind to
`source_post.event_occurred_at` and fall back to `created_at` only
when the event instant is missing. Cited evidence names **Time
Expand Down
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ flowchart LR
| `backend/app/occupation_rating_ingestion.py` | Projects authenticated occupation-rating evidence plus persisted source and represented-occupation catalogs (ADR 0258, ADR 0260, ADR 0261) |
| `frontend/src/components/OccupationRatingProfile.tsx` | Selects an imported source, filters stored occupation titles without ranking, and reads exact Dashboard evidence while preserving absence, uncertainty, and warning semantics (ADR 0259–0262) |
| `ontology_neighborhood.py` | Bounded typed ontology/provenance neighborhood (ADR 0184); PostgreSQL stays authoritative, OWL subclass is not an instance edge |
| `occupational_construct_catalog.py` | Official O*NET 31.0 construct catalog sync (ADR 0250); no ratings or invented IRIs |
| `backend/app/occupational_construct_search.py` | Authorized catalog-label search over assertion-backed constructs (ADR 0257); hidden Posts never mint a hit |
| `ontology_source_cursor.py` | Opaque HMAC source-window continuation (ADR 0124); keyset pagination, never OFFSET |
| `period_report.py` | Fit GRM/GPCM on persisted IRT rows, FIPC-select, EAP-score a period (ADR 0003 slice 3; Bock & Mislevy, 1982) |
| `fixtures.py` | Synthetic demo dataset -- no real data ships in this repo |
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.d/2.20.0-occupational-construct-catalog-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 2.20.0 — Authorized occupational construct catalog search

- Reviewers can search official O*NET cognitive-ability, work-style, and
work-activity labels from the ontology explorer and open the earliest
visible supporting record (ADR 0257).
- Hits require source-eligible, ABAC-visible assertion evidence. Hidden
Posts, withdrawn truth, and conflicting truth statuses stay omitted.
- Continuation uses a construct-IRI keyset. OFFSET, scores, person traits,
and catalog-only oracles remain unavailable.
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover

Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233), the text-channel embedding swap and cosine
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233), occupational construct catalog search
(ADR 0257), the text-channel embedding swap and cosine
Comment on lines +52 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 CLAUDE.md cites the wrong ADR for catalog search

The pointer attributes occupational construct catalog search to ADR 0257, the accepted occupation-rating observation store. The feature is ADR 0265.

Suggested change
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233), occupational construct catalog search
(ADR 0257), the text-channel embedding swap and cosine
(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233), occupational construct catalog search
(ADR 0265), the text-channel embedding swap and cosine
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

clamp (ADR 0190), per-edge channel-score persistence (ADR 0195),
migration replay (ADR 0166), docstring coverage, and the measurement
boundary are all stated in [AGENTS.md](AGENTS.md) -- read it before
Expand Down
44 changes: 44 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@
from backend.app.occupational_construct_ingestion import (
load_occupational_construct_assertions,
)
from backend.app.occupational_construct_search import (
OccupationalConstructSearchError,
occupational_construct_search_error_detail,
occupational_construct_search_http_status,
search_page_to_payload,
search_visible_occupational_constructs,
)
from backend.app.post_content_worker import run_post_content_worker
from backend.app.post_eligibility import SOURCE_POST_ELIGIBILITY_SQL, source_post_visible
from backend.app.post_evaluation_ingestion import (
Expand Down Expand Up @@ -2545,6 +2552,43 @@ async def read_rating_source_occupations(
)


@app.get("/api/occupational-constructs/search")
async def search_occupational_constructs(
q: str = Query(..., min_length=1),
family: str | None = Query(None),
knowledge_cutoff: str | None = Query(None),
cursor: str | None = Query(None),
limit: int | None = Query(None),
account: CurrentAccount = Depends(get_current_account),
pool: asyncpg.Pool = Depends(get_pool),
) -> dict[str, Any]:
"""Assertion-backed catalog matches the reviewer may already open."""
_require_post_read(account)
cutoff_clock = None
if knowledge_cutoff:
try:
cutoff_clock = parse_as_of_clock(knowledge_cutoff)
except ValueError as exc:
raise HTTPException(status.HTTP_422_UNPROCESSABLE_CONTENT, str(exc)) from exc
try:
async with pool.acquire() as conn:
page = await search_visible_occupational_constructs(
conn,
query=q,
family_code=family,
knowledge_cutoff=cutoff_clock,
cursor=cursor,
limit=limit,
can_see_post=lambda row: _can_see_post(account, row),
)
except OccupationalConstructSearchError as exc:
raise HTTPException(
occupational_construct_search_http_status(exc),
occupational_construct_search_error_detail(exc),
) from None
return search_page_to_payload(page)


@app.get("/api/posts/{post_id}/counterparties")
async def read_post_counterparties(
post_id: str,
Expand Down
Loading
Loading