ecoportal-api-graphql 1.3.13 → 1.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -0
- data/lib/ecoportal/api/common/graphql/client.rb +0 -2
- data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
- data/lib/ecoportal/api/graphql/base/file_container.rb +14 -14
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
- data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
- data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
- data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page.rb +17 -16
- data/lib/ecoportal/api/graphql/base/template.rb +35 -0
- data/lib/ecoportal/api/graphql/base.rb +37 -35
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
- data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection.rb +17 -17
- data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
- data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
- data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
- data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
- data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
- data/lib/ecoportal/api/graphql/fragment.rb +3 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -16
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -24
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
- data/lib/ecoportal/api/graphql/input.rb +24 -24
- data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
- data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
- data/lib/ecoportal/api/graphql/model/page.rb +16 -15
- data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
- data/lib/ecoportal/api/graphql/model/template.rb +15 -0
- data/lib/ecoportal/api/graphql/model.rb +32 -30
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation.rb +20 -20
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/payload.rb +23 -23
- data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
- data/lib/ecoportal/api/graphql/query/page.rb +45 -45
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
- data/lib/ecoportal/api/graphql/query.rb +27 -27
- data/lib/ecoportal/api/graphql.rb +9 -5
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +9 -180
- data/.ai-assistance/bridge/CLAUDE.md +0 -338
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +0 -29
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +0 -18
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +0 -42
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +0 -115
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +0 -48
- data/.ai-assistance/bridge/context/gemini-review-response.md +0 -104
- data/.ai-assistance/bridge/context/project.md +0 -42
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +0 -48
- data/.ai-assistance/bridge/queue/.gitkeep +0 -1
- data/.ai-assistance/capabilities/CLAUDE.md +0 -27
- data/.ai-assistance/capabilities/assumptions-log.md +0 -80
- data/.ai-assistance/capabilities/code.md +0 -47
- data/.ai-assistance/capabilities/connectors.md +0 -37
- data/.ai-assistance/capabilities/cowork.md +0 -55
- data/.ai-assistance/code/OVERVIEW.md +0 -155
- data/.ai-assistance/code/data_fields.md +0 -242
- data/.ai-assistance/code/dependencies.md +0 -151
- data/.ai-assistance/code/diff_as_input.md +0 -234
- data/.ai-assistance/code/diff_pairing_engine.md +0 -243
- data/.ai-assistance/code/diff_service_deep_dive.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +0 -55
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +0 -181
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +0 -147
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +0 -277
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +0 -200
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +0 -228
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +0 -197
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +0 -243
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +0 -196
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +0 -132
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +0 -187
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +0 -236
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +0 -183
- data/.ai-assistance/code/ecoportal_schema_reference.md +0 -240
- data/.ai-assistance/code/filter_contract_matrix.md +0 -177
- data/.ai-assistance/code/graphql_domain_knowledge.md +0 -240
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +0 -71
- data/.ai-assistance/code/refactoring/opportunities.md +0 -251
- data/.ai-assistance/code/schema_analysis.md +0 -321
- data/.ai-assistance/code/search_filters.md +0 -868
- data/.ai-assistance/code/spec_coverage.md +0 -73
- data/.ai-assistance/code/template_diff_pairing_domain.md +0 -175
- data/.ai-assistance/code/workflow-command-guide.md +0 -466
- data/.ai-assistance/code/workflow-space.md +0 -353
- data/.ai-assistance/conventions/CLAUDE.md +0 -30
- data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -199
- data/.ai-assistance/conventions/gitignore-rules.md +0 -42
- data/.ai-assistance/conventions/permission-guidance.md +0 -120
- data/.ai-assistance/integrations/README.md +0 -70
- data/.ai-assistance/integrations/gitkraken-mcp.md +0 -107
- data/.ai-assistance/integrations/gitlab-mcp.md +0 -123
- data/.ai-assistance/integrations/local-git.md +0 -60
- data/.ai-assistance/local_paths.example.md +0 -17
- data/.ai-assistance/projects/TODO.md +0 -118
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +0 -168
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +0 -60
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +0 -267
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +0 -53
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +0 -102
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +0 -139
- data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +0 -244
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +0 -93
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +0 -244
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +0 -266
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +0 -86
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +0 -101
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +0 -177
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +0 -161
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +0 -125
- data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +0 -136
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +0 -126
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +0 -261
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +0 -122
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +0 -148
- data/.ai-assistance/projects/page-model/DECISIONS.md +0 -245
- data/.ai-assistance/projects/page-model/TODO.md +0 -190
- data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +0 -93
- data/.ai-assistance/projects/qa-services-delivery/INTENT.md +0 -76
- data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +0 -115
- data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +0 -99
- data/.ai-assistance/projects/qa-services-delivery/TODO.md +0 -81
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +0 -107
- data/.ai-assistance/projects/search-filter-builder/TODO.md +0 -131
- data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +0 -87
- data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +0 -108
- data/.ai-assistance/projects/template-diff-deploy/INTENT.md +0 -12
- data/.ai-assistance/projects/template-diff-deploy/TODO.md +0 -9
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +0 -134
- data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +0 -93
- data/.ai-assistance/projects/template-maintenance/README.md +0 -14
- data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +0 -236
- data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +0 -243
- data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +0 -154
- data/.ai-assistance/projects/workflow-space/TODO.md +0 -213
- data/.ai-assistance/reinstall-claude-desktop-windows.md +0 -136
- data/.ai-assistance/scripts/CLAUDE.md +0 -150
- data/.ai-assistance/scripts/bridge-inbox-check.sh +0 -75
- data/.ai-assistance/scripts/bridge-init.sh +0 -86
- data/.ai-assistance/scripts/bridge-status.sh +0 -44
- data/.ai-assistance/scripts/capabilities-check.ts +0 -104
- data/.ai-assistance/scripts/check-outbox.sh +0 -43
- data/.ai-assistance/scripts/dep_graph.rb +0 -91
- data/.ai-assistance/scripts/lock-acquire.sh +0 -103
- data/.ai-assistance/scripts/lock-multi.sh +0 -124
- data/.ai-assistance/scripts/lock-queue.sh +0 -94
- data/.ai-assistance/scripts/setup-mcps.test.ts +0 -188
- data/.ai-assistance/scripts/setup-mcps.ts +0 -234
- data/.ai-assistance/scripts/task-complete.ts +0 -74
- data/.ai-assistance/scripts/task-create.ts +0 -75
- data/.ai-assistance/scripts/task-read.ts +0 -125
- data/.ai-assistance/scripts/token-logger.js +0 -220
- data/.ai-assistance/scripts/token-report.ts +0 -158
- data/.ai-assistance/scripts/token-session-start.js +0 -66
- data/.ai-assistance/skills/ai-instructions/SKILL.md +0 -48
- data/.ai-assistance/skills/code-specs/SKILL.md +0 -69
- data/.ai-assistance/skills/corporate-policies/SKILL.md +0 -201
- data/.ai-assistance/skills/dep-graph/SKILL.md +0 -139
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
- data/.ai-assistance/skills/gemini-assist/SKILL.md +0 -63
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +0 -205
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +0 -1
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +0 -240
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +0 -25
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +0 -261
- data/.ai-assistance/skills/procedural-memory/SKILL.md +0 -319
- data/.ai-assistance/skills/project-cycle/SKILL.md +0 -177
- data/.ai-assistance/skills/project-self-docs/SKILL.md +0 -181
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +0 -378
- data/.ai-assistance/skills/refactor/SKILL.md +0 -62
- data/.ai-assistance/skills/rubocop/SKILL.md +0 -93
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
- data/.ai-assistance/skills/spec-generation/SKILL.md +0 -72
- data/.ai-assistance/standards-version.json +0 -22
- data/.ai-assistance/token-budget.json +0 -32
- data/.ai-assistance/version.json +0 -39
- data/.claude/settings.json +0 -150
- data/.env.example +0 -18
- data/.gitattributes +0 -15
- data/.gitignore +0 -37
- data/.gitlab-ci.yml +0 -45
- data/.markdownlint.json +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -121
- data/.ruby-version +0 -1
- data/.yardopts +0 -10
- data/CLAUDE.md +0 -243
- data/Gemfile +0 -30
- data/Rakefile +0 -90
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/setup.ps1 +0 -6
- data/docs/self-docs/ARCHITECTURE.md +0 -88
- data/docs/self-docs/CHANGES.jsonl +0 -12
- data/docs/self-docs/COMPLIANCE.md +0 -79
- data/docs/self-docs/CONVENTIONS.md +0 -74
- data/docs/self-docs/INTEGRATIONS.md +0 -65
- data/docs/self-docs/OPERATIONS.md +0 -76
- data/docs/self-docs/OVERVIEW.md +0 -64
- data/docs/self-docs/STATUS.md +0 -73
- data/docs/self-docs/self-docs-index.json +0 -51
- data/docs/worklog.md +0 -977
- data/ecoportal-api-graphql.gemspec +0 -40
- data/scripts/auto-worker-scheduler.sh +0 -386
- data/tests/actions_get.rb +0 -7
- data/tests/contractor_entities_get.rb +0 -20
- data/tests/contractor_entity_create.rb +0 -19
- data/tests/contractor_entity_udpate.rb +0 -20
- data/tests/dump_page_model.rb +0 -74
- data/tests/dump_template_model.rb +0 -90
- data/tests/loc_structure_get.rb +0 -10
- data/tests/loc_structure_update.rb +0 -51
- data/tests/loc_structures_get.rb +0 -15
- data/tests/local_libs.rb +0 -20
- data/tests/validate_queries.rb +0 -127
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# 13 — AI Infrastructure
|
|
2
|
-
|
|
3
|
-
*Extracted from backend source analysis of `C:\docker\ecoPortal_master` (2026-06-08).*
|
|
4
|
-
*This is **customer-facing** infrastructure — not to be reused for internal tooling.*
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
EcoPortal's AI layer is built on AWS Bedrock (primary LLM and embeddings) + OpenSearch KNN
|
|
11
|
-
(vector search) + AWS Bedrock AgentCore (natural language → filters). GCP Vertex AI provides
|
|
12
|
-
a fallback for image analysis. All AI features are organisation-scoped and gated by feature flags.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## LLM Models (AWS Bedrock)
|
|
17
|
-
|
|
18
|
-
| Model ID | Region | Use case |
|
|
19
|
-
|---|---|---|
|
|
20
|
-
| `anthropic.claude-3-5-sonnet-20241022-v2:0` | `ap-southeast-2` | AiSummary generation, Image OCR/metadata, SmartFill |
|
|
21
|
-
| `anthropic.claude-3-sonnet-20240229-v1:0` | inferred | Legacy AiSummary |
|
|
22
|
-
| `meta.llama3-3-70b-instruct-v1:0` | `us-east-2` | Alternative LLM |
|
|
23
|
-
| `amazon.nova-micro-v1:0` | `us-east-1` | Lightweight inference (980 req/min rate limit) |
|
|
24
|
-
|
|
25
|
-
**Guardrails (prompt injection protection):**
|
|
26
|
-
- `us-east-2`: guardrail ID `7gsue70cel0j` v1 (`prompt_attack_only`)
|
|
27
|
-
- `ap-southeast-2`: guardrail ID `5mg9t3aqm3ic` v1 (`prompt_attack_only`)
|
|
28
|
-
|
|
29
|
-
Rate limiting: Redis-backed sorted sets. Claude 3: 90 req/min; Nova Micro: 980 req/min.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Embedding Models (AWS Bedrock Cohere)
|
|
34
|
-
|
|
35
|
-
| Model ID | Env var | Dimensions | Region | Use case |
|
|
36
|
-
|---|---|---|---|---|
|
|
37
|
-
| `cohere.embed-multilingual-v3` | `COHERE_EMBED_V3_MODEL_ID` | 1024 | `ap-southeast-2` | Universal embeddings |
|
|
38
|
-
| `us.cohere.embed-v4:0` | `COHERE_EMBED_V4_MODEL_ID` | 1024 (default) | `us-east-1` | High-perf embeddings |
|
|
39
|
-
|
|
40
|
-
Max batch: 96 texts/call. Used for: page text field vectors, template descriptions, semantic search.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## AI-Powered Features
|
|
45
|
-
|
|
46
|
-
### 1. AiSummary Field (`NewEp::Pages::DataFields::AiSummary`)
|
|
47
|
-
|
|
48
|
-
AI-generated text summary field on pages. Template-configured.
|
|
49
|
-
|
|
50
|
-
**Gem type:** `Base::Page::DataField::AiSummary` (read-only in DataFieldInput)
|
|
51
|
-
**Dedicated mutations:** `generateAiSummary`, `submitAiSummaryFeedback`
|
|
52
|
-
|
|
53
|
-
Key fields:
|
|
54
|
-
- `instructions` — custom prompt addition set on the template field (**reviewable by team**)
|
|
55
|
-
- `trigger` — `submitted` | `approved` (when generation fires)
|
|
56
|
-
- `triggerStageIds` — stages that trigger (empty = all)
|
|
57
|
-
- `textFormat` — `simple_text` | `bullet_points` | `numbered_list` | `headings`
|
|
58
|
-
- `aiSummary` — the generated text
|
|
59
|
-
- `alreadyRated` — whether the user has given feedback
|
|
60
|
-
|
|
61
|
-
Versioning: `NewEp::Pages::AiSummaryVersion` tracks prompt, token counts, status, ratings.
|
|
62
|
-
|
|
63
|
-
### 2. SmartFill Field (`NewEp::Pages::DataFields::SmartFill`)
|
|
64
|
-
|
|
65
|
-
AI-auto-populated data fields using template-defined prompts. Supports plain text, date,
|
|
66
|
-
select, rich text, mailbox, actions list field types.
|
|
67
|
-
|
|
68
|
-
**Gem type:** `Base::Page::DataField::SmartFill` (read-only in DataFieldInput)
|
|
69
|
-
**Dedicated mutations:** `generateSmartFill`, `submitSmartFillFeedback`
|
|
70
|
-
|
|
71
|
-
Key fields:
|
|
72
|
-
- `instructions` — the AI prompt for this field (**reviewable by team**)
|
|
73
|
-
- `trigger_type` — `manual` | `snapshot` | `live`
|
|
74
|
-
- `status` — `pending` | `generating` | `success` | `failed`
|
|
75
|
-
- `edit_enabled` — whether users can override the AI-generated value
|
|
76
|
-
- `generated_at` — timestamp
|
|
77
|
-
|
|
78
|
-
### 3. Template AI Description
|
|
79
|
-
|
|
80
|
-
Templates can have an auto-generated AI description used for semantic search.
|
|
81
|
-
|
|
82
|
-
- Field: `ai_description` on `Enzyme::Mould`
|
|
83
|
-
- Embeddings: `ai_description_embeddings` (Cohere v4, 1024 dims)
|
|
84
|
-
- Job: `NewEp::Pages::Templates::AiDescriptionEmbedJob`
|
|
85
|
-
- Query log: `NewEp::Pages::Templates::AiQueryLog`
|
|
86
|
-
|
|
87
|
-
### 4. Semantic Search (OpenSearch KNN)
|
|
88
|
-
|
|
89
|
-
Vector-based page search on `text_field_vectors.embedding` (nested field).
|
|
90
|
-
|
|
91
|
-
- Index: Enzyme::Ooze + NewEp::Page
|
|
92
|
-
- Vector path: `text_field_vectors.embedding`
|
|
93
|
-
- Dimensions: 1024 (Cohere v4)
|
|
94
|
-
- Min score: 0.65, KNN k: 10
|
|
95
|
-
- Gated by: `organization.pages_semantic_search_enabled?`
|
|
96
|
-
- Service: `NewEp::Es::SemanticQs::Page`
|
|
97
|
-
|
|
98
|
-
### 5. Smart Search — Natural Language → Filters
|
|
99
|
-
|
|
100
|
-
Converts a user's natural-language query into structured register search filters.
|
|
101
|
-
|
|
102
|
-
- Service: `NewEp::GeneratedFilters::PageFilters::Generate`
|
|
103
|
-
- Backend: `AwsBedrock::AgentCore::InvokeAgentRuntime`
|
|
104
|
-
- Agent ARN: `arn:aws:bedrock-agentcore:ap-southeast-2:697073004009:runtime/prod_master_page_filters_agent-P10hX5HEIG`
|
|
105
|
-
- Auth: JWT tokens via `NewEp::AgentSessions::TokenService` (scope: `page_filters_agent`)
|
|
106
|
-
- Dev endpoint: `http://host.docker.internal:8080/invocations` or `SMART_QUERY_AGENT_URL`
|
|
107
|
-
|
|
108
|
-
This is the backend for the "build register filters on the go" customer feature.
|
|
109
|
-
|
|
110
|
-
### 6. Image OCR / Metadata Extraction
|
|
111
|
-
|
|
112
|
-
Extracts text, captions, descriptions, and sensitivity flags from uploaded images.
|
|
113
|
-
|
|
114
|
-
**Primary:** AWS Bedrock Claude 3.5 Sonnet (`ap-southeast-2`)
|
|
115
|
-
**Fallback:** GCP Vertex AI Gemini (`gemini-3.1-flash-lite`)
|
|
116
|
-
|
|
117
|
-
Supported formats: PNG, JPG, JPEG, GIF, WebP, BMP
|
|
118
|
-
Max size: 4.71 MB (Bedrock), 19 MB (GCP); Max resolution: 1568px / 1.15 MP
|
|
119
|
-
|
|
120
|
-
Output: `description`, `caption`, `extracted_text`, `sensitive_content`, `overall_confidence`
|
|
121
|
-
|
|
122
|
-
### 7. Inline Text AI Tools
|
|
123
|
-
|
|
124
|
-
General text manipulation operations accessible from any text-capable field.
|
|
125
|
-
|
|
126
|
-
**Model:** GCP Vertex AI Gemini (`gemini-3.1-flash-lite`)
|
|
127
|
-
**Mutations:** `aiToolsSummarize`, `aiToolsFixSpellingGrammar`, `aiToolsMakeFormal`,
|
|
128
|
-
`aiToolsRephrase`, `aiToolsTranslate`
|
|
129
|
-
|
|
130
|
-
Context injection: field label, description, max char limit (via `AiTools::ContextInput`).
|
|
131
|
-
Translation backend: Google Cloud Translate.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Video Transcription
|
|
136
|
-
|
|
137
|
-
Not implemented. The application uses vision models (image OCR) but no video
|
|
138
|
-
transcription pipeline was found in the codebase. Status: not available.
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Async Job Queue
|
|
143
|
-
|
|
144
|
-
| Job | Queue | Purpose |
|
|
145
|
-
|---|---|---|
|
|
146
|
-
| `NewEp::Pages::DataFields::AiSummaryGenerationJob` | `p3` | Generate AI summary (max 3 retries) |
|
|
147
|
-
| `NewEp::Pages::DataFields::AiSummaryTriggerJob` | async | Trigger on page submit/approve |
|
|
148
|
-
| `Kickstand::NewEp::Pages::AiSummaryCreateVersionJob` | Kickstand | Create version record on stage submission |
|
|
149
|
-
| `NewEp::Pages::Templates::AiDescriptionEmbedJob` | `p3` | Embed template description (Cohere v4) |
|
|
150
|
-
| `Kickstand::NewEp::Pages::GenerateFieldEmbeddingsJob` | Kickstand | Embed page text field content |
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## AI Prompt Instructions — Team Review Note
|
|
155
|
-
|
|
156
|
-
Both `AiSummary` and `SmartFill` fields store an `instructions` string that is appended
|
|
157
|
-
to Bedrock's system prompt at generation time. These instructions are set by template
|
|
158
|
-
builders (human, in-app) and directly influence output quality and safety.
|
|
159
|
-
|
|
160
|
-
**Current situation:**
|
|
161
|
-
- No workflow exists in the gem or backend for auditing/reviewing these prompts at scale.
|
|
162
|
-
- Instructions are stored per-field-instance in the page document.
|
|
163
|
-
- To review: query pages that have AiSummary/SmartFill fields and read the `instructions` field.
|
|
164
|
-
|
|
165
|
-
**Future work needed:**
|
|
166
|
-
- A review/approval workflow for template-level AI field instructions
|
|
167
|
-
- Possibly surfacing instructions in the template admin UI for bulk review
|
|
168
|
-
- Tracked in: `projects/TODO.md` — "AI prompt review workflow"
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## Dependencies (Gemfile)
|
|
173
|
-
|
|
174
|
-
```ruby
|
|
175
|
-
gem 'aws-sdk-bedrockruntime', '~> 1.39' # LLM invocation
|
|
176
|
-
gem 'aws-sdk-bedrockagentcore', '~> 1.14' # Agent (smart search)
|
|
177
|
-
gem 'opensearch-ruby' # Vector search
|
|
178
|
-
gem 'faraday_middleware-aws-sigv4' # AWS SigV4 auth
|
|
179
|
-
gem 'google-cloud-translate', '3.6.1' # Translation for AI tools
|
|
180
|
-
gem 'memo_wise' # Embedding memoisation
|
|
181
|
-
gem 'image_processing', '~> 1.2' # Image resizing
|
|
182
|
-
gem 'ruby-vips', '~> 2' # Image processing backend
|
|
183
|
-
```
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
# EcoPortal GraphQL Schema — AI Agent Quick Reference
|
|
2
|
-
|
|
3
|
-
**Source:** Live introspection `20260605T101224_live_ep_graphql_schema.graphql.json`
|
|
4
|
-
**Use this doc to:** orient quickly on the schema without re-running extraction scripts.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Architecture at a Glance
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
Query {
|
|
12
|
-
currentOrganization: Organization! ← ALL org data lives here
|
|
13
|
-
checkInConfig(identifier:) ← public kiosk endpoint (no auth)
|
|
14
|
-
publicPage / publicTemplate ← anonymous access
|
|
15
|
-
user / users ← user management
|
|
16
|
-
kickstand* ← workflow automation
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Everything important is nested under `currentOrganization`.**
|
|
21
|
-
There are no top-level `page`, `action`, or `contractorEntity` query fields —
|
|
22
|
-
you always go through `currentOrganization.page(id:)`, etc.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Mutation Pattern — Consistent Across All Mutations
|
|
27
|
-
|
|
28
|
-
**All 161 mutations use a single `input: SomeType!` wrapper.** No mutation has
|
|
29
|
-
separate top-level arguments. This is consistent — build your gem input classes
|
|
30
|
-
as `InputClass.from_model(model)` returning a hash that maps to the single `input:` arg.
|
|
31
|
-
|
|
32
|
-
```graphql
|
|
33
|
-
mutation UpdateSomething($input: UpdateSomethingInput!) {
|
|
34
|
-
updateSomething(input: $input) { item { ...fields } errors { ... } }
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Schema-Wide Shared Patterns
|
|
41
|
-
|
|
42
|
-
### `patchVer: Int!` — Optimistic Concurrency Lock
|
|
43
|
-
Present on: `BasePageInterface`, `CheckInConfig`
|
|
44
|
-
- **Non-null on read** — every page/kiosk config response includes it.
|
|
45
|
-
- **Optional on write** (mutation input) — but should always be passed.
|
|
46
|
-
- **Pattern:** read `patchVer` from model → include in update input → server rejects if stale.
|
|
47
|
-
- **Gem:** `BasePage` maps it as `passthrough :patchVer`. Not yet injected in mutation inputs.
|
|
48
|
-
|
|
49
|
-
### `IdDiffInput` — Incremental Relationship Updates
|
|
50
|
-
```graphql
|
|
51
|
-
input IdDiffInput { additions: [ID!], removals: [ID!] }
|
|
52
|
-
```
|
|
53
|
-
Used for array relationships in **update** mutations (not create). Create mutations use `[ID!]` flat arrays.
|
|
54
|
-
|
|
55
|
-
Always build separate `Create` and `Update` input classes for areas using `IdDiffInput`.
|
|
56
|
-
|
|
57
|
-
### `OneToManyInput` — Nested Object Arrays
|
|
58
|
-
```graphql
|
|
59
|
-
input DataFieldOneToManyInput { additions: [DataFieldInput!], updates: [DataFieldInput!], deletions: [ID!] }
|
|
60
|
-
```
|
|
61
|
-
Pattern for complex nested array updates. Present for: `dataFields`, `visitorTypeConfigurations`,
|
|
62
|
-
`requirements`, `legalAgreements`.
|
|
63
|
-
|
|
64
|
-
### `DeltaResult` — Incremental Sync
|
|
65
|
-
```graphql
|
|
66
|
-
type DeltaResult { id: ID!, delta: DeltaEnum! } # UPDATED | NOT_FOUND | NEW
|
|
67
|
-
```
|
|
68
|
-
Query fields: `pageDelta(ids:)`, `actionDelta(ids:)`, `contractorEntityDelta(ids:)`,
|
|
69
|
-
`personMemberDelta(ids:)`, `templateDelta(ids:)` — all on `currentOrganization`.
|
|
70
|
-
|
|
71
|
-
Pass a set of cached IDs → get back which ones changed, were deleted, or are new.
|
|
72
|
-
|
|
73
|
-
### Mutation Payload Pattern
|
|
74
|
-
Most mutations return: `{ item: TypeUnion, errors: ValidationErrors }` or
|
|
75
|
-
`{ item: SomeType, nextTasks: [MinimalTaskInterface], errors }` (page/task mutations).
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Type Inventory by Area
|
|
80
|
-
|
|
81
|
-
### Pages
|
|
82
|
-
| Type | Kind | Purpose |
|
|
83
|
-
|------|------|---------|
|
|
84
|
-
| `BasePageInterface` | INTERFACE | 50 fields shared by all pages |
|
|
85
|
-
| `BasicPage` | OBJECT | Non-staged page (implements BasePageInterface) |
|
|
86
|
-
| `PhasedPage` | OBJECT | Multi-stage form/register (implements BasePageInterface) |
|
|
87
|
-
| `PageUnion` | UNION | `BasicPage \| PhasedPage` — returned by queries |
|
|
88
|
-
| `Stage` | OBJECT | Single stage of a PhasedPage |
|
|
89
|
-
| `MinimalStage` | OBJECT | `id, name, ordering, state` — lightweight |
|
|
90
|
-
| `UpdatePageInput` | INPUT | `id!, stageId, submit, dataFields, task, page, patchVer, publish` |
|
|
91
|
-
| `PageInput` | INPUT | `locations, otherTags, timeZone, state, taskPriority, name, externalId` |
|
|
92
|
-
| `DataFieldInput` | INPUT | Union-like — one of 20 field type sub-inputs |
|
|
93
|
-
| `DataFieldOneToManyInput` | INPUT | `additions, updates, deletions` for dataFields |
|
|
94
|
-
| `CreateFromTemplateInput` | INPUT | `templateId!, dataFields!, page, stageId, submit, attach` |
|
|
95
|
-
|
|
96
|
-
**Queries:** `org.page(id:)`, `org.pages(searchConf:, after:, first:, ...)`, `org.myDraftPages`,
|
|
97
|
-
`org.allPages`, `org.templates`, `org.fullTemplates`, `org.minimalTemplates`
|
|
98
|
-
|
|
99
|
-
**Key mutations:** `updatePage`, `archivePage`, `unarchivePage`,
|
|
100
|
-
`createPageFromTemplate`, `buildPageFromTemplate`, `createPageDraft`,
|
|
101
|
-
`deleteDraftPage`, `publishDraftPage`, `approveReviewPageTask`, `rejectReviewPageTask`
|
|
102
|
-
|
|
103
|
-
**`PageUnion` members:** Only `BasicPage` and `PhasedPage`. Always query `__typename` to
|
|
104
|
-
dispatch to the right Ruby model class.
|
|
105
|
-
|
|
106
|
-
### Actions
|
|
107
|
-
| Type | Kind | Purpose |
|
|
108
|
-
|------|------|---------|
|
|
109
|
-
| `Action` | OBJECT | Action instance |
|
|
110
|
-
| `Category` | OBJECT | Action category (name, value, archived, weight) |
|
|
111
|
-
| `UpdateActionInput` | INPUT | `id!, name!, status!, updateMode, recurrence, closeDetail, ...` |
|
|
112
|
-
| `CreateActionInput` | INPUT | `name!, status!, recurrence, linkedResource, draft, ...` |
|
|
113
|
-
| `ActionRecurrenceInput` | INPUT | `daily, weekly, or monthly` sub-input |
|
|
114
|
-
| `SeriesUpdateMode` | ENUM | `ALL \| FORWARD \| ONE` |
|
|
115
|
-
|
|
116
|
-
**Gem gaps:** `recurrence`, `updateMode`, `tags`, `dueIn/reminderIn`, `linkedResources`
|
|
117
|
-
|
|
118
|
-
### ContractorEntities
|
|
119
|
-
| Type | Kind | Purpose |
|
|
120
|
-
|------|------|---------|
|
|
121
|
-
| `ContractorEntity` | OBJECT | Contractor org |
|
|
122
|
-
| `UpdateContractorEntityInput` | INPUT | Uses `IdDiffInput` for `leadContractorIds`, `associatedPeopleIds` |
|
|
123
|
-
| `CreateContractorEntityInput` | INPUT | Uses `[ID!]` (flat arrays) for same fields |
|
|
124
|
-
|
|
125
|
-
### Location Structures
|
|
126
|
-
| Type | Kind | Purpose |
|
|
127
|
-
|------|------|---------|
|
|
128
|
-
| `LocationNodeInterface` | INTERFACE | Shared by all location nodes |
|
|
129
|
-
| `UpdateLocationStructureInput` | INPUT | `id!, name, visitorManagementEnabled, archived, published, weight` |
|
|
130
|
-
|
|
131
|
-
No `patchVer` on location structures.
|
|
132
|
-
|
|
133
|
-
### Visitor Management / CheckIn
|
|
134
|
-
| Type | Kind | Purpose |
|
|
135
|
-
|------|------|---------|
|
|
136
|
-
| `CheckInConfig` | OBJECT | Kiosk configuration — has `patchVer: Int!` |
|
|
137
|
-
| `VisitorType` | OBJECT | Visitor category with check-in fields |
|
|
138
|
-
| `Visit` | OBJECT | Check-in record |
|
|
139
|
-
| `UpdateCheckInConfigInput` | INPUT | `id!, locationIds: IdDiffInput, visitorTypeConfigurations:, requirements:, ...` |
|
|
140
|
-
|
|
141
|
-
**Key for RS location fix:** `updateCheckInConfig(input: { id:, locationIds: { additions: [...], removals: [...] } })`
|
|
142
|
-
|
|
143
|
-
**Queries on org:** `checkInConfig(id:)`, `checkInConfigs(...)`, `visitorTypes(...)`,
|
|
144
|
-
`visits(...)`, `visitStatus(...)`, `identifyVisitor(...)`
|
|
145
|
-
|
|
146
|
-
**Mutations:** `checkIn`, `checkOut`, `checkInOnBehalf`, `checkOutOnBehalf`,
|
|
147
|
-
`createCheckInConfig`, `updateCheckInConfig`, `destroyCheckInConfig`,
|
|
148
|
-
`createVisitorType`, `updateVisitorType`, `destroyVisitorType`
|
|
149
|
-
|
|
150
|
-
### PersonMembers
|
|
151
|
-
| Type | Kind | Purpose |
|
|
152
|
-
|------|------|---------|
|
|
153
|
-
| `PersonMember` | OBJECT | Person (user, contractor, employee) |
|
|
154
|
-
| `PersonSchema` | OBJECT | Custom person schema (org-specific person data model) |
|
|
155
|
-
|
|
156
|
-
No `PersonMemberInput` — person management is not exposed as mutation (read-only in this API).
|
|
157
|
-
|
|
158
|
-
**Queries:** `personMember(id:)`, `personMembers(searchConf:)`, `attachablePersonMembers(searchConf:)`,
|
|
159
|
-
`personMembersCreatedSince(date:)`, `personMemberDelta(ids:)`
|
|
160
|
-
|
|
161
|
-
### Registers
|
|
162
|
-
| Type | Kind | Purpose |
|
|
163
|
-
|------|------|---------|
|
|
164
|
-
| `Register` | OBJECT | Register (page collection/module) — dashboards, templates, pages, preset views |
|
|
165
|
-
| `PresetView` | OBJECT | Saved column/sort config for `previewPages`; id used as `presetViewId` argument |
|
|
166
|
-
|
|
167
|
-
**Queries:** `register(id:)`, `registers`, `minimalRegisters`, `register.presetViews`
|
|
168
|
-
|
|
169
|
-
**Mutations:** `createRegister`, `updateRegister`, `destroyRegister`,
|
|
170
|
-
`createPresetView`, `updatePresetView`, `permissionPresetView`, `destroyPresetView`
|
|
171
|
-
|
|
172
|
-
**Gem coverage:** `Builder::Register` (create/update/destroy) + `Builder::Register::PresetView`
|
|
173
|
-
(list/create/update/permission/destroy). `Query::RegisterPresetViews` to list views by register.
|
|
174
|
-
`Base::Register`, `Model::Register`, `Base::PresetView`, `Model::PresetView` implemented.
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Enums Quick Reference
|
|
179
|
-
|
|
180
|
-
| Enum | Values |
|
|
181
|
-
|------|--------|
|
|
182
|
-
| `StateEnum` | `INACTIVE` / `ACTIVE` / `COMPLETE` |
|
|
183
|
-
| `ActionsStatusEnum` | `open` / `closed` |
|
|
184
|
-
| `TaskPriorityEnum` | `low` / `medium` / `high` / `none` |
|
|
185
|
-
| `SeriesUpdateMode` | `ALL` / `FORWARD` / `ONE` |
|
|
186
|
-
| `DeltaEnum` | `UPDATED` / `NOT_FOUND` / `NEW` |
|
|
187
|
-
| `StageStateEnum` | check schema — `active`, `completed`, `pending`, etc. |
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## Gem Coverage Map
|
|
192
|
-
|
|
193
|
-
| Area | Queries | Mutations | Gem status |
|
|
194
|
-
|------|---------|-----------|------------|
|
|
195
|
-
| Actions | ✓ action, actions, actionCategories | ✓ create, update, archive | Well covered. Gaps: recurrence, updateMode, tags |
|
|
196
|
-
| ContractorEntities | ✓ contractorEntities, contractorEntity | ✓ create, update, destroy, manageRelationships | Well covered. Verify IdDiff on update |
|
|
197
|
-
| LocationStructures | ✓ structure, structures, locationDraftById | ✓ draft mutations | Well covered |
|
|
198
|
-
| **Pages** | **✓ fully covered** | **✓ fully covered** | **Phases A–E complete 2026-06-08** |
|
|
199
|
-
| PersonMembers | ✗ | n/a | Gap |
|
|
200
|
-
| Registers | ✓ register, registers, presetViews | ✓ create, update, destroy + preset view CRUD | Covered 2026-06-08 |
|
|
201
|
-
| VisitorManagement | ✗ | ✗ | Gap (customer urgency) |
|
|
202
|
-
| Dashboards/Charts | ✗ | ✗ | Future — see TODO |
|
|
203
|
-
| Delta queries | ✓ pageDelta | n/a | Done |
|
|
204
|
-
| AI tools | ✗ (AiSummary/SmartFill read-only) | ✗ (mutations pending) | See TODO — AI field mutations |
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Key Rules for Implementing New Areas
|
|
209
|
-
|
|
210
|
-
1. **Always fetch before update.** If the output type has `patchVer`, you cannot update
|
|
211
|
-
without first querying the current value. Build this into the `from_model` input class.
|
|
212
|
-
|
|
213
|
-
2. **Create vs Update input classes are different.** `IdDiffInput` in update, `[ID!]` in
|
|
214
|
-
create — always check both input types separately.
|
|
215
|
-
|
|
216
|
-
3. **Spread `__typename` on union types.** `PageUnion`, `DataFieldUnion`, `SectionUnion`
|
|
217
|
-
all require `__typename` in the fragment to dispatch to the right Ruby model class.
|
|
218
|
-
|
|
219
|
-
4. **Mutations always use `input:` wrapper.** No mutation has separate top-level args.
|
|
220
|
-
|
|
221
|
-
5. **Check `nonNull` carefully.** `required: true` in a Ruby input class should match
|
|
222
|
-
`field: Type!` in the schema. Mismatches cause server validation errors.
|
|
223
|
-
|
|
224
|
-
6. **OneToManyInput for nested array updates.** Where you see `*OneToManyInput`, that
|
|
225
|
-
area needs separate `additions`, `updates`, and `deletions` handling — not a flat replace.
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Files to Check Before Implementing Anything
|
|
230
|
-
|
|
231
|
-
```
|
|
232
|
-
.ai-assistance/code/schema_analysis.md ← full schema survey
|
|
233
|
-
.ai-assistance/code/diff_as_input.md ← as_update/as_input pipeline spec
|
|
234
|
-
.ai-assistance/code/ecoportal_schema_reference.md ← this file
|
|
235
|
-
.ai-assistance/projects/page-model/TODO.md ← page project plan
|
|
236
|
-
.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md ← core arch decisions
|
|
237
|
-
lib/ecoportal/api/common/graphql/model/as_input.rb ← current AsInput (known issue: no patchVer injection)
|
|
238
|
-
lib/ecoportal/api/graphql/logic/base_query.rb ← query dispatch (recently updated)
|
|
239
|
-
lib/ecoportal/api/graphql/interface/base_page.rb ← BasePage partial mapping
|
|
240
|
-
```
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# Filter Contract Matrix — GraphQL register/page filters
|
|
2
|
-
|
|
3
|
-
**Purpose.** Evidence-based ground-truth mapping of *ES register/page filter operations* to
|
|
4
|
-
their exact params, per register field type, so the compat `FilterTranslator` can be validated
|
|
5
|
-
against the backend rather than conjecture. Built after the Date/Select translator bug
|
|
6
|
-
(commit e45ba58) where the original translation table was never validated.
|
|
7
|
-
|
|
8
|
-
**Authoritative source.** `ecoPortal_master` Rails backend, `NewEp::Es` filter stack:
|
|
9
|
-
|
|
10
|
-
- **Field-type → allowed operations** (the contract a register exposes):
|
|
11
|
-
`app/services/new_ep/es/definitions/page.rb`
|
|
12
|
-
- **Operation → exact params** (constructor kwargs / `argument` declarations):
|
|
13
|
-
`app/services/new_ep/es/filters/pages/**` and `.../filters/global/**`
|
|
14
|
-
- **Operation string → class dispatch:**
|
|
15
|
-
`app/services/new_ep/es/filters/deserializer.rb:51-58` — the `operation` string is
|
|
16
|
-
`camelize`d + suffixed `Filter`, resolved **first** under `NewEp::Es::Filters::Pages::`,
|
|
17
|
-
then falling back to `NewEp::Es::Filters::Global::`. An operation with no matching class
|
|
18
|
-
raises `UnknownFilterError` (`deserializer.rb:59`). This is why `options_filter` /
|
|
19
|
-
`text_filter` (v2 names) are NOT valid GraphQL operations — see §"v2 → GraphQL" below.
|
|
20
|
-
|
|
21
|
-
All line numbers below are `file:line` in `ecoPortal_master` unless noted. GraphQL param names
|
|
22
|
-
match the backend constructor / `argument` declarations exactly.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 1. v2 → GraphQL translator input types (what `Compat::FilterTranslator` handles)
|
|
27
|
-
|
|
28
|
-
`lib/ecoportal/api/graphql/compat/filter_translator.rb` accepts *v2-style* `type:` hashes and
|
|
29
|
-
emits GraphQL `{operation:, params:}` hashes. The v2 `type` names are NOT backend operation
|
|
30
|
-
names; the emitted `operation` names ARE. Every emitted operation below resolves to a real
|
|
31
|
-
backend class (verified via the deserializer dispatch).
|
|
32
|
-
|
|
33
|
-
| v2 `type` | Emitted GraphQL `operation` | Emitted params | Backend class (evidence) | Translator handles? |
|
|
34
|
-
|---|---|---|---|---|
|
|
35
|
-
| `date_filter` (system key, e.g. `created_at`) | `date_filter` | `key, time_zone, gte?(=lbound), lte?(=ubound)` | `Global::DateFilter` — `date_filter.rb:28` (`key, path, gt, gte, lt, lte`) | YES (`filter_translator.rb:110-124`) |
|
|
36
|
-
| `date_filter` (field key, e.g. `date.zXXXX`) | `range_date_filter` | `key, path=membranes.date, time_zone, gte?, lte?` | `Pages::RangeDateFilter` — `range_date_filter.rb:797` (`key, path, gte, lte, gt, lt`) | YES (`filter_translator.rb:110-124`) |
|
|
37
|
-
| `text_filter` | `match_filter` | `key, value(=query)` | `Global::MatchFilter` — `match_filter.rb:6` (`key, value, path`) | YES (`filter_translator.rb:74-82`) |
|
|
38
|
-
| `exact_filter` / `is_filter` | `is_filter` | `key, path?(derived membranes.<type>), value` | `Pages::IsFilter` — `is_filter.rb:258` (`key, path, value`) | YES (`filter_translator.rb:52-57`) |
|
|
39
|
-
| `options_filter` (system key) | `one_of_filter` / `none_of_filter` (exclude) | `key, values(=chosen)` | `Global::OneOfFilter` — `one_of_filter.rb:16` (`key, values`); `Global::NoneOfFilter` — `none_of_filter.rb:6` (`key, values`) | YES (`filter_translator.rb:90-104`) |
|
|
40
|
-
| `options_filter` (field key, e.g. `select_str.zXXXX`) | `has_any_filter` / `doesnt_have_any_filter` (exclude) | `key, path(=membranes.<type>), field_key='selected', values(=chosen)` | `Pages::HasAnyFilter` — `has_any_filter.rb:995` → `Pages::BaseHasAnyFilter` — `base_has_any_filter.rb:10` (`key, path, field_key, values, invert`); `Pages::DoesntHaveAnyFilter` — `doesnt_have_any_filter.rb:1061` | YES (`filter_translator.rb:90-104`) |
|
|
41
|
-
| `tag_filter` | *(passthrough — no rewrite)* | — | n/a (already GraphQL-shaped) | passthrough (`filter_translator.rb:37-39`) |
|
|
42
|
-
| `register_filter` | *(passthrough)* | — | `Pages::RegisterFilter` (`filters/pages/register_filter.rb`) | passthrough |
|
|
43
|
-
| `and_filter` / `or_filter` | *(passthrough)* | — | `Global::AndFilter` / `Global::OrFilter` | passthrough |
|
|
44
|
-
| already-`{operation:,params:}` hash | *(passthrough)* | — | — | passthrough (unmatched `type`) |
|
|
45
|
-
| SearchConf value object (`to_h`, not Hash) | *(passthrough)* | — | — | passthrough (`filter_translator.rb:25`) |
|
|
46
|
-
|
|
47
|
-
### Why `options_filter` / `text_filter` are NOT emitted verbatim
|
|
48
|
-
|
|
49
|
-
- **`options_filter`**: there is NO `Pages::OptionsFilter` or `Global::OptionsFilter` class.
|
|
50
|
-
The closest backend class is `Pages::OptionsFieldFilter` (op `options_field_filter`,
|
|
51
|
-
`options_field_filter.rb:9`, takes `chosen`/`mode`) — a *different* operation the register
|
|
52
|
-
definitions never expose. Emitting `options_filter` verbatim would raise `UnknownFilterError`.
|
|
53
|
-
So the translator rewrites to the backend-blessed `has_any_filter`/`one_of_filter`.
|
|
54
|
-
- **`text_filter`**: a `Pages::TextFilter` class DOES exist (`text_filter.rb:1252`) but its
|
|
55
|
-
constructor takes `key:, query:` (not `value:`) and register definitions never expose a
|
|
56
|
-
`text_filter` operation — they expose `contains_filter`/`is_filter`. The translator therefore
|
|
57
|
-
emits `match_filter` (`Global::MatchFilter`, `match_filter.rb:6`), which is a valid operation
|
|
58
|
-
with `{key, value}`. NOTE: `match_filter` matches the *analyzed* value (partial/full-text);
|
|
59
|
-
for whole-value exact use `is_filter` (see §exact-vs-partial in `search_filters.md`).
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## 2. Field-type contract matrix (register/page fields)
|
|
64
|
-
|
|
65
|
-
One row per **(field type, operation)** enumerated from `page.rb`. `key` = field definition key
|
|
66
|
-
(`<type>.zXXXX`); `path` = the nested membranes path the definition hardcodes. `field_key` /
|
|
67
|
-
`values` shown where the operation carries a subfield. "Translator?" marks whether the compat
|
|
68
|
-
`FilterTranslator` currently produces this operation from a v2 input.
|
|
69
|
-
|
|
70
|
-
### 2.1 System / metadata fields (no `membranes.*` path)
|
|
71
|
-
|
|
72
|
-
| Field | v2 type | GraphQL operation(s) | params | page.rb evidence | Translator? |
|
|
73
|
-
|---|---|---|---|---|---|
|
|
74
|
-
| Created on / Updated on | `date_filter` | `date_filter`, `isnt_in_date_filter` | `key=created_at`, gte/lte/time_zone | `page.rb:31-39` | YES (system date branch) |
|
|
75
|
-
| External ID | `exact_filter` | `is_filter`, `isnt_filter`, `is_empty_filter`, `isnt_empty_filter` | `key=external_id`, value | `page.rb:65-70` | YES via `exact_filter`→`is_filter` (no path — system key) |
|
|
76
|
-
| Page ID (`mould_counter`) | — | `is_filter`, `isnt_filter`, `is_empty_filter`, `isnt_empty_filter`, `contains_filter`, `doesnt_contain_filter` | `key=mould_counter`, value | `page.rb:72-79` | is_filter YES; contains_filter NO |
|
|
77
|
-
| Page name (`name`) | `text_filter`/`exact_filter` | `is_filter`, `isnt_filter`, `contains_filter`, `doesnt_contain_filter` | `key=name`, value | `page.rb:81-86` | is_filter YES; match/contains partial: `text_filter`→`match_filter` (valid but `name` uses QueryFilter internally — see NOTE) |
|
|
78
|
-
| Page status (`state`) | `options_filter`/`exact` | `is_filter`, `isnt_filter` | `key=state`, value(`@extras`) | `page.rb:87-99` | via `exact_filter`→`is_filter` YES |
|
|
79
|
-
| System ID (`id`) | `exact_filter` | `is_filter`, `isnt_filter`, `one_of_filter` | `key=id`, value / values | `page.rb:101-105` | is_filter + one_of_filter YES |
|
|
80
|
-
| Assigned to | — | `is_filter`, `isnt_filter`, `has_all/any_filter`, `doesnt_have_all/any_filter` | `key=assigned_to`, field_key=`assigned_to`, values | `page.rb:15-22` | NO (no v2 driver) |
|
|
81
|
-
| Created by | — | `is_filter`, `isnt_filter`, `has_any_filter`, `doesnt_have_any_filter` | `key=creator_id`, field_key=`creator_id` | `page.rb:24-29` | NO |
|
|
82
|
-
| Created via (`source`) | — | `is_filter`, `isnt_filter` | `key=source`, value | `page.rb:41-45` | via is_filter YES (if driven) |
|
|
83
|
-
| Custom tags (`other_tags`) | `tag_filter` | `has_any/all_filter`, `doesnt_have_any/all_filter`, `is_empty_filter`, `isnt_empty_filter` | `key=other_tags`, field_key=`other_tags`, values | `page.rb:56-63` | passthrough (`tag_filter`) |
|
|
84
|
-
| Tags / locations (`all_location_ids`) | `tag_filter` | `has_any/all_filter`, `doesnt_have_any/all_filter`, `is_empty_filter`, `isnt_empty_filter` | `key=all_location_ids`, field_key=`all_location_ids`, values | `page.rb:107-114` | passthrough (values lowercased backend-side: `has_any_filter.rb:1024`) |
|
|
85
|
-
| Current Stage (`active_stage_names`) | — | `has_any/all_filter`, `doesnt_have_any/all_filter` | `key=active_stage_names`, field_key=`active_stage_names`, values | `page.rb:138-143` | NO |
|
|
86
|
-
|
|
87
|
-
NOTE (`name`): `contains_filter` on `name` routes to `Global::MatchFilter` (`contains_filter.rb:453-455`)
|
|
88
|
-
and `is_filter` on `name` routes to `Global::ExactFilter` on `name.exact` (`is_filter.rb:283-284`).
|
|
89
|
-
The translator's `text_filter`→`match_filter{key:'name',value:}` therefore hits the right backend
|
|
90
|
-
class for partial name search.
|
|
91
|
-
|
|
92
|
-
### 2.2 Register data fields (nested `membranes.<type>` path)
|
|
93
|
-
|
|
94
|
-
| Field type | GraphQL operation(s) | params (key/path/field_key/values) | page.rb evidence | Backend op class | Translator? |
|
|
95
|
-
|---|---|---|---|---|---|
|
|
96
|
-
| **date** (`date.zXXXX`) | `range_date_filter`, `isnt_in_range_date_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.date`; range: gte/lte/gt/lt/time_zone | `page.rb:191-209` | `Pages::RangeDateFilter` `range_date_filter.rb:797`; field_key resolved to `value` `range_date_filter.rb:822-828` | YES (`date_filter` field branch → `range_date_filter`) |
|
|
97
|
-
| **select_str** (`select_str.zXXXX`) | `has_any/all_filter`, `doesnt_have_any/all_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.select_str`, field_key=`selected`, values | `page.rb:279-300` | `Pages::HasAnyFilter`→`BaseHasAnyFilter` `base_has_any_filter.rb:10`; search_key=`path.selected` `base_has_any_filter.rb:42-44` | YES (`options_filter` field branch → `has_any_filter`) |
|
|
98
|
-
| **select_num** (`select_num.zXXXX`) | same as select_str, path=`membranes.select_num` | key, path, field_key=`selected`, values | `page.rb:256-277` | `Pages::HasAnyFilter`→`BaseHasAnyFilter` | YES (field branch; caller passes path/`select_num` prefix) |
|
|
99
|
-
| **plain_text** (`plain_text.zXXXX`) | `is_filter`, `isnt_filter`, `is_empty_filter`, `isnt_empty_filter`, `contains_filter`, `doesnt_contain_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.plain_text`; is_filter → `.exact` subfield | `page.rb:145-165` | `is_filter`→`Pages::IsFilter`→`BaseIsFilter` search_key=`exact` `is_filter.rb:315-316`; contains→`Pages::TextFilter` `contains_filter.rb:459-461` | is_filter YES (exact_filter→is_filter); contains NO |
|
|
100
|
-
| **rich_text** (`rich_text.zXXXX`) | `contains_filter`, `doesnt_contain_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.rich_text`; contains → `content` subfield | `page.rb:303-321` | `contains_filter`→`BaseContainsFilter` field_key=`content` `contains_filter.rb:477-478` | NO (no is/exact op; contains only) |
|
|
101
|
-
| **number** (`number.zXXXX`) | `numeric_range_filter`, `isnt_in_numeric_range_filter`, `is_filter`, `isnt_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.number`; range: gte/lte/gt/lt on `.value` | `page.rb:168-188` | `numeric_range_filter`→`Pages::NumericRangeFilter` search_key=`path.value` `numeric_range_filter.rb:941-943` | is_filter YES; numeric_range NO |
|
|
102
|
-
| **gauge** (`gauge.zXXXX`) | `is_filter`, `isnt_filter`, `numeric_range_filter`, `isnt_in_numeric_range_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.gauge` | `page.rb:234-254` | numeric via `NumericRangeFilter`; is via `IsFilter` search_key=`value` `is_filter.rb:317-318` | is_filter YES; numeric_range NO |
|
|
103
|
-
| **file** (`file.zXXXX`) | `has_any_filter`, `doesnt_have_any_filter` (×`file_container_id`, `content_type`), `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.file`, field_key=`file_container_id`\|`content_type`, values | `page.rb:212-232` | `has_any_filter`→`Pages::File::HasAnyFilter` `has_any_filter.rb:1034-1035` | NO |
|
|
104
|
-
| **image_gallery** (`image_gallery.zXXXX`) | `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.image_gallery` | `page.rb:545-561` | `is_empty`→`Pages::ImageGallery::IsEmptyFilter` `is_empty_filter.rb:572-573` | NO |
|
|
105
|
-
| **people** (`people.zXXXX`) | `has_any/all_filter`, `doesnt_have_any/all_filter` (×`id`/`contractor_organization_id`/`schema_id`/`prefilter_location_ids`), `is_direct_supervisor_filter`, `has_direct_supervisor_filter`, `is/isnt_contractor_administrator_filter`, `people_field_contains_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.people`, field_key varies, values | `page.rb:466-499` | `Pages::People::HasAnyFilter` `has_any_filter.rb:1030-1031` | NO |
|
|
106
|
-
| **contractor_entities** (`contractor_entities.zXXXX`) | `has_any/all_filter`, `doesnt_have_any/all_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.contractor_entities`, field_key=`contractor_entities_ids`, values | `page.rb:324-344` | `BaseHasAnyFilter`; `required_data` loads ContractorEntity `base_has_any_filter.rb:27-28` | NO |
|
|
107
|
-
| **cross_reference / page_reference** (`cross_reference.zXXXX`, `any_reference_field`) | `has_any_filter`, `doesnt_have_any_filter`, `has_all_filter`, `doesnt_have_all_filter`, `meets_conditions_filter`(if CRR), `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.cross_references`, field_key=`id`, values=`@page_id`; meets_conditions: `register_id` | `page.rb:502-542` | `Pages::CrossReferences::HasAnyFilter` `has_any_filter.rb:1021-1022` | NO |
|
|
108
|
-
| **actions_list** (`actions_list.zXXXX`) | `has_any/all_filter`, `doesnt_have_any/all_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.actions_list`, field_key=`id`, values=`@action_id` | `page.rb:347-367` | `Pages::ActionsList::HasAnyFilter` `has_any_filter.rb:1032-1033` | NO |
|
|
109
|
-
| **checklist** (`checklist.zXXXX`) | `all_options_ticked_filter`, `not_all_options_ticked_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.checklist` | `page.rb:445-463` | `all_options_ticked_filter` = `Pages::AllOptionsTickedFilter` (`all_options_ticked_filter.rb`) | NO |
|
|
110
|
-
| **geo** (`geo.zXXXX`) | `is_filter`, `isnt_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.geo`; is → `address.exact` subfield | `page.rb:370-388` | `IsFilter` search_key=`address.exact` `is_filter.rb:319-320` | is_filter YES (if driven); rest NO |
|
|
111
|
-
| **signature** (`signature.zXXXX`) | `is_filter`, `isnt_filter` (value=`@user_id`), `range_date_filter`, `isnt_in_range_date_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.signature`; is → `signed_by_id`; range → `date` subfield | `page.rb:391-411` | `IsFilter` search_key=`signed_by_id` `is_filter.rb:323-324`; range → `Pages::Signature::RangeDateFilter` `range_date_filter.rb:810-811` | is_filter/range partial (translator emits generic ops; subfield resolution is backend-side by key prefix) |
|
|
112
|
-
| **mailbox** (`mailbox.zXXXX`) | `range_date_filter`, `isnt_in_range_date_filter`, `has_any/all_filter`, `doesnt_have_any/all_filter` (×`all_recipients`/`sender_email`), `contains_filter`, `doesnt_contain_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.mailbox`, field_key varies, values | `page.rb:414-442` | `Pages::Mailbox::*` `has_any_filter.rb:1019-1020` | NO |
|
|
113
|
-
| **law** (`law.zXXXX`) | `has_any_filter`, `doesnt_have_any_filter`, `is_empty_filter`, `isnt_empty_filter`, `presence_filter`, `not_presence_filter` | key, path=`membranes.law`, field_key=`snippets_ids`, values=`@law_snippet_id` | `page.rb:564-582` | `BaseHasAnyFilter`; `required_data` loads Snippet `base_has_any_filter.rb:29` | NO |
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 3. Operation → param contract (backend constructor signatures)
|
|
118
|
-
|
|
119
|
-
Cited so the guard spec asserts exact param shapes. All under
|
|
120
|
-
`app/services/new_ep/es/filters/`.
|
|
121
|
-
|
|
122
|
-
| Operation | Namespace / class | Constructor params (evidence) |
|
|
123
|
-
|---|---|---|
|
|
124
|
-
| `date_filter` | `Global::DateFilter` | `key, path?, gt?, gte?, lt?, lte?` — `global/date_filter.rb:28` |
|
|
125
|
-
| `range_date_filter` | `Pages::RangeDateFilter` | `key, path?, gte?, lte?, gt?, lt?` — `pages/range_date_filter.rb:797` |
|
|
126
|
-
| `is_filter` | `Pages::IsFilter` | `key, path?, value?` — `pages/is_filter.rb:258` |
|
|
127
|
-
| `isnt_filter` | `Pages::IsntFilter` | `key, path?, value` — `pages/isnt_filter.rb:360` |
|
|
128
|
-
| `match_filter` | `Global::MatchFilter` | `key, value, path?` — `global/match_filter.rb:6` |
|
|
129
|
-
| `contains_filter` | `Pages::ContainsFilter` | `key, path?, value` — `pages/contains_filter.rb:445` |
|
|
130
|
-
| `has_any_filter` | `Pages::HasAnyFilter` → `BaseHasAnyFilter` | `key, path?, field_key?, values` — `pages/has_any_filter.rb:995`, `pages/base_has_any_filter.rb:10` |
|
|
131
|
-
| `has_all_filter` | `Pages::HasAllFilter` → `BaseHasAllFilter` | `key, path?, field_key?, values` — `pages/has_all_filter.rb:1135`, `pages/base_has_all_filter.rb:59` |
|
|
132
|
-
| `doesnt_have_any_filter` | `Pages::DoesntHaveAnyFilter` | `key, path?, field_key?, values` (invert) — `pages/doesnt_have_any_filter.rb:1061` |
|
|
133
|
-
| `doesnt_have_all_filter` | `Pages::DoesntHaveAllFilter` | `key, path?, field_key?, values` (invert) — `pages/doesnt_have_all_filter.rb:1199` |
|
|
134
|
-
| `one_of_filter` | `Global::OneOfFilter` | `key, values` — `global/one_of_filter.rb:16` |
|
|
135
|
-
| `none_of_filter` | `Global::NoneOfFilter` | `key, values` — `global/none_of_filter.rb:6` |
|
|
136
|
-
| `numeric_range_filter` | `Pages::NumericRangeFilter` | `key, path?, gte?, lte?, gt?, lt?` — `pages/numeric_range_filter.rb:917` |
|
|
137
|
-
| `is_empty_filter` | `Pages::IsEmptyFilter` | `key, path?` (search_key by type) — `pages/is_empty_filter.rb:552` |
|
|
138
|
-
| `presence_filter` | `Pages::PresenceFilter` | `key, path?` — `pages/presence_filter.rb:724` |
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 4. Known-UNCOVERED field types / operations (visible gap, not silent)
|
|
143
|
-
|
|
144
|
-
The compat `FilterTranslator` intentionally translates only the v2 filter shapes that real
|
|
145
|
-
consumers emit (`date_filter`, `text_filter`, `exact_filter`/`is_filter`, `options_filter`,
|
|
146
|
-
plus passthroughs). The following register field types / operations have a backend contract
|
|
147
|
-
(cataloged above) but NO translator branch. This is by design — do not add speculative
|
|
148
|
-
translations without a consumer. The guard spec enumerates these so the gap stays visible.
|
|
149
|
-
|
|
150
|
-
**Field types with NO dedicated translator path** (any v2 filter targeting them falls through
|
|
151
|
-
to passthrough — the caller must pass a pre-shaped `{operation:,params:}` hash):
|
|
152
|
-
|
|
153
|
-
- `number` (numeric_range_filter), `gauge` (numeric_range_filter)
|
|
154
|
-
- `rich_text` (contains_filter / doesnt_contain_filter)
|
|
155
|
-
- `file` (has_any_filter on file_container_id / content_type)
|
|
156
|
-
- `image_gallery` (is_empty / presence only)
|
|
157
|
-
- `people`, `contractor_entities`, `cross_reference` / `page_reference`
|
|
158
|
-
- `actions_list`, `checklist`, `mailbox`, `law`, `signature` (range/is subfields)
|
|
159
|
-
- `geo` (is_filter on address.exact — reachable via exact_filter if the caller sets key `geo.zXXXX`)
|
|
160
|
-
|
|
161
|
-
**System fields with NO translator driver:** `assigned_to`, `creator_id` (Created by),
|
|
162
|
-
`active_stage_names` (Current Stage), `mould_counter` contains_filter.
|
|
163
|
-
|
|
164
|
-
**Operations never emitted by the translator** (must be passed pre-shaped): `has_all_filter`,
|
|
165
|
-
`doesnt_have_all_filter`, `numeric_range_filter`, `isnt_in_numeric_range_filter`,
|
|
166
|
-
`isnt_in_range_date_filter`, `contains_filter`, `doesnt_contain_filter`, `is_empty_filter`,
|
|
167
|
-
`isnt_empty_filter`, `presence_filter`, `not_presence_filter`, `meets_conditions_filter`,
|
|
168
|
-
`all_options_ticked_filter`, `people_field_contains_filter`, direct-supervisor / contractor-admin
|
|
169
|
-
filters.
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## 5. Change log
|
|
174
|
-
|
|
175
|
-
- 2026-07-08 — Initial matrix built from `page.rb` + `filters/pages/**` + `filters/global/**`.
|
|
176
|
-
Guard spec `spec/ecoportal/api/graphql/compat/filter_translator_spec.rb` extended to pin every
|
|
177
|
-
emitted operation to its backend param contract and to enumerate the uncovered types.
|