Skip to content

Architecture

ETLPipeline (blocking) --runs on a background event loop--> AsyncETLPipeline.run(query)
                                                                  |
  +---------------------------------------------------------------+
  v
AsyncExtractor.fetch_page(cursor) --> ListingPage --> records neither processed nor quarantined
                                                  |  (max_concurrency at a time)
                                                  v
                          AsyncRelevanceFilter.is_relevant --no--> mark processed
                                                  | yes
                                                  v
                          AsyncExtractor.fetch_full_text (best source -> abstract)
                                                  |
                                                  +--> MemoryIngestor (optional)
                                                  |      AsyncChunkIngestor: TextChunker -> AsyncEmbedder
                                                  |        -> AsyncEmbeddingStore
                                                  |      AsyncSearchIndexer: AsyncTextSearchStore
                                                  |      AsyncCompositeIngestor: several at once
                                                  v
                          AsyncEntityExtractor.extract_record(record, text)
                                                  |      (typed entities; rejections with reasons)
                                                  v
                          AsyncExporter.write(record, entities) --durable_writes--> mark processed

Before the first page: AsyncExporter.open()
After each page: AsyncExporter.flush() --> mark the records it made durable;
                 failed attempts to AsyncStateManager.record_failure (R18);
                 AsyncStateManager.save_metadata(cursor, truncated, newest-first head)
When the run ends: AsyncExporter.flush(), AsyncExporter.aclose(), AsyncStateManager.flush();
                   RunFinished(metrics) to on_event
Throughout: ShutdownSignal stops new records; on_event receives RunStarted, PageFetched,
            RecordFinished, PageFinished

Search:    AsyncHybridSearcher.search(query) --> AsyncTextSearchStore.search (BM25) -------+--> fusion --> SearchOutcome
                                             --> AsyncSimilarArticleFinder (vector memory) -+
Discovery: build_discovery_graph(seed) --> AsyncEdgeSource.neighbours --> DiscoveryGraph --> filter_graph
Backfill:  AsyncEmbeddingStore.iter_records --> merge_passages --> AsyncTextSearchStore.index

Layers

Layer Interface Implementations Import from
Extract AsyncExtractor AsyncArxivExtractor, AsyncPubMedExtractor, AsyncSemanticScholarExtractor, AsyncOpenAlexExtractor sci_etl_core.extractors
Parse Parser, TableParser PdfPlumberParser, LatexTarballParser, HtmlTextParser, DocxParser, JatsXmlParser sci_etl_core.parsers
LLM AsyncLLMClient AsyncOpenAICompatibleClient, CachingLLMClient sci_etl_core.llm
LLM cache AsyncLLMResponseCache InMemoryLLMResponseCache, AsyncSqliteLLMResponseCache sci_etl_core.llm
Relevance AsyncRelevanceFilter AsyncLLMRelevanceFilter, AsyncEmbeddingRelevanceFilter sci_etl_core.llm
Entities AsyncEntityExtractor AsyncLLMEntityExtractor (dicts, or Pydantic models with schema=) sci_etl_core.llm
Export AsyncExporter AsyncCsvExporter, AsyncJsonlExporter, AsyncClaimStoreExporter sci_etl_core.exporters, sci_etl_core.claims
Claims (provisional) AsyncClaimStore, AsyncRejectionStore AsyncLLMClaimExtractor; InMemoryClaimStore, AsyncSqliteClaimStore; InMemoryRejectionStore, AsyncSqliteRejectionStore; locate_quote sci_etl_core.claims
State AsyncStateManager AsyncFileStateManager, AsyncSqliteStateManager sci_etl_core.state
Embeddings AsyncEmbedder AsyncOpenAIEmbedder, AsyncSentenceTransformerEmbedder sci_etl_core.embeddings
Chunking TextChunker SlidingWindowChunker sci_etl_core.embeddings
Vector memory AsyncEmbeddingStore InMemoryEmbeddingStore, AsyncSqliteEmbeddingStore sci_etl_core.embeddings
Memory ingest MemoryIngestor AsyncChunkIngestor, AsyncSearchIndexer, AsyncCompositeIngestor sci_etl_core.embeddings, sci_etl_core.search, sci_etl_core
Text search AsyncTextSearchStore InMemoryTextSearchStore, AsyncSqliteFts5Store; MetadataFilter, RangeFilter; backfill_text_index sci_etl_core.search
Fusion FusionStrategy reciprocal_rank_fusion, normalized_score_fusion; AsyncHybridSearcher sci_etl_core.search
Discovery graph AsyncEdgeSource EmbeddingEdgeSource, MetadataEdgeSource; build_discovery_graph, filter_graph sci_etl_core.search
Post-processing Processor, RecordValidator ProcessorChain, NormalizationStep, DeduplicationStep, ClusteringStep, CompletenessStep, QualityFlagStep, ValueClipStep, TableLayoutStep; NumericRangeValidator, KeywordExclusionValidator, CompositeValidator sci_etl_core.processors
Table sinks TableSink SqlTableSink, Plotly3DSink sci_etl_core.processors.sinks
Orchestration — AsyncETLPipeline, ETLPipeline sci_etl_core

The pipelines, stage interfaces, and most implementations are also re-exported from sci_etl_core itself; parser implementations, processor steps, validators, and claims come from their subpackages. Supporting modules: sci_etl_core.config, sci_etl_core.http_async (build_async_client), sci_etl_core.rate_limiter (including HostRateLimiter), sci_etl_core.signals, sci_etl_core.observability (progress events and RunMetrics), sci_etl_core.exceptions, and sci_etl_core.discovery (the read-model for user interfaces). Every module logs through the standard logging module; see Logging. Every package loads its public names on first access, so importing one component never requires another component's optional dependencies.

The API reference documents each module in detail.