一键导入
minoo-ingestion
Use when working on Minoo ingestion pipeline, mappers, materializer, or ingest logs in src/Ingestion/ or tests relating to ingestion
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when working on Minoo ingestion pipeline, mappers, materializer, or ingest logs in src/Ingestion/ or tests relating to ingestion
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when working on Minoo HTTP controllers, routing, or request handling in src/Http/Controller/ (or src/Http/Middleware/) or route definitions in src/Provider/
Use when working on Minoo entity types, access policies, service providers, or seed data in src/ or tests/Minoo/
Use when working on Minoo templates, CSS design system, or SSR rendering in templates/, public/css/, or src/Controller/ render methods
Use when working on Minoo search, autocomplete, or NorthCloud search integration in src/Search/ or SearchServiceProvider
| name | minoo:ingestion |
| description | Use when working on Minoo ingestion pipeline, mappers, materializer, or ingest logs in src/Ingestion/ or tests relating to ingestion |
Files: src/Ingestion/, src/Provider/IngestServiceProvider.php
Tests: tests/Minoo/Unit/Ingest/, tests/Minoo/Integration/IngestPipelineTest.php
Fixtures: tests/fixtures/ojibwe_lib/
NorthCloud Envelope (JSON)
→ PayloadValidator.validate() → ValidationResult (errors[])
→ EntityMapper.map() → Value Object (typed fields)
→ IngestImporter.import() → IngestLog (status: pending_review)
--- human approval ---
→ IngestMaterializer.materialize() → MaterializationResult (created[], skipped[])
→ Entities persisted via EntityTypeManager
PayloadValidator
public function validate(array $envelope): ValidationResult;
// ValidationResult::isValid(): bool, ::getErrors(): string[]
IngestImporter
public function import(array $envelope): IngestLog;
// Always returns IngestLog — failed validation → status='failed', error_message set
// Valid payload → status='pending_review', payload_raw + payload_parsed stored as JSON
IngestMaterializer
public function materialize(IngestLog $log, bool $dryRun = false): MaterializationResult;
// dryRun=true: previews without persisting (primaryEntityId = null)
// Throws RuntimeException on JSON decode failure
MaterializationResult
addCreated(string $type, array $fields, ?int $id = null): void;
addSkipped(string $type, string $key, string $reason): void;
addUpdated(string $type, int $id, array $fields): void;
setPrimaryEntityId(int $id): void;
getPrimaryEntityId(): ?int;
getCreated(): array; getSkipped(): array; getUpdated(): array;
MaterializationContext — deduplication within a single run:
getSpeakerId(string $code): ?int; setSpeakerId(string $code, int $id): void;
getWordPartId(string $form, string $type): ?int; setWordPartId(string $form, string $type, int $id): void;
All mappers return typed value objects with toArray(): array (snake_case keys).
DictionaryEntryMapper — map(array $data, string $sourceUrl): DictionaryEntryFields
lemma → word, definition → definition (array joined with "; "), part_of_speech → partOfSpeechstem → stem, language_code → languageCode (default 'oj'), inflected_forms → JSON encodedslug via SlugGenerator, sets status: 0, timestamps to time()SpeakerMapper — map(array $data): SpeakerFields
name → name, code → code, bio → bio (nullable)status: 1 (published), generates slug from namefromCode(string $code): SpeakerFields — minimal speaker for implicit creationWordPartMapper — map(array $data, string $sourceUrl): ?WordPartFields
null if morphological_role not in ['initial', 'medial', 'final']form → form, morphological_role → type, definition → definitionExampleSentenceMapper — map(array $data, int $dictionaryEntryId, ?int $speakerId, string $languageCode): ExampleSentenceFields
ojibwe_text, english_text, audio_url, source_sentence_id mapped directlydictionaryEntryId and speakerId as parametersCulturalCollectionMapper — map(array $data, string $sourceUrl): CulturalCollectionFields
strip_tags() → normalize whitespacetitle, description, source_attribution (nullable), sourceUrlIngestMaterializer::materialize() dispatches by entity_type_target:
Dictionary entry materialization:
example_sentences via getOrCreateSpeaker() — dedup by codeword_parts via getOrCreateWordPart() — dedup by "$form|$type"Get-or-create pattern:
$existingId = $context->getSpeakerId($code);
if ($existingId !== null) return $existingId;
// Query storage for existing by code
// If not found: create new entity, save, cache in context
$context->setSpeakerId($code, $id);
ingest_log, keys: ['id' => 'ilid', 'uuid' => 'uuid', 'label' => 'title']pending_review → approved → (materialized) | rejected | failedsource, entity_type_target, entity_id, payload_raw, payload_parsed, error_message, reviewed_by, reviewed_atUnit tests — mapper output verification:
$mapper = new DictionaryEntryMapper(new SlugGenerator());
$fields = $mapper->map($data, 'https://source.url');
$this->assertSame('expected_word', $fields->word);
Materializer dry-run — mock EntityTypeManager, verify never() calls:
$storage->expects($this->never())->method('create');
$result = $materializer->materialize($log, dryRun: true);
$this->assertNull($result->getPrimaryEntityId());
Integration test — full pipeline with in-memory SQLite:
putenv('WAASEYAA_DB=:memory:');
// Import from JSON fixture → materialize → load entity → assert fields
dryRun parameter: Always test both paths — dry run returns null primaryEntityIdpayload_raw and payload_parsed both use JSON_THROW_ON_ERROR for encode/decodemorphological_role returns null — callers must handlestatus: 1 (published), all other types default to status: 0docs/specs/ingestion-pipeline.md — full pipeline architecture, envelope format, field mappingsdocs/specs/entity-model.md — entity type definitions, field definitionswaaseyaa_get_spec entity-system — EntityBase, storage, query builder