Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions config/mappings/pmkisan/grievance.init.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# PM-KISAN grievance init -> /ChatbotOTP on the OTP service: send an OTP.

request: |
{
"Types": "Ben_id",
"Values": beckn.message.contract.commitments[0].commitmentAttributes.enrolmentId
}

# "True" is sent (on_init DRAFT, challenge.issued); anything else is a 502.
response: |
$exists(_status)
? {}
: (
$true := function($v) { $exists($v) and $lowercase($string($v)) = "true" };
$sent := $true(response.Rsponce) or $true(response.Responce) or $true(response.Status);
$commitment := beckn.message.contract.commitments[0];
$ca := $commitment.commitmentAttributes;

$otpWindowMillis := 10 * 60 * 1000;
$not($sent)
? { "_error": { "status": 502, "code": "NET_DOWNSTREAM_UNAVAILABLE",
"message": "PM-KISAN could not send the OTP" } }
: {
"context": $merge([beckn.context, { "action": "on_init", "timestamp": $now() }]),
"message": {
"contract": $merge([beckn.message.contract, {
"commitments": [$merge([$commitment, {
"status": { "descriptor": { "code": "DRAFT" } },
"commitmentAttributes": {
"@context": $ca.`@context`,
"@type": $ca.`@type`,
"informationMode": "OnDemand",
"scheme": $ca.scheme,
"challenge": {
"issued": {
"method": "SMS_OTP",
"length": 4,
"valueType": "NUMERIC",
"expiresAt": $fromMillis($millis() + $otpWindowMillis)
}
}
}
}])]
}])
}
}
)
90 changes: 90 additions & 0 deletions config/mappings/pmkisan/grievance.status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# PM-KISAN grievance status -> /GrievanceStatusCheck, matched on case.filedOn.

request: |
{
"Type": "Reg_No_Status",
"IdentityNo": beckn.message.contract.commitments[0].commitmentAttributes.enrolmentId
}

# "False": 502; no record on filedOn: 400. Only allow-listed fields are returned.
response: |
$exists(_status)
? {}
: (
$false := function($v) { $exists($v) and $lowercase($string($v)) = "false" };
$refused := $false(response.Status) or $false(response.Responce)
or $false(response.Rsponce) or $false(response.status);

$c := beckn.message.contract.commitments[0];
$ca := $c.commitmentAttributes;
$records := $type(response) = "array" ? response
: $type(response.details) = "array" ? response.details
: $exists(response.details) ? [response.details]
: [];

/* ISO or dd/MM/yyyy (or dd-MM-yyyy) to ISO; time dropped. */
$iso := function($d) {
(
$day := $type($d) = "string" ? $split($trim($d), /[ T]/)[0];
$exists($match($day, /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)[0]) ? $day
: $exists($match($day, /^[0-9]{2}[\/-][0-9]{2}[\/-][0-9]{4}$/)[0])
? $substring($day, 6, 4) & "-" & $substring($day, 3, 2) & "-" & $substring($day, 0, 2)
)
};
/* Non-empty trimmed string, else absent. */
$text := function($v) {
$type($v) = "string" and $length($trim($v)) > 0 ? $trim($v)
};
/* CaseStatusCode by lowercased, letters-only phrase. */
$codes := {
"registered": "Registered", "underreview": "UnderReview",
"awaitingapplicant": "AwaitingApplicant", "replied": "Replied",
"disposed": "Replied",
"resolved": "Resolved", "rejected": "Rejected", "closed": "Closed"
};

$record := $filter($records, function($r) { $iso($r.GrievanceDate) = $ca.case.filedOn })[0];
$phrase := $text($record.GrievanceStatus);
$remark := $text($record.OfficerReply);
$complaint := $text($record.GrievanceDescription);

$refused
? { "_error": { "status": 502, "code": "NET_DOWNSTREAM_UNAVAILABLE",
"message": "PM-KISAN could not read the grievance" } }
: $not($exists($record))
? { "_error": { "status": 400, "code": "BIZ_GENERIC_ERROR",
"message": "No grievance matches this registration number and filing date." } }
: {
"context": $merge([beckn.context, { "action": "on_status", "timestamp": $now() }]),
"message": {
"contract": {
"id": beckn.message.contract.id,
"commitments": [{
"status": { "descriptor": { "code": "ACTIVE" } },
"offer": $c.offer,
"resources": $c.resources,
"commitmentAttributes": {
"@context": $ca.`@context`,
"@type": $ca.`@type`,
"informationMode": "Direct",
"scheme": $ca.scheme,
"grievance": $complaint ? { "description": $complaint },
"case": {
"status": {
"code": $phrase
? (
$known := $lookup($codes, $lowercase($replace($phrase, /[^A-Za-z]/, "")));
$exists($known) ? $known : "UnderReview"
)
: ($remark ? "Replied" : "UnderReview")
},
"filedOn": $iso($record.GrievanceDate),
"reply": $remark,
"repliedOn": $remark ? $iso($record.OfficeReplyDate)
}
}
}]
}
}
}
)
79 changes: 79 additions & 0 deletions config/mappings/pmkisan/grievance.support.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# PM-KISAN grievance support -> /LodgeGrievance: lodge a grievance.

request: |
(
$ch := beckn.message.support.channels[0];
{
"Type": "Reg_No_Details",
"IdentityNo": $ch.enrolmentId,
"GrievanceType": $ch.grievance.category.code,
"GrievanceDescription": $trim($ch.grievance.description)
}
)

# Known refusal: 400, else 502 (ADR-0004). Success adds case.ticketNo and filedOn (IST).
response: |
$exists(_status)
? {}
: (
$false := function($v) { $exists($v) and $lowercase($string($v)) = "false" };
$refused := $false(response.Status) or $false(response.Responce)
or $false(response.Rsponce) or $false(response.status);
/* Text or number; empty is none. */
$handle := function($v) {
($type($v) = "string" and $v != "") or $type($v) = "number" ? $string($v)
};
$ticketNo := $exists($handle(response.GrievanceID))
? $handle(response.GrievanceID)
: $handle(response.GrievanceNo);
$ch := beckn.message.support.channels[0];

/* The portal's reason, lower-cased, to what the caller is told. */
$KNOWN_REFUSALS := {
"registration number not found": {
"message": "PM-KISAN has no registration for this enrolmentId.",
"path": "$.message.support.channels[0].enrolmentId"
}
};
$portalMessage := (
$m := response.message ? response.message : response.Message;
$type($m) = "string" ? $trim($m)
);
$known := $portalMessage ? $lookup($KNOWN_REFUSALS, $lowercase($portalMessage));

$filedOn := $exists(beckn.context.timestamp)
? $fromMillis($toMillis(beckn.context.timestamp), "[Y0001]-[M01]-[D01]", "+0530")
: $now("[Y0001]-[M01]-[D01]", "+0530");
$refused and $exists($known)
? { "_error": { "status": 400, "code": "BIZ_GENERIC_ERROR",
"message": $known.message, "path": $known.path, "cause": $portalMessage } }
: $refused
? { "_error": { "status": 502, "code": "NET_DOWNSTREAM_UNAVAILABLE",
"message": "PM-KISAN did not accept the grievance", "cause": $portalMessage } }
: {
"context": $merge([beckn.context, { "action": "on_support", "timestamp": $now() }]),
"message": {
"support": {
"channels": [{
"@context": $ch.`@context`,
"@type": $ch.`@type`,
"informationMode": "Direct",
"provider": $ch.provider,
"scheme": $ch.scheme,
"enrolmentId": $ch.enrolmentId,
/* The code and description sent; a name beside the code is dropped. */
"grievance": {
"category": { "code": $ch.grievance.category.code },
"description": $ch.grievance.description
},
"case": {
"ticketNo": $ticketNo,
"status": { "code": "Registered" },
/* IST: status matches on this date. */
"filedOn": $filedOn
}
}]
}
}
}
)
27 changes: 26 additions & 1 deletion config/provider-adapter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ modules:
# one-line change.
extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global"

# Fetch api-schemas/ from the branch until the published URL serves it (ADR-0003).
extendedSchema_contextRewrite: "https://openagrinet.github.io/network-specs/api-schemas/|https://raw.githubusercontent.com/OpenAgriNet/network-specs/api-schema-packs-v0.1/api-schemas/"

extendedSchema_cacheTTL: "86400" # 24h
extendedSchema_maxCacheSize: "100"
extendedSchema_downloadTimeout: "30"
Expand Down Expand Up @@ -507,10 +510,11 @@ modules:

# ------------------------------------------------------------------
# Grievance -- support files a grievance, status reads it back.
# PM-KISAN: init (OTP), then support or status with the OTP.
# ------------------------------------------------------------------
- id: Grievance
config:
bindingKeys: "pmfby|openagrinet:Grievance"
bindingKeys: "pmfby|openagrinet:Grievance,pmkisan|openagrinet:Grievance"

# Paired by position, tried in order: status names the binding on
# its contract, support on its channel.
Expand All @@ -531,6 +535,27 @@ modules:
tokenExpiryField: responseDynamic.token.validTo
headerName: Authorization

# PM-KISAN grievance: encrypted bodies, OTP on the chatbot service (ADR-0001, ADR-0002).
pmkisan:
authScheme: none
envelope: aesGcm
envelopeKeyEnv: PMKISAN_GRIEVANCE_KEY
envelopeNonceEnv: PMKISAN_GRIEVANCE_IV
envelopeTokenEnv: PMKISAN_SERVICE_TOKEN
envelopeTokenField: TokenNo
envelopeRequestField: EncryptedRequest
envelopeResponsePaths: "d.output,output"
otpEnvelope: aesCbcKeyInBand
otpBindingKey: "pmkisan-chatbot|openagrinet:SchemeApplication"
otpTokenEnv: PMKISAN_OTP_TOKEN
otpTokenField: Token
otpRequestField: EncryptedRequest
otpResponsePaths: "d.output,output"
otpActions: init
otpVerifyPath: /ChatbotOTPVerified
otpVerifyActions: "support,status"
otpVerifyTimeoutMs: "10000"

# Declaring a step above is not enough: THIS list is what runs. A step
# that appears under providerSteps but not here never executes, and the
# request falls through to the 404 above -- which looks like a registry
Expand Down
10 changes: 9 additions & 1 deletion pkg/plugin/implementation/Grievance/cmd/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@ func (p grievanceProvider) parseConfig(config map[string]string) (*Grievance.Con
cfg.MaxResponseBytes = value
}

// Envelope settings first: the auth parser refuses unknown settings.
envelopes, rest, err := Grievance.ParseEnvelopes(config)
if err != nil {
return nil, err
}
cfg.EnvelopeByProvider = envelopes.Envelopes
cfg.HostedElsewhere = envelopes.HostedElsewhere

// One credential profile per provider.
auth, err := common.ParseProviderAuth(config)
auth, err := common.ParseProviderAuth(rest)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading