Skip to content

Fix: filter mandi-price response to the requested market and price fields - #119

Merged
manjudr merged 19 commits into
developmentfrom
fix/107-mandi-price-filter-the-response-to-the-requested-market-and-price-fields
Nov 9, 2026
Merged

manjudr merged 19 commits into
developmentfrom
fix/107-mandi-price-filter-the-response-to-the-requested-market-and-price-fields

Conversation

@kelvinprabhu

@kelvinprabhu kelvinprabhu commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Refs #97, #98
Closes #107
Spec: MandiPrice/v0.1 attributes.yaml

Changes

Change Reason
marketCode is optional. When provided, it is sent upstream and echoed in results. Otherwise, district-wide results are returned with each row's own market. Each row's market carries the requested district and state codes. Supports Agmarknet district-wide searches; an answer's market can be selected again.
marketName without marketCode is refused before Agmarknet is called (HTTP 400): send marketCode for that market, or omit marketName for a district search. Agmarknet takes no market name, so a name alone would return the whole district.
Several supportedCommodities are not refused: only the first is sent upstream, and the adapter logs a warning with the count. Agmarknet takes one commodity code per call.
State and district are mandatory; district must be a numeric code. Commodity, marketCode, date format and price-field values are left to the schema validator. Agmarknet requires these codes; no repeat of what the pack already enforces.
validity.startsAt and validity.endsAt are mandatory and validated for correct order. They become from_date and to_date; prevents reversed ranges being sent upstream.
Only the requested supportedPriceFields are returned; absent selection returns all three. Prevents unnecessary fields.
Trailing whitespace is trimmed from marketName. Agmarknet sends a trailing space.

…ields [#107]

Refuse a select whose market.marketCode is absent or empty before
Agmarknet is called; without it the query widens to the whole district.
Each on_select resource carries market.marketCode, where
catalog-ondemand.yaml puts it.

Refuse a select whose supportedPriceFields lists a value outside the
pack's Minimum/Maximum/Modal enum, which would otherwise return rows
with no price.

Supersedes the marketCode change in #98.
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🛡️ Trivy security scan (CRITICAL,HIGH,MEDIUM,LOW)

View full run

Go dependencies

Package Severity Installed Fixed in Advisory
golang.org/x/net HIGH v0.60.0 0.62.0 CVE-2026-78661
golang.org/x/net MEDIUM v0.60.0 0.62.0 CVE-2026-97045

Container image

No findings at CRITICAL,HIGH,MEDIUM,LOW.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

📊 Test Coverage: ✅ Passed — 91% of changed lines covered, min 80%

…107]

marketCode is used when given and must then be a non-empty string.
Without it the query covers the whole district: no marketcode is sent
upstream, no single code is echoed, and each row names its own market.
marketName is trimmed of the upstream's trailing space.

Refs #97
@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Implemented option 1 from the open question: marketCode is optional. When given it is used and echoed; when absent the search is district-wide and no single code is claimed. PR description updated.

@kelvinprabhu kelvinprabhu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DONE SELF REVIEW

@kelvinprabhu kelvinprabhu self-assigned this Nov 3, 2026
@kelvinprabhu kelvinprabhu added bug Something isn't working adapter Adapter codebase (beckn-onix). Additive - use alongside a layer label labels Nov 3, 2026
@kelvinprabhu kelvinprabhu added this to the current milestone Nov 3, 2026
@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Mandi Price: Optional marketCode & district-level search (PR #119, issue #107)
@manjudr @ameersohel45
Network spec reference (MandiPrice/v0.1):

market:
  type: object
  anyOf:
    - required: [marketCode]
    - required: [marketName]
    - required: [district]
    - required: [state]
    - required: [location]

anyOf means at least one of these five fields must be present. They are alternative ways to identify the market scope, not fields that must all be provided.

Change decisions
• marketCode is not treated as mandatory. When provided, the adapter uses it to fetch the specific market. When absent, district-level search is possible if a district code is provided.
• Making marketCode mandatory would exclude valid use cases already supported by the MandiPrice/v0.1.

Where Agmarknet differs from the network spec
• Agmarknet always requires a state code. The spec allows marketName or location alone, but Agmarknet cannot directly answer these queries.
• The network spec therefore allows more flexible market identification than the upstream provider supports.

Open questions

PR: #119

Spec: https://github.com/OpenAgriNet/network-specs/blob/schema-packs-v0.1/schema/MandiPrice/v0.1/attributes.yaml

@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Mandi Price: Optional marketCode & district-level search (PR #119, issue #107) @manjudr @ameersohel45 Network spec reference (MandiPrice/v0.1):

market:
  type: object
  anyOf:
    - required: [marketCode]
    - required: [marketName]
    - required: [district]
    - required: [state]
    - required: [location]

anyOf means at least one of these five fields must be present. They are alternative ways to identify the market scope, not fields that must all be provided.

Change decisions • marketCode is not treated as mandatory. When provided, the adapter uses it to fetch the specific market. When absent, district-level search is possible if a district code is provided. • Making marketCode mandatory would exclude valid use cases already supported by the MandiPrice/v0.1.

Where Agmarknet differs from the network spec • Agmarknet always requires a state code. The spec allows marketName or location alone, but Agmarknet cannot directly answer these queries. • The network spec therefore allows more flexible market identification than the upstream provider supports.

Open questions

PR: #119

Spec: https://github.com/OpenAgriNet/network-specs/blob/schema-packs-v0.1/schema/MandiPrice/v0.1/attributes.yaml

Conclusions / Decisions

  • Agmarknet-specific schema changes:
    • Make stateCode, districtCode, and commodity mandatory.
    • Keep marketCode optional to support district-level searches.
  • Price fields: Support only the price fields available through Agmarknet, based on the existing field mappings.

Rationale: Align the adapter schema with Agmarknet's actual API requirements while retaining optional market-level filtering.

…price select [#107]

State and district are checked in separate guards so the refusal names
the missing one. validity.startsAt and validity.endsAt are mapped to the
upstream's from_date and to_date, so both must be dates and the start
must not be after the end; an empty or free-text value used to be sent
upstream as "--" or crash the date reorder.

Tests cover each mandatory field, Minimum alone, and all three price
fields when none are given.

Refs #97
Agmarknet takes no market name, so a select with marketName and no
marketCode queries the district and keeps only the named market's rows,
matched trimmed and case-insensitively. A name that matches none of the
markets the district reported is refused with a 400 asking for
market.marketCode; a district with no rows at all is an empty answer.

jsonmapper: a response-half $error starting with "[refused] " is now a
400 in the mapping's words rather than a 502, for refusals that can only
be decided once the provider has answered. Other response failures stay
502.

Refs #97
…as the response half's 502 [#107]

The "[refused]" marker in jsonmapper is removed, so jsonmapper is as on
development. A marketName that matches none of the district's markets is
still refused with a message asking for market.marketCode; it is raised
from the response half and so reported as a 502.

Refs #97

@kelvinprabhu kelvinprabhu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the comments shorter not more than 3 lines max

…ts [#107]

Agmarknet takes no market name, so a name without a code would answer
for the whole district. It is now refused before Agmarknet is called
with a 400 that asks for market.marketCode, replacing the response-side
name filter and its 502. Comments added by this change are kept to
three lines.

Refs #97
@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Review addressed: every comment added by this PR is now three lines or fewer, and a market name without a code is refused up front (400).

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kelvinprabhu, the config/mappings/agmarknet/mandi-price.select.yaml file needs an update in the helm chart repository as well. Additionally, please update the API collection to include valid request and publish requests.

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant validations are defined.

The schema validator already refuses an unknown or empty supportedPriceFields,
an empty or non-string marketCode, a validity that is not a date-time, and a
commodity without a code. Keep only what the pack does not cover.
- state/district guards: $exists instead of a type check the schema covers
- request: marketcode set directly; JSONata omits it when absent
- response: @context read from $ra rather than the full path again
- remove the schema-note comment
]

- market.state: keep the presence check, drop the code-shape regex
- reword the marketName-without-marketCode refusal
@kelvinprabhu
kelvinprabhu requested a review from manjudr November 4, 2026 10:09
@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Open Question: Multiple Commodities in a Mandi Selection

Pack: MandiPrice/v0.1 allows supportedCommodities to contain multiple items (minItems: 1, no upper bound). See [attributes.yaml#L47-L58](https://github.com/OpenAgriNet/network-specs/blob/schema-packs-v0.1/schema/MandiPrice/v0.1/attributes.yaml#L47-L58).

Agmarknet: Accepts only one commoditycode per request. Verified with live API calls for district 338, 20–31 October 2026:

  • commoditycode=4,23 → HTTP 500 (invalid numeric input).
  • commoditycode=4&commoditycode=23 → HTTP 200, but returns only Onion because the last value wins.

Current behaviour: The adapter rejects multiple commodities with HTTP 400, requiring exactly one commodity code. Without this validation, the consumer could receive a successful signed response containing only one commodity, while the others are silently dropped.

Decision needed:

  1. Multiple upstream calls: Make one Agmarknet request per commodity and merge the results. This requires a plugin change and a defined policy for partial failures. (use the fanout)

Which approach should we adopt?
@manjudr @ameersohel45

#107]

Not needed for #107 and not broken, so left unchanged.

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the pack at network-specs@schema-packs-v0.1 and the adapter's validator rather than the description. Two inline findings, two here.


1. The header states a pack rule the pack does not contain

Problem

config/mappings/agmarknet/mandi-price.select.yaml:20-43 says the pack's OnDemand branch "EXCLUDES" market and validity, and builds twenty-four lines of reasoning on it. In MandiPrice/v0.1/attributes.yaml, market is on the top-level required list (line 21) and the OnDemand if/then adds only required: [supportedPriceFields] (lines 189-195). There is no exclusion anywhere in that file.

Recommendation

Delete lines 20-43 and 45-51. If the marketCode-optional decision needs recording, put it in an ADR under the repo's ADR directory and link it from this PR, not from the mapping.


2. The description claims three guards the diff does not add

Problem

The table lists marketCode non-empty, date format validation, and a supportedPriceFields enum. The file has six required checks and none of them is any of those. A reviewer reading the table approves behaviour that is not in the code.

Recommendation

Cut the table to two things: what was broken, and what this PR does about it. Drop those three rows, or say plainly that the pack enforces them.


Resolved

The validations flagged as redundant were correctly removed. The pack carries each one, and schemav2validator does enforce required, minLength and enum:

  • supportedCommodities items -- required: [code], attributes.yaml:56
  • marketCode -- minLength: 1, attributes.yaml:113
  • supportedPriceFields -- enum: [Minimum, Maximum, Modal], attributes.yaml:65

The validity existence check is not redundant and was right to keep: TimePeriod sets minProperties: 1, so the pack accepts one end on its own.


Still blocking: the multiple-commodities question from 4 Nov has no answer yet.

@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Open Question: Multiple Commodities in a Mandi Selection

Pack: MandiPrice/v0.1 allows supportedCommodities to contain multiple items (minItems: 1, no upper bound). See [attributes.yaml#L47-L58](https://github.com/OpenAgriNet/network-specs/blob/schema-packs-v0.1/schema/MandiPrice/v0.1/attributes.yaml#L47-L58).

Agmarknet: Accepts only one commoditycode per request. Verified with live API calls for district 338, 20–31 October 2026:

  • commoditycode=4,23 → HTTP 500 (invalid numeric input).
  • commoditycode=4&commoditycode=23 → HTTP 200, but returns only Onion because the last value wins.

Current behaviour: The adapter rejects multiple commodities with HTTP 400, requiring exactly one commodity code. Without this validation, the consumer could receive a successful signed response containing only one commodity, while the others are silently dropped.

Decision needed:

  1. Multiple upstream calls: Make one Agmarknet request per commodity and merge the results. This requires a plugin change and a defined policy for partial failures. (use the fanout)

Which approach should we adopt? @manjudr @ameersohel45

Decision: Multiple Commodities in a Mandi Selection

After discussion, we will adopt the following approach:

  • When a request contains multiple commodities, the adapter will select only the first commodity (supportedCommodities[0]).
  • Only the first commodity code will be passed to the Agmarknet API.
  • The adapter will log a message indicating that multiple commodities were provided and only the first one is being processed.
  • The request will not fail with HTTP 400 just because multiple commodities were supplied.

Rationale: Agmarknet accepts only one commodity code per request. This approach maintains compatibility without silently implying that all requested commodities were processed.

Team decision: several supportedCommodities are no longer refused with a
400. Only supportedCommodities[0] is sent to Agmarknet, which takes one
code per call, and the plugin logs a warning naming the count.
…mmodity log [#107]

- market.district and market.state are the requested codes, so an
  answer's market can be selected again
- extra commodities are ignored without a plugin log; prerequisites.go
  is back to development
]

Only supportedCommodities[0] is sent to Agmarknet; the plugin now warns
with the count so the dropped commodities show in the adapter log.
#107]

The pack requires supportedCommodities with minItems 1 and a code on each
item, and schemav2validator refuses those before the mapping runs.
Replaces the hand-written payload walk with the shared path walker.

@kelvinprabhu kelvinprabhu left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done Review for Commodities

The pack's OnDemand branch only adds required: [supportedPriceFields]; it
excludes neither market nor validity.
@kelvinprabhu

Copy link
Copy Markdown
Collaborator Author

Re review 5796644178:

  1. Agreed. The OnDemand branch only adds required: [supportedPriceFields]. Removed the header's exclusion claims in f8b587e.
  2. Description updated: the table now says commodity, marketCode, date format and price-field values are left to the schema.

Multiple commodities: decided and implemented. supportedCommodities[0] is sent, the rest are ignored, and the adapter logs MandiPrice select names N commodities… (f58edca, e93f2cf).

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 91ca538. One inline, two here, and one of mine withdrawn.


Withdrawn: the date-shape finding

You are right and I was wrong. I grepped for compiler.AssertFormat() because go.mod pulls santhosh-tekuri, and missed that extended validation runs on kin-openapi. extended_schema.go:870 passes openapi3.EnableFormatValidation(), and kin-openapi registers a date-time RFC3339 validator by default (openapi3/schema_formats.go:59). format: date-time is enforced. No guard needed in the mapping.


1. The test fixture carries a validity window production would reject

Problem

mappings_test.go:80 is "validity": { "startsAt": "2025-08-20", "endsAt": "2025-08-21" }. Those are dates, not date-times. By the rule you just established, format: date-time fails them with a 400 before the mapping ever runs -- the same 400 you got live for "01-09-2026".

selectRequest is the fixture behind every test in this file, so the whole suite proves behaviour on a payload no caller can send. The helmcharts example gets this right: 2026-09-01T00:00:00+05:30.

Recommendation

Change both ends of selectRequest to RFC3339, and the same in the "validity that ends before it starts" case at line 747. The upstream query assertions stay as they are -- $ddmmyyyy reads the first ten characters either way.


2. A stale claim in the test

Problem

mappings_test.go:229 reads "Nothing was resolved before the call, which is the whole claim of this plugin having no prerequisites." The plugin now has one, registered at prerequisites.go:17.

Recommendation

Change it to say nothing is resolved before the call -- the one entry only logs.


Resolved

  • The header's pack-exclusion claims are gone, and the 51 lines are now 14.
  • district and state are the requested codes alongside marketCode, so an answer's market can be selected again. mappings_test.go asserts it.
  • The description no longer claims guards that are not in the code, and says plainly what the schema validator covers.
  • Multiple commodities: decision recorded, implemented, and covered by TestShippedMappingSendsOnlyTheFirstCommodity.

// first is sent to Agmarknet, which takes one code per call; the rest are
// dropped, not refused.
func logExtraCommodities(ctx context.Context, beckn any) (map[string]any, error) {
path := "message.contract.commitments[].resources[].resourceAttributes.supportedCommodities[]"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning counts something the mapping does not read

Problem

This path sums supportedCommodities across every commitment and every resource. The mapping only ever reads commitments[0].resources[0] -- see mandi-price.select.yaml:19, :102 and :109.

The two disagree in both directions. A select with two commitments naming one commodity each sums to 2 and logs "names 2 commodities" when neither commitment lost anything. And the case that really does lose data -- a second resource, which the mapping drops whole -- produces no warning at all.

Recommendation

LeavesAt has no index syntax; walk at common/paths.go:115 only handles the [] suffix, so commitments[0] would be looked up as a literal key. Walk to the first commitment's first resource with plain type assertions and count supportedCommodities there, so the number in the log is the number in the request that is actually sent. Warn separately when there is more than one commitment or resource, since those are dropped whole and nothing reports it today.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d3e43ad. The warning now walks to commitments[0].resources[0] with plain type assertions, the same entry the mapping reads, and counts supportedCommodities there. Extra commitments and extra resources get their own warning, since they are dropped whole. Covered by TestDroppedSelectionsCountsWhatTheMappingReads, including the two-commitments case.

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at 91ca538. No new commits since the last round, so the three open findings are unchanged and none has a reply. One new finding below, inline.


1. The state guard is weaker on this branch than on development

Problem

Inline on config/mappings/agmarknet/mandi-price.select.yaml:30.


Still open from 6 Nov, no reply

prerequisites.go:24 -- the warning counts supportedCommodities across every commitment and every resource, while the mapping reads commitments[0].resources[0] only. Thread r4490298102.

mappings_test.go:80 -- "startsAt": "2025-08-20" is a date, not a date-time. By the rule you established in r4490145955, format: date-time rejects it with a 400 before the mapping runs. selectRequest is the fixture behind every test in the file.

mappings_test.go:229 -- still reads "the whole claim of this plugin having no prerequisites". The plugin has one, at prerequisites.go:17.

@manjudr

manjudr commented Nov 9, 2026

Copy link
Copy Markdown
Member

Merge readiness

Not yet. One blocker.

The state guard (mandi-price.select.yaml:30). After this merges, "state": "Maharashtra" instead of "MH" passes the guard, goes upstream as statecode=Maharashtra, and the caller gets a signed, spec-valid "no prices" for a state that has prices. development catches that today at line 80; this PR removes the catch. A silent wrong answer is the failure class this PR set out to fix, so it should not ship.

Worth doing in the same pass, none blocking on its own:

  • mappings_test.go:80 -- selectRequest carries a date-only validity, which format: date-time rejects with a 400 before the mapping runs. Every test in the file runs on it, so the suite passes without proving anything about a request a caller can actually send. One line.
  • mappings_test.go:229 -- stale comment, the plugin now has a prerequisite.

Let through: the warning count at prerequisites.go:24. It only affects what a log line says.

Order: this PR first, then re-sync the mapping into helmcharts#123 and merge that.

@kelvinprabhu
kelvinprabhu requested a review from manjudr November 9, 2026 07:23

@manjudr manjudr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at d3e43ad. The blocker is cleared. Two test-hygiene items left, neither affects shipped behaviour.


Resolved

The state guard (62f3d2b). $match($ra.market.state, /^[A-Za-z]{2,4}$/) is back, the message names the shape, and "a state name where the upstream wants a code" covers it. "Chattisgarh" is refused; "CG" passes.

The warning count (d3e43ad). droppedSelections now walks commitments[0].resources[0] with plain type assertions -- the same entry the mapping reads -- and warns separately about dropped commitments, dropped resources and dropped commodities. prerequisites_test.go covers all four shapes including the empty one. I ran the suites: MandiPrice, MandiPrice/cmd and MandiPrice/publish all pass.


Still open

1. The test fixture carries a validity window production rejects

mappings_test.go:80 is still "validity": { "startsAt": "2025-08-20", "endsAt": "2025-08-21" }. The pack reaches TimePeriod through $ref and both ends are format: date-time (AgricultureResource/v0.1/attributes.yaml:181, :186), which kin-openapi enforces -- the rule you established in r4490145955.

selectRequest is the fixture behind every test in the file, so the suite passes on a payload no caller can send. Change both ends to RFC3339, and the same at line 745. The from_date/to_date assertions are unaffected -- $ddmmyyyy reads the first ten characters either way.

2. A stale claim in the test

mappings_test.go:229 still reads "which is the whole claim of this plugin having no prerequisites". The plugin has one, registered at prerequisites.go:18. The package comment above it was updated in d3e43ad; this line was missed.


Approving. Both are a few minutes of work -- please fold them in before merge rather than in a follow-up.

@manjudr
manjudr merged commit 318c2df into development Nov 9, 2026
3 of 4 checks passed
@manjudr
manjudr deleted the fix/107-mandi-price-filter-the-response-to-the-requested-market-and-price-fields branch November 9, 2026 11:59
manjudr pushed a commit that referenced this pull request Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adapter Adapter codebase (beckn-onix). Additive - use alongside a layer label bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mandi price: filter the response to the requested market and price fields

2 participants