feat: add the Mandi provider plugin - #13
Open
ameersohel45 wants to merge 2 commits into
Open
Conversation
ameersohel45
force-pushed
the
feat/8-mandi-plugin
branch
from
September 3, 2026 17:39
0bc82e8 to
a06911b
Compare
Serves openagrinet:MandiPrice against Agmarknet's Vistaar select, as a sibling to weather: a domain package of 58 lines wrapping internal/upstream, which needed no change for it. That was the test of whether the machinery and domain split from 2b3cab1 actually held, and it did. The package has NO prerequisites, and the reason is the pack rather than luck. A MandiPrice select names the market it wants -- market.marketCode, market.district, market.state -- and carries a commodity code and a validity window, which is every parameter the upstream takes. There is no top-level location in the pack, so nothing has to turn a point into a market, which is the one thing the provider backend needs a spatial SQL query for and the one thing this adapter may not do. The mapping carries the whole contract. Three things in it are not obvious: the upstream's records use Title Case keys WITH SPACES -- `Modal Price` -- so they need backticks, and its prices are STRINGS, so they need $number before they satisfy the pack's numeric types. Both are pinned by a verbatim capture from the provider backend's own documentation. dates convert twice. The pack speaks ISO, the upstream speaks dd-MM-yyyy, so the request half converts out and the response half converts back. the pack requires none of the fields the upstream needs -- an OnDemand select requires only supportedCommodities and supportedPriceFields, leaving market and validity optional. So a spec-valid select can be unanswerable, and the mapping's required: block refuses those with its own message rather than earning a 400 or, worse, an empty result that reads as "no prices". Resource ids are built from codes rather than the names the upstream reports: "Kasdol APMC" and "Paddy(Common)" carry spaces and brackets, and an id a consumer may put in a URL should not. Verified: the shipped mapping run through the real mapper and the real step answers two records as two Direct resources with their prices converted, the offer's references rewritten to match, and absent min/max left absent rather than zeroed. Both directions validate -- the select against beckn.yaml and MandiPrice v0.1 in OnDemand mode, the on_select against beckn.yaml and the pack in Direct mode, with no errors.
Adds mandi alongside weather in config/oan-provider-adapter.yaml, so the reference shows the thing that is actually interesting about this design: two domain packages in one pipeline, sharing the module, the registry client and the mapper, and sharing nothing else. The whole cost of the second capability is one providerSteps entry and one line in steps. No routing table, no new module, no new port. Which one answers is decided by the payload -- each step builds a binding key from it, serves the request if the key is its own, and passes it through untouched if not -- so the order they appear in does not matter either. mandi uses authScheme query, because Agmarknet's Vistaar API takes its token as a query parameter. The adapter holds the parameter's name and the name of the environment variable carrying the value, never the value, and redacts it from the URL it logs -- so a token cannot reach the log by way of the request. Verified by booting this config in an image that has both plugins: both ProviderStep plugins load, the pipeline initialises as [validateSign validateSchema weather mandi signAck], and the module registers at /. Worth noting the published adapter image does NOT yet carry mandi.so, so against that image this config fails at startup with "plugin mandi not found" until it is rebuilt from this branch.
ameersohel45
force-pushed
the
feat/8-mandi-plugin
branch
from
September 6, 2026 13:01
a06911b to
ede4859
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Targets
feat/41-oan-adapter-pluginsrather thandevelopment, because it builds on the machinery that PR #2 introduces. Merge that one first.What
A second provider capability,
openagrinet:MandiPrice, served against Agmarknet's Vistaar select. Additive: six new files, one line added to the plugin build list, and nothing existing changed.The domain package —
pkg/plugin/implementation/mandi/38 lines, plus a 20-line prerequisites file that is deliberately empty. It supplies its name and nothing else; recognising the capability, resolving the call plan, authenticating, calling with the registry's budget and translating in both directions are all
internal/upstream's.internal/upstreamneeded no change for it. That was the test of whether the machinery and domain split in PR #2 actually held, and it is the reason this PR is as small as it is.No prerequisites, and not by luck
A
MandiPriceselect names the market it wants —market.marketCode,market.district,market.state— and carries a commodity code and a validity window, which is every parameter the upstream takes. The pack has no top-level location, so nothing has to turn a point into a market.That matters because turning a point into a market is exactly what the provider backend needs a PostGIS query for, and the adapter may not talk to a database. The pack's shape and the upstream's contract happen to agree, so the plugin is data only.
The mapping —
config/mappings/agmarknet/mandi-price.select.yamlWhere the actual work is. Three things in it are not obvious:
`Modal Price`— so they need backticks, and its prices are strings, so they need$number()before they satisfy the pack's numeric types. A verbatim capture from the provider backend's own documentation is pinned as a fixture, so neither can drift unnoticed.dd-MM-yyyy, so the request half converts out and the response half converts back.OnDemandselect requires onlysupportedCommoditiesandsupportedPriceFields, leavingmarketandvalidityoptional, and definesmarket.districtandmarket.stateas "name or governed code". So a spec-valid select can still be unanswerable, and therequired:block refuses those with its own message rather than earning a 400 from Agmarknet — or worse, an empty result that reads as "no prices".Resource ids are built from codes rather than the names the upstream reports:
Kasdol APMCandPaddy(Common)carry spaces and brackets, and an id a consumer may put in a URL should not.Query-string auth
Agmarknet takes its token as a query parameter.
authScheme: queryfor that arrives in PR #2, since it is machinery rather than anything mandi-specific. The config here holds the parameter's name and the name of the environment variable carrying the value, never the value.Why
Two capabilities from two domain packages in one pipeline, dispatched by binding key read from the payload. Which one answers is decided by the request, not by a routing table, a URL or a domain — each step recognises its own key and passes through anything else.
It also settles a claim PR #2 could only assert: that adding a domain costs a small package and a mapping. It cost 58 lines of Go and one line in the build script.
Testing
go vet ./...clean andgo test ./...green across 63 packages.The shipped mapping is run through the real mapper and the real provider step against a captured Agmarknet response, rather than asserted about. Both directions validate with
jsonschema— the select againstbeckn.yamlandMandiPrice v0.1inOnDemandmode, theon_selectagainstbeckn.yamland the pack inDirectmode — with zero errors.End to end on the local stack, both capabilities loaded into one provider adapter:
Directresource per price record, prices converted from strings, dates in ISO, the offer's references rewritten to what was returned, and a record that reported no minimum or maximum coming back with those absent rather than zeroedOnDemandwhile carrying no pricesThe local Postman collection covers all of that: 8 requests, 39 assertions, no failures.
Notes for review
dd-MM-yyyy, and a query token it rejects when absent. The real API's credentials, host and exact record set are untested.marketcode, widening to the district. Our step makes one call, so an empty result stays empty. Left alone until the real API's behaviour is observed, along with the question of whether Agmarknet reports "no data" as an HTTP 400.Closes #8