From 5a47bc87d8ed06c92c7848b60a3cd9a636316be9 Mon Sep 17 00:00:00 2001 From: egg Date: Thu, 12 Feb 2026 16:53:54 +0800 Subject: [PATCH] fix(sql): remove colon prefix from SQL comments to prevent bind param errors, archive trace-progressive-ui SQLAlchemy text() parses :param patterns in SQL comments as bind parameters. When EventFetcher replaces the WHERE clause via string substitution, orphaned :container_id in comments causes "A value is required for bind parameter 'container_id'" errors. Changes: - Remove colon prefix from parameter names in SQL comments for lot_history, lot_rejects, lot_holds, lot_materials - Archive trace-progressive-ui change (22/22 tasks complete) - Sync delta specs to main: add trace-staged-api, progressive-trace-ux, merge api-safety-hygiene (+2 requirements) Co-Authored-By: Claude Opus 4.6 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/api-safety-hygiene/spec.md | 0 .../specs/progressive-trace-ux/spec.md | 0 .../specs/trace-staged-api/spec.md | 0 .../2026-02-12-trace-progressive-ui}/tasks.md | 0 openspec/specs/api-safety-hygiene/spec.md | 24 +++++ openspec/specs/progressive-trace-ux/spec.md | 64 +++++++++++++ openspec/specs/trace-staged-api/spec.md | 89 +++++++++++++++++++ .../sql/query_tool/lot_history.sql | 4 +- .../sql/query_tool/lot_holds.sql | 2 +- .../sql/query_tool/lot_materials.sql | 2 +- .../sql/query_tool/lot_rejects.sql | 2 +- 14 files changed, 182 insertions(+), 5 deletions(-) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/.openspec.yaml (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/design.md (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/proposal.md (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/specs/api-safety-hygiene/spec.md (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/specs/progressive-trace-ux/spec.md (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/specs/trace-staged-api/spec.md (100%) rename openspec/changes/{trace-progressive-ui => archive/2026-02-12-trace-progressive-ui}/tasks.md (100%) create mode 100644 openspec/specs/progressive-trace-ux/spec.md create mode 100644 openspec/specs/trace-staged-api/spec.md diff --git a/openspec/changes/trace-progressive-ui/.openspec.yaml b/openspec/changes/archive/2026-02-12-trace-progressive-ui/.openspec.yaml similarity index 100% rename from openspec/changes/trace-progressive-ui/.openspec.yaml rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/.openspec.yaml diff --git a/openspec/changes/trace-progressive-ui/design.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/design.md similarity index 100% rename from openspec/changes/trace-progressive-ui/design.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/design.md diff --git a/openspec/changes/trace-progressive-ui/proposal.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/proposal.md similarity index 100% rename from openspec/changes/trace-progressive-ui/proposal.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/proposal.md diff --git a/openspec/changes/trace-progressive-ui/specs/api-safety-hygiene/spec.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/api-safety-hygiene/spec.md similarity index 100% rename from openspec/changes/trace-progressive-ui/specs/api-safety-hygiene/spec.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/api-safety-hygiene/spec.md diff --git a/openspec/changes/trace-progressive-ui/specs/progressive-trace-ux/spec.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/progressive-trace-ux/spec.md similarity index 100% rename from openspec/changes/trace-progressive-ui/specs/progressive-trace-ux/spec.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/progressive-trace-ux/spec.md diff --git a/openspec/changes/trace-progressive-ui/specs/trace-staged-api/spec.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/trace-staged-api/spec.md similarity index 100% rename from openspec/changes/trace-progressive-ui/specs/trace-staged-api/spec.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/specs/trace-staged-api/spec.md diff --git a/openspec/changes/trace-progressive-ui/tasks.md b/openspec/changes/archive/2026-02-12-trace-progressive-ui/tasks.md similarity index 100% rename from openspec/changes/trace-progressive-ui/tasks.md rename to openspec/changes/archive/2026-02-12-trace-progressive-ui/tasks.md diff --git a/openspec/specs/api-safety-hygiene/spec.md b/openspec/specs/api-safety-hygiene/spec.md index d1c933e..0007a53 100644 --- a/openspec/specs/api-safety-hygiene/spec.md +++ b/openspec/specs/api-safety-hygiene/spec.md @@ -103,3 +103,27 @@ Route and service test files SHALL exist and cover core behaviors. - **WHEN** pytest discovers tests - **THEN** `tests/test_mid_section_defect_service.py` SHALL contain tests for date validation, pagination logic, and loss reasons caching +### Requirement: Staged trace API endpoints SHALL apply rate limiting +The `/api/trace/seed-resolve`, `/api/trace/lineage`, and `/api/trace/events` endpoints SHALL apply per-client rate limiting using the existing `configured_rate_limit` mechanism. + +#### Scenario: Seed-resolve rate limit exceeded +- **WHEN** a client sends more than 10 requests to `/api/trace/seed-resolve` within 60 seconds +- **THEN** the endpoint SHALL return HTTP 429 with a `Retry-After` header + +#### Scenario: Lineage rate limit exceeded +- **WHEN** a client sends more than 10 requests to `/api/trace/lineage` within 60 seconds +- **THEN** the endpoint SHALL return HTTP 429 with a `Retry-After` header + +#### Scenario: Events rate limit exceeded +- **WHEN** a client sends more than 15 requests to `/api/trace/events` within 60 seconds +- **THEN** the endpoint SHALL return HTTP 429 with a `Retry-After` header + +### Requirement: Mid-section defect analysis endpoint SHALL internally use staged pipeline +The existing `/api/mid-section-defect/analysis` endpoint SHALL internally delegate to the staged trace pipeline while maintaining full backward compatibility. + +#### Scenario: Analysis endpoint backward compatibility +- **WHEN** a client calls `GET /api/mid-section-defect/analysis` with existing query parameters +- **THEN** the response JSON structure SHALL be identical to pre-refactoring output +- **THEN** existing rate limiting (6/min analysis, 15/min detail, 3/min export) SHALL remain unchanged +- **THEN** existing distributed lock behavior SHALL remain unchanged + diff --git a/openspec/specs/progressive-trace-ux/spec.md b/openspec/specs/progressive-trace-ux/spec.md new file mode 100644 index 0000000..553e66b --- /dev/null +++ b/openspec/specs/progressive-trace-ux/spec.md @@ -0,0 +1,64 @@ +## ADDED Requirements + +### Requirement: useTraceProgress composable SHALL orchestrate staged fetching with reactive state +`useTraceProgress` SHALL provide a shared composable for sequential stage fetching with per-stage reactive state updates. + +#### Scenario: Normal three-stage fetch sequence +- **WHEN** `useTraceProgress` is invoked with profile and params +- **THEN** it SHALL execute seed-resolve → lineage → events sequentially +- **THEN** after each stage completes, `current_stage` and `completed_stages` reactive refs SHALL update immediately +- **THEN** `stage_results` SHALL accumulate results from completed stages + +#### Scenario: Stage failure does not block completed results +- **WHEN** the lineage stage fails after seed-resolve has completed +- **THEN** seed-resolve results SHALL remain visible and accessible +- **THEN** the error SHALL be captured in stage-specific error state +- **THEN** subsequent stages (events) SHALL NOT execute + +### Requirement: mid-section-defect SHALL render progressively as stages complete +The mid-section-defect page SHALL display partial results as each trace stage completes. + +#### Scenario: Seed lots visible before lineage completes +- **WHEN** seed-resolve stage completes (≤3s for ≥10 seed lots) +- **THEN** the seed lots list SHALL be rendered immediately +- **THEN** lineage and events sections SHALL show skeleton placeholders + +#### Scenario: KPI/charts visible after events complete +- **WHEN** lineage and events stages complete +- **THEN** KPI cards and charts SHALL render with fade-in animation +- **THEN** no layout shift SHALL occur (skeleton placeholders SHALL have matching dimensions) + +#### Scenario: Detail table pagination unchanged +- **WHEN** the user requests detail data +- **THEN** the existing detail endpoint with pagination SHALL be used (not the staged API) + +### Requirement: query-tool lineage tab SHALL load on-demand +The query-tool lineage tab SHALL load lineage data for individual lots on user interaction, not batch-load all lots. + +#### Scenario: User clicks a lot to view lineage +- **WHEN** the user clicks a lot card to expand lineage information +- **THEN** lineage SHALL be fetched via `/api/trace/lineage` for that single lot's container IDs +- **THEN** response time SHALL be ≤3s for the individual lot + +#### Scenario: Multiple lots expanded +- **WHEN** the user expands lineage for multiple lots +- **THEN** each lot's lineage SHALL be fetched independently (not batch) +- **THEN** already-fetched lineage data SHALL be preserved (not re-fetched) + +### Requirement: Both pages SHALL display a stage progress indicator +Both mid-section-defect and query-tool SHALL display a progress indicator showing the current trace stage. + +#### Scenario: Progress indicator during staged fetch +- **WHEN** a trace query is in progress +- **THEN** a progress indicator SHALL display the current stage (seed → lineage → events) +- **THEN** completed stages SHALL be visually distinct from pending/active stages +- **THEN** the indicator SHALL replace the existing single loading spinner + +### Requirement: Legacy static query-tool.js SHALL be removed +The pre-Vite static file `src/mes_dashboard/static/js/query-tool.js` (3056L, 126KB) SHALL be deleted as dead code. + +#### Scenario: Dead code removal verification +- **WHEN** `static/js/query-tool.js` is deleted +- **THEN** grep for `static/js/query-tool.js` SHALL return zero results across the codebase +- **THEN** `query_tool.html` template SHALL continue to function via `frontend_asset('query-tool.js')` which resolves to the Vite-built bundle +- **THEN** `frontend/src/query-tool/main.js` (Vue 3 Vite entry) SHALL remain unaffected diff --git a/openspec/specs/trace-staged-api/spec.md b/openspec/specs/trace-staged-api/spec.md new file mode 100644 index 0000000..48b97c9 --- /dev/null +++ b/openspec/specs/trace-staged-api/spec.md @@ -0,0 +1,89 @@ +## ADDED Requirements + +### Requirement: Staged trace API SHALL expose seed-resolve endpoint +`POST /api/trace/seed-resolve` SHALL resolve seed lots based on the provided profile and parameters. + +#### Scenario: query_tool profile seed resolve +- **WHEN** request body contains `{ "profile": "query_tool", "params": { "resolve_type": "lot_id", "values": [...] } }` +- **THEN** the endpoint SHALL call existing lot resolve logic and return `{ "stage": "seed-resolve", "seeds": [...], "seed_count": N, "cache_key": "trace:{hash}" }` +- **THEN** each seed object SHALL contain `container_id`, `container_name`, and `lot_id` + +#### Scenario: mid_section_defect profile seed resolve +- **WHEN** request body contains `{ "profile": "mid_section_defect", "params": { "date_range": [...], "workcenter": "..." } }` +- **THEN** the endpoint SHALL call TMTT detection logic and return seed lots in the same response format + +#### Scenario: Empty seed result +- **WHEN** seed resolution finds no matching lots +- **THEN** the endpoint SHALL return HTTP 200 with `{ "stage": "seed-resolve", "seeds": [], "seed_count": 0, "cache_key": "trace:{hash}" }` +- **THEN** the error code `SEED_RESOLVE_EMPTY` SHALL NOT be used for empty results (reserved for resolution failures) + +#### Scenario: Invalid profile +- **WHEN** request body contains an unrecognized `profile` value +- **THEN** the endpoint SHALL return HTTP 400 with `{ "error": "...", "code": "INVALID_PROFILE" }` + +### Requirement: Staged trace API SHALL expose lineage endpoint +`POST /api/trace/lineage` SHALL resolve lineage graph for provided container IDs using `LineageEngine`. + +#### Scenario: Normal lineage resolution +- **WHEN** request body contains `{ "profile": "query_tool", "container_ids": [...] }` +- **THEN** the endpoint SHALL call `LineageEngine.resolve_full_genealogy()` and return `{ "stage": "lineage", "ancestors": {...}, "merges": {...}, "total_nodes": N }` + +#### Scenario: Lineage result caching with idempotency +- **WHEN** two requests with the same `container_ids` set (regardless of order) arrive +- **THEN** the cache key SHALL be computed as `trace:lineage:{sorted_cids_hash}` +- **THEN** the second request SHALL return cached result from L2 Redis (TTL = 300s) + +#### Scenario: Lineage timeout +- **WHEN** lineage resolution exceeds 10 seconds +- **THEN** the endpoint SHALL return HTTP 504 with `{ "error": "...", "code": "LINEAGE_TIMEOUT" }` + +### Requirement: Staged trace API SHALL expose events endpoint +`POST /api/trace/events` SHALL query events for specified domains using `EventFetcher`. + +#### Scenario: Normal events query +- **WHEN** request body contains `{ "profile": "query_tool", "container_ids": [...], "domains": ["history", "materials"] }` +- **THEN** the endpoint SHALL return `{ "stage": "events", "results": { "history": { "data": [...], "count": N }, "materials": { "data": [...], "count": N } }, "aggregation": null }` + +#### Scenario: mid_section_defect profile with aggregation +- **WHEN** request body contains `{ "profile": "mid_section_defect", "container_ids": [...], "domains": ["upstream_history"] }` +- **THEN** the endpoint SHALL automatically run aggregation logic after event fetching +- **THEN** the response `aggregation` field SHALL contain the aggregated results (not null) + +#### Scenario: Partial domain failure +- **WHEN** one domain query fails while others succeed +- **THEN** the endpoint SHALL return HTTP 200 with `{ "error": "...", "code": "EVENTS_PARTIAL_FAILURE" }` +- **THEN** the response SHALL include successfully fetched domains in `results` and list failed domains in `failed_domains` + +### Requirement: All staged trace endpoints SHALL apply rate limiting and caching +Every `/api/trace/*` endpoint SHALL use `configured_rate_limit()` and L2 Redis caching. + +#### Scenario: Rate limit exceeded on any trace endpoint +- **WHEN** a client exceeds the configured request budget for a trace endpoint +- **THEN** the endpoint SHALL return HTTP 429 with a `Retry-After` header +- **THEN** the body SHALL contain `{ "error": "...", "meta": { "retry_after_seconds": N } }` + +#### Scenario: Cache hit on trace endpoint +- **WHEN** a request matches a cached result in L2 Redis (TTL = 300s) +- **THEN** the cached result SHALL be returned without executing backend logic +- **THEN** Oracle DB connection pool SHALL NOT be consumed + +### Requirement: cache_key parameter SHALL be used for logging correlation only +The optional `cache_key` field in request bodies SHALL be used solely for logging and tracing correlation. + +#### Scenario: cache_key provided in request +- **WHEN** a request includes `cache_key` from a previous stage response +- **THEN** the value SHALL be logged for correlation purposes +- **THEN** the value SHALL NOT influence cache lookup or rate limiting logic + +#### Scenario: cache_key omitted in request +- **WHEN** a request omits the `cache_key` field +- **THEN** the endpoint SHALL function normally without any degradation + +### Requirement: Existing `GET /api/mid-section-defect/analysis` SHALL remain compatible +The existing analysis endpoint (GET method) SHALL internally delegate to the staged pipeline while maintaining identical external behavior. + +#### Scenario: Legacy analysis endpoint invocation +- **WHEN** a client calls `GET /api/mid-section-defect/analysis` with existing query parameters +- **THEN** the endpoint SHALL internally execute seed-resolve → lineage → events + aggregation +- **THEN** the response format SHALL be identical to the pre-refactoring output +- **THEN** a golden test SHALL verify output equivalence diff --git a/src/mes_dashboard/sql/query_tool/lot_history.sql b/src/mes_dashboard/sql/query_tool/lot_history.sql index 9a2ec4f..298bf1e 100644 --- a/src/mes_dashboard/sql/query_tool/lot_history.sql +++ b/src/mes_dashboard/sql/query_tool/lot_history.sql @@ -2,8 +2,8 @@ -- Retrieves complete production history for a LOT -- -- Parameters: --- :container_id - CONTAINERID to query (16-char hex) --- {{ WORKCENTER_FILTER }} - Optional workcenter name filter (replaced by service) +-- container_id - CONTAINERID to query (16-char hex) +-- WORKCENTER_FILTER - Optional workcenter name filter (replaced by service) -- -- Output columns: -- PJ_TYPE - Product type (from DW_MES_CONTAINER) diff --git a/src/mes_dashboard/sql/query_tool/lot_holds.sql b/src/mes_dashboard/sql/query_tool/lot_holds.sql index b9c1b10..d2d9842 100644 --- a/src/mes_dashboard/sql/query_tool/lot_holds.sql +++ b/src/mes_dashboard/sql/query_tool/lot_holds.sql @@ -2,7 +2,7 @@ -- Retrieves HOLD/RELEASE history for a LOT -- -- Parameters: --- :container_id - CONTAINERID to query (16-char hex) +-- container_id - CONTAINERID to query (16-char hex) -- -- Note: Uses HOLDTXNDATE/RELEASETXNDATE (NOT TXNDATETIME) -- NULL RELEASETXNDATE means currently HOLD diff --git a/src/mes_dashboard/sql/query_tool/lot_materials.sql b/src/mes_dashboard/sql/query_tool/lot_materials.sql index 0a729e2..407642e 100644 --- a/src/mes_dashboard/sql/query_tool/lot_materials.sql +++ b/src/mes_dashboard/sql/query_tool/lot_materials.sql @@ -2,7 +2,7 @@ -- Retrieves material consumption records for a LOT -- -- Parameters: --- :container_id - CONTAINERID to query (16-char hex) +-- container_id - CONTAINERID to query (16-char hex) -- -- Note: Uses MATERIALPARTNAME (NOT MATERIALNAME) -- Uses QTYCONSUMED (NOT CONSUMEQTY) diff --git a/src/mes_dashboard/sql/query_tool/lot_rejects.sql b/src/mes_dashboard/sql/query_tool/lot_rejects.sql index 7b84473..f3e40a8 100644 --- a/src/mes_dashboard/sql/query_tool/lot_rejects.sql +++ b/src/mes_dashboard/sql/query_tool/lot_rejects.sql @@ -2,7 +2,7 @@ -- Retrieves reject (defect) records for a LOT -- -- Parameters: --- :container_id - CONTAINERID to query (16-char hex) +-- container_id - CONTAINERID to query (16-char hex) -- -- Note: Uses LOSSREASONNAME (NOT REJECTREASONNAME) -- Uses TXNDATE (NOT TXNDATETIME)