muninn

package module
v0.2.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 12 Imported by: 0

README

Muninn

Go Reference

Muninn is a Go library for searching French public procurement data through a single, normalized API.

It queries compatible data sources concurrently, merges strong cross-source matches, preserves source provenance, and returns partial results when one source is unavailable. Muninn is a library only: it does not require a database, run a server, or impose a cache.

Features

  • Federated search across BOAMP, BEAUAMP, and DECP
  • One normalized Tender model for notices and awarded contracts
  • Exact DECP filtering across all awarded suppliers by SIRET
  • Capability-aware routing for exact, approximate, and unsupported filters
  • Concurrent provider calls with partial-result reporting
  • Conservative cross-source consolidation with raw source provenance
  • Deterministic sorting and opaque cursor pagination
  • Optional, auditable BEAUAMP attribution context for open BOAMP notices
  • Configurable HTTP clients, timeouts, and retry policies
  • No third-party runtime dependencies

Requirements

Muninn currently targets Go 1.26.

The built-in providers call public remote APIs, so applications should always set an appropriate context deadline and account for upstream availability and rate limits.

Installation

go get github.com/kvitrvn/muninn

Quick start

Create an engine with the providers your application needs, then call Engine.Search:

package main

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/kvitrvn/muninn"
	"github.com/kvitrvn/muninn/beauamp"
	"github.com/kvitrvn/muninn/boamp"
	"github.com/kvitrvn/muninn/decp"
)

func main() {
	engine := muninn.NewEngine(
		boamp.New(),
		beauamp.New(),
		decp.New(),
	)

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	result, err := engine.Search(ctx, muninn.Query{
		Keywords:  []string{"GED"},
		ObjetOnly: true,
		Statuses:  []muninn.TenderStatus{muninn.StatusOpen},
		Sort:      muninn.Sort{Field: muninn.SortByDeadline},
		PageSize:  25,
		Enrichment: &muninn.EnrichmentOptions{},
	})
	if err != nil {
		log.Fatal(err)
	}

	for _, tender := range result.Items {
		fmt.Printf(
			"[%s] %s — deadline: %s\n",
			strings.Join(tender.ProviderNames(), "+"),
			tender.Objet,
			tender.DateLimiteReponse.Format(time.DateOnly),
		)
	}

	for _, warning := range result.Warnings {
		log.Printf(
			"provider %s (%s): %s",
			warning.Provider,
			warning.Code,
			warning.Message,
		)
	}
}

Search returns an error for an invalid query, a canceled context, an invalid cursor, no compatible provider, or when every attempted provider fails. If at least one provider succeeds, its results are returned even when another provider fails; in that case SearchResult.Partial is true and SearchResult.Warnings explains why.

Choosing providers

Providers can be used together through Engine or directly through their package clients.

Package Data source Best suited for Important limitations
boamp Official BOAMP notices Active notices, response deadlines, departments, and historical award notices Supplier SIREN matching is approximate because legacy winners are identified by name; amount and supplier SIRET filtering are unsupported
beauamp Enriched BOAMP data on data.gouv.fr Structured buyer and supplier names/SIRENs, CPV codes, and indicative amounts Supplier SIRET, department, and deadline filters are unsupported; supplier identity and other enriched fields remain indicative
decp Essential public procurement data (DECP) Published awarded contracts, exact supplier SIRENs/SIRETs, and reference amounts Contains awards rather than active notices; department, deadline, open, and closed filters are unsupported

The engine inspects each provider's Capabilities before searching:

  • providers with exact support are queried normally;
  • approximate support is accepted and reported as a warning;
  • providers that cannot satisfy a required filter are skipped and reported;
  • if no provider can execute the query, Search returns muninn.ErrNoCapableProvider.

Calling a provider directly is stricter. An unsupported criterion returns *muninn.UnsupportedFilterError instead of being ignored. Pagination and sorting belong to Engine and are also rejected by direct provider calls.

Building queries

Query fields are combined with AND. Values within the same slice are combined with OR, except when MatchAll requires every keyword.

Field Description
Keywords Text terms to search for
ObjetOnly Restrict keyword matching to the normalized title/object
MatchAll Require every keyword instead of any keyword
Departements Buyer department codes
PublishedFrom, PublishedTo Inclusive publication date range
DeadlineFrom, DeadlineTo Inclusive response deadline range
CPVCodes CPV prefixes
MontantMin, MontantMax Known contract amount range in euros
BuyerSIREN Exact nine-digit buyer SIREN
SupplierSIREN Nine-digit legal-entity SIREN of any awarded supplier, across its establishments
SupplierSIRET Exact 14-digit SIRET of any awarded supplier
NoticeTypes Competition, award, or correction notices
Statuses Open, closed, or awarded tenders
OpenAt Time used to derive open and closed status; zero means now
Sort Publication date descending by default, or deadline ascending
PageSize Results per page; 50 by default and 200 maximum
Cursor Opaque cursor returned by a previous search
CandidateLimit Maximum records collected per provider; zero uses a safe provider default
Enrichment Optional BEAUAMP attribution context for the returned page; nil disables it

Use Query.Validate when you want to reject invalid user input before making a network call:

query := muninn.Query{
	BuyerSIREN:    "200055703",
	SupplierSIREN: "123456789",
	MontantMin:    50_000,
	MontantMax:    250_000,
	PageSize:      50,
}

if err := query.Validate(); err != nil {
	// err is a *muninn.ValidationError
}
Open tenders

StatusOpen is a derived lifecycle state. A tender is open when it is a competition or correction notice, has a known response deadline, has not passed that deadline, and has no known awarded supplier.

For reproducible searches, set Query.OpenAt. When it is zero, the engine uses the current time and stores that value in the pagination cursor.

Auditable BEAUAMP enrichment

For an open-tender search, BOAMP remains the only source used to determine whether a notice is still answerable. Setting Query.Enrichment makes a configured beauamp.Client run only after sorting and pagination, on the current BOAMP page:

result, err := engine.Search(ctx, muninn.Query{
	Statuses: []muninn.TenderStatus{muninn.StatusOpen},
	OpenAt:   time.Now(),
	Enrichment: &muninn.EnrichmentOptions{
		HistoryMonths: 24, // default 24, maximum 60
		HistoryLimit:  5,  // default 5, maximum 50
	},
})

The enrichment keeps three concepts separate:

  • ExactRelations: a BEAUAMP award carrying the same BOAMP award ID, or a source-reported contract-notice ID;
  • Candidates: same buyer SIREN and CPV, compatible dates, and object-token Jaccard similarity of at least 0.85; these are never merged automatically;
  • BuyerHistory: recent awarded notices for the same buyer and CPV family.

Every relation includes its confidence and evidence. Conflicts reports, for example, an indicative BEAUAMP award linked to a BOAMP notice that is still open. BEAUAMP never changes the BOAMP status, deadline, primary Items, Total, TotalExact, Partial, or main Warnings. Coverage gaps, resource errors, and truncation are reported only under SearchResult.Enrichment.

BEAUAMP rows are loaded once per selected resource with all page buyer SIRENs. Monthly resources cover the requested history, with annual files used after the publisher removes a completed year's monthly files. Current-month daily resources are included and streamed from CSV when the tabular API has not indexed them.

result, err := muninn.NewEngine(boamp.New()).Search(ctx, muninn.Query{
	Keywords: []string{"electronic archiving"},
	Statuses: []muninn.TenderStatus{muninn.StatusOpen},
	OpenAt:   time.Date(2026, time.July, 28, 0, 0, 0, 0, time.UTC),
	Sort:     muninn.Sort{Field: muninn.SortByDeadline},
})
Pagination

Pass NextCursor back with the same query to fetch the next page:

query := muninn.Query{
	Keywords: []string{"cybersecurity"},
	PageSize: 20,
}

for {
	result, err := engine.Search(ctx, query)
	if err != nil {
		return err
	}

	consume(result.Items)

	if result.NextCursor == "" {
		break
	}
	query.Cursor = result.NextCursor
}

Cursors are tied to the complete query. Changing a filter or sort order while reusing a cursor returns *muninn.ValidationError. A cursor may also become stale if the upstream result set changes between pages.

Working with results

SearchResult contains:

Field Meaning
Items Current page of normalized tenders
Total Consolidated matches before pagination
TotalExact Whether the total is unaffected by warnings or skipped providers
NextCursor Opaque cursor for the next page, or an empty string
Partial Whether warnings affected coverage or precision
Warnings Machine-readable provider warnings
Enrichment Optional BEAUAMP context and its independent coverage, partial flag, and warnings

Warnings use stable codes:

  • muninn.WarningProviderError
  • muninn.WarningUnsupportedFilter
  • muninn.WarningApproximateFilter
  • muninn.WarningTruncated
  • muninn.WarningCoverageGap
  • muninn.WarningResourceError

Each Tender keeps all contributing source records:

type SourceReference struct {
	Provider   string
	ID         string
	RecordID   string
	RelatedIDs []string
	URL        string
	RawFields  map[string]any
}

Use Tender.ProviderNames for display and Tender.Sources when you need the native identifier, row identifier, related identifiers, source URL, or raw source fields. Tender.Lots preserves BEAUAMP lot-level suppliers, CPV codes, amounts, and source rows while the existing flat fields remain available.

Awarded contractors are exposed through Tender.Suppliers. DECP preserves the native titulaire order, accepts SIREN and SIRET identifiers, ignores unrelated identifier schemes, and removes duplicates. BOAMP maps both legacy result notices and eForms winners; when a legacy notice only contains a company name, SupplierSIREN resolves official company names and reports approximate support. Consolidation keeps distinct establishments while using their common SIREN to relate records across sources.

Migration note: Tender.Supplier Buyer was replaced by Tender.Suppliers []Buyer. Callers must iterate the list instead of reading a single awarded contractor.

Muninn merges records only when there is strong cross-source evidence. When evidence is incomplete, it keeps separate tenders rather than risk a false merge.

HTTP and retry configuration

Each built-in provider accepts a custom *http.Client and retry.Policy. Defaults are suitable for basic usage, but production applications will usually want their own transport, observability, and deadlines.

httpClient := &http.Client{
	Timeout: 15 * time.Second,
}
retryPolicy := retry.Policy{
	MaxAttempts: 4,
	BaseDelay:   200 * time.Millisecond,
	MaxDelay:    3 * time.Second,
}

engine := muninn.NewEngine(
	boamp.New(
		boamp.WithHTTPClient(httpClient),
		boamp.WithRetryPolicy(retryPolicy),
	),
	decp.New(
		decp.WithHTTPClient(httpClient),
		decp.WithRetryPolicy(retryPolicy),
	),
	beauamp.New(
		beauamp.WithHTTPClient(httpClient),
		beauamp.WithRetryPolicy(retryPolicy),
	),
)

Retries apply to HTTP 429 responses, HTTP 5xx responses, and transient network errors. Context cancellation and deadlines stop retries immediately. The default policy makes four attempts in total with exponential backoff, full jitter, and a five-second cap.

Provider-specific options include:

  • decp.WithDataset to select another DECP dataset;
  • beauamp.WithResources to pin exact tabular resource IDs;
  • beauamp.WithResourceCacheTTL to control resource-catalog caching;
  • base URL options for tests and controlled upstream proxies.

Error handling

Use errors.Is for sentinel and context errors, and errors.As for structured validation errors:

result, err := engine.Search(ctx, query)
if err != nil {
	var validationErr *muninn.ValidationError

	switch {
	case errors.As(err, &validationErr):
		// Invalid field or cursor.
	case errors.Is(err, muninn.ErrNoProviders):
		// The engine was created without providers.
	case errors.Is(err, muninn.ErrNoCapableProvider):
		// Every provider was incompatible with the query.
	case errors.Is(err, context.Canceled),
		errors.Is(err, context.DeadlineExceeded):
		// The request was canceled or timed out.
	default:
		// Every attempted provider failed.
	}
}

Provider warnings are not returned as error when usable results exist. Check result.Partial and inspect result.Warnings after every successful search if coverage matters to your application.

Implementing a provider

Implement muninn.Provider to add an internal data source or another public API:

type Provider interface {
	Name() string
	Capabilities() muninn.Capabilities
	Search(context.Context, muninn.Query) (muninn.ProviderResult, error)
}

ProviderResult.Total describes matches in that provider before federation. Set TotalExact to describe the source count and Truncated when collection stopped before all matches were fetched.

Provider implementations should:

  • validate the query with muninn.ValidateProviderQuery and Query.Validate;
  • reject unsupported filters with muninn.ValidateCapabilities;
  • honor context cancellation;
  • return normalized Tender values with at least one SourceReference;
  • avoid implementing engine-owned pagination and sorting.

Known limitations

  • BOAMP is authoritative, but some fields extracted from recent eForms payloads remain best effort. Historical supplier-SIREN searches also depend on the official company-search API and name matching, so the engine emits an approximate-filter warning.
  • BEAUAMP data is indicative attribution context, not a second proof that a market is open. Its tabular API only supports a subset of the source data and filters.
  • DECP contains awarded contracts, not the complete active-notice lifecycle.
  • Search completeness depends on upstream availability, pagination limits, API rate limits, and schema stability.
  • Muninn does not provide persistence, caching of search results, an HTTP API, or a background index.

Documentation

Development

go test ./...
go test -race ./...
go vet ./...

License

Muninn is distributed under the MIT License.

Documentation

Overview

Package muninn provides a federated Go search engine for French public procurement data.

Providers declare their capabilities and return normalized ProviderResult values. Engine queries compatible providers concurrently, preserves partial results, consolidates strong cross-source matches, applies common lifecycle filters, and returns a deterministic SearchResult page.

The built-in providers live in the boamp, beauamp and decp subpackages. Applications compose only the sources they need:

engine := muninn.NewEngine(boamp.New(), beauamp.New(), decp.New())
result, err := engine.Search(ctx, muninn.Query{
	Keywords:  []string{"GED"},
	ObjetOnly: true,
	PageSize:  25,
})

Tender.Sources preserves the native notice ID, row ID, related IDs, URL and raw payload of every source contributing to a consolidated record. Optional BEAUAMP enrichment is secondary and auditable: it never changes the BOAMP result used to decide that a tender is open. Muninn deliberately favors missed merges over false merges when cross-source evidence is incomplete.

Index

Examples

Constants

View Source
const (
	DefaultEnrichmentHistoryMonths  = 24
	MaxEnrichmentHistoryMonths      = 60
	DefaultEnrichmentHistoryLimit   = 5
	MaxEnrichmentHistoryLimit       = 50
	DefaultEnrichmentCandidateLimit = 10000
)
View Source
const (
	// DefaultPageSize is used by Engine when Query.PageSize is zero.
	DefaultPageSize = 50
	// MaxPageSize prevents a caller from accidentally materializing an
	// unbounded response.
	MaxPageSize = 200
)

Variables

View Source
var (
	// ErrNoProviders means Engine was constructed without a source.
	ErrNoProviders = errors.New("muninn: no providers configured")
	// ErrNoCapableProvider means every provider was incompatible with at least
	// one requested filter.
	ErrNoCapableProvider = errors.New("muninn: no provider supports the query")
)

Functions

func ValidateCapabilities

func ValidateCapabilities(q Query, caps Capabilities) error

ValidateCapabilities checks that caps can execute every criterion in q. Approximate support is accepted and discoverable through Capabilities.

func ValidateProviderQuery

func ValidateProviderQuery(q Query) error

ValidateProviderQuery rejects pagination and ordering fields that are owned by Engine. Built-in providers call it so direct usage never ignores them.

Types

type AvisType

type AvisType int

AvisType distinguishes a call-for-competition notice from an award/result notice.

const (
	AvisInconnu AvisType = iota
	AvisAppelConcurrence
	AvisAttribution
	AvisRectificatif
)

func (AvisType) String

func (a AvisType) String() string

type Buyer

type Buyer struct {
	Nom             string
	SIRET           string
	SIREN           string
	Ville           string
	CodeDepartement string
}

Buyer represents an economic actor of a tender: either the public buyer or one of the awarded contractors (titulaires) in Tender.Suppliers.

func (Buyer) SIREN9

func (b Buyer) SIREN9() string

SIREN9 returns the 9-digit SIREN identifying the legal entity, derived from SIREN when set, otherwise from the first 9 digits of SIRET (a SIRET is a SIREN plus a 5-digit establishment number). It returns "" when neither yields a plausible SIREN. This is the stable key used to relate an actor across sources (a buyer or a supplier keeps its SIREN, its SIRET may vary per site).

type Capabilities

type Capabilities map[Filter]SupportLevel

Capabilities declares provider behavior. Missing entries are unsupported.

func (Capabilities) Support

func (c Capabilities) Support(filter Filter) SupportLevel

Support returns the declared support level for a filter.

type EngagementType

type EngagementType int

EngagementType is the contractual engagement type: firm contract or framework agreement (purchase-order based or with subsequent contracts). This is a separate axis from the procedure type, and the two combine.

const (
	EngagementInconnu EngagementType = iota
	EngagementFerme
	EngagementAccordCadreBC // purchase-order framework agreement
	EngagementAccordCadreMS // subsequent-contract framework agreement
)

func (EngagementType) String

func (e EngagementType) String() string

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine federates providers, consolidates their records, applies normalized filters, and returns a deterministic page.

func NewEngine

func NewEngine(providers ...Provider) *Engine

NewEngine constructs a federated search engine.

func (*Engine) Search

func (e *Engine) Search(ctx context.Context, q Query) (SearchResult, error)

Search queries compatible providers concurrently. A provider failure is non-fatal when another provider succeeds; the response is then marked partial and carries a machine-readable warning.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/kvitrvn/muninn"
)

type staticProvider struct {
	name    string
	tenders []muninn.Tender
}

var _ muninn.Provider = staticProvider{}

func (p staticProvider) Name() string { return p.name }

func (p staticProvider) Capabilities() muninn.Capabilities {
	return muninn.Capabilities{
		muninn.FilterTitleKeywords: muninn.Exact,
		muninn.FilterNoticeType:    muninn.Exact,
		muninn.FilterStatusOpen:    muninn.Exact,
	}
}

func (p staticProvider) Search(_ context.Context, _ muninn.Query) (muninn.ProviderResult, error) {
	return muninn.ProviderResult{
		Items:      p.tenders,
		Total:      len(p.tenders),
		TotalExact: true,
	}, nil
}

func main() {
	p := staticProvider{name: "interne", tenders: []muninn.Tender{
		{
			Sources:           []muninn.SourceReference{{Provider: "interne", ID: "1"}},
			Objet:             "Solution GED",
			AvisType:          muninn.AvisAppelConcurrence,
			DatePublication:   time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC),
			DateLimiteReponse: time.Date(2026, 8, 15, 0, 0, 0, 0, time.UTC),
		},
	}}

	result, err := muninn.NewEngine(p).Search(context.Background(), muninn.Query{
		Keywords:  []string{"GED"},
		ObjetOnly: true,
		Statuses:  []muninn.TenderStatus{muninn.StatusOpen},
		OpenAt:    time.Date(2026, 7, 28, 0, 0, 0, 0, time.UTC),
	})
	if err != nil {
		panic(err)
	}
	for _, tender := range result.Items {
		fmt.Printf("%s — %s\n", tender.Objet, tender.StatusAt(time.Date(2026, 7, 28, 0, 0, 0, 0, time.UTC)))
	}
}
Output:
Solution GED — open

type Enricher added in v0.2.7

type Enricher interface {
	Name() string
	Enrich(
		ctx context.Context,
		items []Tender,
		options EnrichmentOptions,
		openAt time.Time,
	) (EnrichmentResult, error)
}

Enricher is an optional secondary source detected by Engine. Implementations receive only the already sorted and paginated primary results. They must not mutate those tenders or use their data to alter the primary search result.

type EnrichmentConflict added in v0.2.7

type EnrichmentConflict struct {
	Code    string
	Message string

	BOAMPStatus   TenderStatus
	BEAUAMPStatus TenderStatus
	RelatedID     string
}

EnrichmentConflict reports contradictory lifecycle or identifier context.

type EnrichmentCoverage added in v0.2.7

type EnrichmentCoverage struct {
	RequestedFrom time.Time
	RequestedTo   time.Time
	AvailableFrom time.Time
	AvailableTo   time.Time
	FreshAt       time.Time
}

EnrichmentCoverage describes both the requested history window and the interval for which the secondary source actually supplied resources.

type EnrichmentOptions added in v0.2.7

type EnrichmentOptions struct {
	HistoryMonths  int
	HistoryLimit   int
	CandidateLimit int
}

EnrichmentOptions controls the optional secondary attribution context. Zero values use the documented defaults.

func (EnrichmentOptions) Validate added in v0.2.7

func (o EnrichmentOptions) Validate() error

Validate checks enrichment bounds without performing I/O.

type EnrichmentResult added in v0.2.7

type EnrichmentResult struct {
	Items    []TenderEnrichment
	Coverage EnrichmentCoverage
	Partial  bool
	Warnings []Warning
}

EnrichmentResult is kept separate from the primary search guarantees.

type Filter

type Filter string

Filter identifies one independently supportable query feature.

const (
	FilterTitleKeywords Filter = "title_keywords"
	FilterFullText      Filter = "full_text"
	FilterDepartments   Filter = "departments"
	FilterPublication   Filter = "publication_date"
	FilterDeadline      Filter = "deadline"
	FilterCPV           Filter = "cpv"
	FilterAmount        Filter = "amount"
	FilterBuyerSIREN    Filter = "buyer_siren"
	FilterSupplierSIREN Filter = "supplier_siren"
	FilterSupplierSIRET Filter = "supplier_siret"
	FilterNoticeType    Filter = "notice_type"
	FilterStatusOpen    Filter = "status_open"
	FilterStatusClosed  Filter = "status_closed"
	FilterStatusAwarded Filter = "status_awarded"
)

type ProcedureType

type ProcedureType int

ProcedureType is the award procedure type, independent of the engagement type (see EngagementType).

const (
	ProcedureInconnue ProcedureType = iota
	ProcedureOuverte
	ProcedureRestreinte
	ProcedureNegocieeAvecPublicite
	ProcedureNegocieeSansPublicite
	ProcedureDialogueCompetitif
	ProcedureConcours
)

func (ProcedureType) String

func (p ProcedureType) String() string

type Provider

type Provider interface {
	Name() string
	Capabilities() Capabilities
	Search(ctx context.Context, q Query) (ProviderResult, error)
}

Provider is the contract implemented by every procurement source.

type ProviderResult

type ProviderResult struct {
	Items      []Tender
	Total      int
	TotalExact bool
	Truncated  bool
}

ProviderResult is the normalized response produced by one data source. Total describes matches from that provider before cross-source merging.

type Query

type Query struct {
	Keywords []string

	// ObjetOnly restricts keyword matching to the normalized title/object.
	// When false, providers may use their native full-text search.
	ObjetOnly bool
	// MatchAll requires every keyword instead of at least one.
	MatchAll bool

	Departements []string

	PublishedFrom time.Time
	PublishedTo   time.Time
	DeadlineFrom  time.Time
	DeadlineTo    time.Time

	CPVCodes   []string
	MontantMin float64
	MontantMax float64
	BuyerSIREN string
	// SupplierSIREN matches any awarded contractor (titulaire) by its stable
	// 9-digit legal-entity identifier, including establishments with another
	// SIRET.
	SupplierSIREN string
	// SupplierSIRET matches one awarded contractor (titulaire) by its exact
	// 14-digit establishment identifier.
	SupplierSIRET string

	NoticeTypes []AvisType
	Statuses    []TenderStatus

	// OpenAt is the instant used to derive open/closed statuses. Its zero value
	// means "now"; Engine freezes that instant in the pagination cursor.
	OpenAt time.Time

	Sort     Sort
	PageSize int
	Cursor   string

	// CandidateLimit caps how many records each provider may collect before
	// federation. Zero uses the provider's safe default.
	CandidateLimit int

	// Enrichment enables optional, source-specific context on the page returned
	// by Engine. A nil value preserves the regular federated-search behaviour.
	// Enrichment never changes Items, Total, TotalExact, Partial or the main
	// Warnings collection.
	Enrichment *EnrichmentOptions
}

Query describes a federated procurement search. Criteria are combined with AND; values inside a slice (departments, CPV codes, notice types, statuses) are combined with OR.

func (Query) Validate

func (q Query) Validate() error

Validate checks query invariants without performing I/O.

type RelatedTender added in v0.2.7

type RelatedTender struct {
	Tender     Tender
	Relation   RelationType
	Confidence RelationConfidence
	Evidence   RelationEvidence
}

RelatedTender wraps a normalized BEAUAMP notice without merging it into the authoritative primary tender.

type RelationConfidence added in v0.2.7

type RelationConfidence string

RelationConfidence is intentionally categorical: only a shared native identifier can produce Exact or SourceReported confidence.

const (
	ConfidenceExact          RelationConfidence = "exact"
	ConfidenceSourceReported RelationConfidence = "source_reported"
	ConfidenceCandidate      RelationConfidence = "candidate"
)

type RelationEvidence added in v0.2.7

type RelationEvidence struct {
	BOAMPID              string
	BEAUAMPAttributionID string
	BEAUAMPContractID    string

	BuyerSIREN          string
	BuyerSIRENEstimated bool
	CPVRoots            []string
	ObjectSimilarity    float64
	PublicationGapDays  int
	TemporalConsistent  bool
}

RelationEvidence is the auditable set of facts used for one relationship.

type RelationType added in v0.2.7

type RelationType string

RelationType states why a BEAUAMP notice is related to the primary BOAMP notice. Composite candidates are explicitly not direct relations.

const (
	RelationSameAwardNotice    RelationType = "same_award_notice"
	RelationReportedContract   RelationType = "reported_contract"
	RelationCompositeCandidate RelationType = "composite_candidate"
)

type SearchResult

type SearchResult struct {
	Items      []Tender
	Total      int
	TotalExact bool
	NextCursor string
	Partial    bool
	Warnings   []Warning
	Enrichment *EnrichmentResult
}

SearchResult is the stable public response returned by Engine.

type Sort

type Sort struct {
	Field     SortField
	Direction SortDirection
}

Sort describes the ordering of a federated result. The zero value means publication date, newest first.

type SortDirection

type SortDirection string

SortDirection controls ascending or descending ordering.

const (
	SortAscending  SortDirection = "asc"
	SortDescending SortDirection = "desc"
)

type SortField

type SortField string

SortField selects the deterministic ordering applied after federation.

const (
	SortByPublication SortField = "publication"
	SortByDeadline    SortField = "deadline"
)

type SourceReference

type SourceReference struct {
	Provider   string
	ID         string
	RecordID   string
	RelatedIDs []string
	URL        string
	RawFields  map[string]any
}

SourceReference preserves the identity and raw payload of one source that contributed to a normalized Tender.

type SupportLevel

type SupportLevel uint8

SupportLevel describes how reliably a provider can honor a filter.

const (
	Unsupported SupportLevel = iota
	Approximate
	Exact
)

type Tender

type Tender struct {
	Sources []SourceReference
	Lots    []TenderLot

	Titre    string
	Objet    string
	CPVCodes []string
	Buyer    Buyer

	// Suppliers are the awarded contractors (titulaires) when the notice is an
	// award result. An empty list means the contract is not yet awarded or the
	// winners are unknown for this source.
	Suppliers []Buyer

	AvisType   AvisType
	Procedure  ProcedureType
	Engagement EngagementType

	DatePublication   time.Time
	DateLimiteReponse time.Time

	// MontantEstime is the contract amount in euros, 0 when not disclosed. Its
	// authority depends on the source: DECP reports the legally binding awarded
	// amount, BEAUAMP an indicative consolidated value, BOAMP rarely any. When
	// consolidating several sources, prefer the DECP value.
	MontantEstime float64
}

Tender is the normalized representation of a public procurement notice.

func FilterTenders

func FilterTenders(tenders []Tender, q Query, at time.Time) []Tender

FilterTenders applies every criterion that can be checked on the normalized model. Providers still perform native filtering first; this second pass gives federated results consistent semantics.

func MergeTenders

func MergeTenders(tenders []Tender) []Tender

MergeTenders deduplicates records and enriches cross-source matches.

Records from different providers are merged only when buyer SIREN, normalized object and at least one CPV root agree, their publication dates are no more than 180 days apart, and either the winning supplier or a native notice ID agrees. Missing evidence prevents a merge.

func (Tender) DedupKey

func (t Tender) DedupKey() string

DedupKey identifies the record itself, not a speculative cross-source contract match. Cross-source consolidation uses stricter evidence in MergeTenders.

func (Tender) ProviderNames

func (t Tender) ProviderNames() []string

ProviderNames returns the sorted, deduplicated providers contributing to t.

func (Tender) StatusAt

func (t Tender) StatusAt(at time.Time) TenderStatus

StatusAt derives the lifecycle status at at. Deadlines are compared at day granularity because several public datasets expose no response time.

type TenderEnrichment added in v0.2.7

type TenderEnrichment struct {
	TenderKey      string
	ExactRelations []RelatedTender
	Candidates     []RelatedTender
	BuyerHistory   []Tender
	Conflicts      []EnrichmentConflict
}

TenderEnrichment contains attribution context for one primary Tender. Its TenderKey is the primary tender's DedupKey and Items preserve page order.

type TenderLot added in v0.2.7

type TenderLot struct {
	ID            string
	Objet         string
	CPVCodes      []string
	Suppliers     []Buyer
	MontantEstime float64
	Sources       []SourceReference
}

TenderLot preserves lot-level CPV codes, amounts, suppliers and native BEAUAMP rows without breaking the historical flat Tender fields.

type TenderStatus

type TenderStatus int

TenderStatus is the lifecycle state derived from notice type, response deadline and award information at a given instant.

const (
	StatusUnknown TenderStatus = iota
	StatusOpen
	StatusClosed
	StatusAwarded
)

func (TenderStatus) String

func (s TenderStatus) String() string

type UnsupportedFilterError

type UnsupportedFilterError struct {
	Filter Filter
}

UnsupportedFilterError is returned when a provider is called directly with a criterion it cannot honor.

func (*UnsupportedFilterError) Error

func (e *UnsupportedFilterError) Error() string

type ValidationError

type ValidationError struct {
	Field   string
	Problem string
}

ValidationError identifies one invalid query field.

func (*ValidationError) Error

func (e *ValidationError) Error() string

type Warning

type Warning struct {
	Provider string
	Code     WarningCode
	Message  string
	Err      error
}

Warning describes a non-fatal source-level problem.

type WarningCode

type WarningCode string

WarningCode is a stable machine-readable reason why a federated response is partial or approximate.

const (
	WarningProviderError     WarningCode = "provider_error"
	WarningUnsupportedFilter WarningCode = "unsupported_filter"
	WarningApproximateFilter WarningCode = "approximate_filter"
	WarningTruncated         WarningCode = "truncated"
	WarningCoverageGap       WarningCode = "coverage_gap"
	WarningResourceError     WarningCode = "resource_error"
)

Directories

Path Synopsis
Package beauamp implements muninn.Provider and muninn.Enricher for BEAUAMP (Base Étendue, Améliorée et Unifiée des Annonces des Marchés Publics), published on data.gouv.fr.
Package beauamp implements muninn.Provider and muninn.Enricher for BEAUAMP (Base Étendue, Améliorée et Unifiée des Annonces des Marchés Publics), published on data.gouv.fr.
Package boamp implements muninn.Provider for the BOAMP API (DILA), exposed through an Opendatasoft platform (Explore API v2.1).
Package boamp implements muninn.Provider for the BOAMP API (DILA), exposed through an Opendatasoft platform (Explore API v2.1).
Package consolidate provides a provider-compatible merger.
Package consolidate provides a provider-compatible merger.
Package decp implements muninn.Provider for the DECP dataset (Données Essentielles de la Commande Publique), the mandatory open data of awarded public contracts of 40,000 € HT or more.
Package decp implements muninn.Provider for the DECP dataset (Données Essentielles de la Commande Publique), the mandatory open data of awarded public contracts of 40,000 € HT or more.
internal
httpx
Package httpx provides a shared HTTP retry helper for the providers that hit external, potentially rate-limited government APIs (Opendatasoft, data.gouv.fr).
Package httpx provides a shared HTTP retry helper for the providers that hit external, potentially rate-limited government APIs (Opendatasoft, data.gouv.fr).
ods
Package ods provides the shared plumbing for querying an Opendatasoft Explore API v2.1 dataset.
Package ods provides the shared plumbing for querying an Opendatasoft Explore API v2.1 dataset.
Package retry defines the retry/backoff configuration shared by every muninn provider (boamp, decp, beauamp) for requests against their external, potentially rate-limited government APIs.
Package retry defines the retry/backoff configuration shared by every muninn provider (boamp, decp, beauamp) for requests against their external, potentially rate-limited government APIs.
Package search provides client-side filters over already-fetched []muninn.Tender, complementing the filters each provider pushes server-side (useful when a source does not natively support every criterion, or to refine results after aggregation).
Package search provides client-side filters over already-fetched []muninn.Tender, complementing the filters each provider pushes server-side (useful when a source does not natively support every criterion, or to refine results after aggregation).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL