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 ¶
- Constants
- Variables
- func ValidateCapabilities(q Query, caps Capabilities) error
- func ValidateProviderQuery(q Query) error
- type AvisType
- type Buyer
- type Capabilities
- type EngagementType
- type Engine
- type Enricher
- type EnrichmentConflict
- type EnrichmentCoverage
- type EnrichmentOptions
- type EnrichmentResult
- type Filter
- type ProcedureType
- type Provider
- type ProviderResult
- type Query
- type RelatedTender
- type RelationConfidence
- type RelationEvidence
- type RelationType
- type SearchResult
- type Sort
- type SortDirection
- type SortField
- type SourceReference
- type SupportLevel
- type Tender
- type TenderEnrichment
- type TenderLot
- type TenderStatus
- type UnsupportedFilterError
- type ValidationError
- type Warning
- type WarningCode
Examples ¶
Constants ¶
const ( DefaultEnrichmentHistoryMonths = 24 MaxEnrichmentHistoryMonths = 60 DefaultEnrichmentHistoryLimit = 5 MaxEnrichmentHistoryLimit = 50 DefaultEnrichmentCandidateLimit = 10000 )
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 ¶
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 ¶
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.
type Buyer ¶
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 ¶
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 (*Engine) Search ¶
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
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 ¶
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.
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.
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 ¶
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 ¶
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 ¶
DedupKey identifies the record itself, not a speculative cross-source contract match. Cross-source consolidation uses stricter evidence in MergeTenders.
func (Tender) ProviderNames ¶
ProviderNames returns the sorted, deduplicated providers contributing to t.
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 ¶
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" )
Source Files
¶
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). |